1. 项目概述
最近帮朋友开发了一个鲜花团购秒杀系统,用Python+Flask实现后端,前端采用Vue.js。这个项目最核心的挑战是如何应对秒杀场景下的高并发问题,同时保证系统稳定性和数据一致性。经过两个月的开发和优化,系统成功支撑了情人节单日50万次的秒杀请求,峰值QPS达到3000+。
2. 系统架构设计
2.1 技术选型考量
选择Flask而非Django主要基于以下考虑:
- 秒杀系统需要高度定制化的架构设计,Flask的轻量级特性更适合
- 微服务架构下各模块耦合度低,Flask的灵活性优势明显
- 性能调优空间更大,可以针对性地优化关键路径
前端选择Vue.js而非Bootstrap的原因:
- 秒杀页面需要实时更新库存和倒计时,Vue的响应式特性更合适
- 组件化开发便于复用秒杀按钮、倒计时等通用组件
- 更好的用户体验,避免传统表单提交导致的页面刷新
2.2 微服务模块划分
系统分为四个核心微服务:
- 用户服务:处理注册、登录、个人信息管理
- 商品服务:管理鲜花商品信息、分类、库存
- 订单服务:处理订单创建、支付、查询
- 秒杀服务:独立部署的核心秒杀逻辑
提示:秒杀服务需要单独部署,避免影响其他业务功能
3. 数据库设计
3.1 核心表结构
python复制class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
password_hash = db.Column(db.String(128))
phone = db.Column(db.String(11))
# 其他字段...
class Product(db.Model):
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
description = db.Column(db.Text)
price = db.Column(db.Float)
stock = db.Column(db.Integer)
image_url = db.Column(db.String(256))
class FlashSale(db.Model):
__tablename__ = 'flash_sales'
id = db.Column(db.Integer, primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('products.id'))
start_time = db.Column(db.DateTime)
end_time = db.Column(db.DateTime)
sale_price = db.Column(db.Float)
stock_count = db.Column(db.Integer)
# 其他字段...
3.2 关键设计决策
-
库存字段分离设计:
- Product表存储常规库存
- FlashSale表存储秒杀专用库存
- 避免常规销售影响秒杀活动
-
索引优化:
sql复制CREATE INDEX idx_flash_sale_time ON flash_sales(start_time, end_time); CREATE INDEX idx_product_stock ON products(stock); -
读写分离配置:
python复制SQLALCHEMY_BINDS = { 'master': 'mysql+pymysql://user:pass@master:3306/db', 'slave': 'mysql+pymysql://user:pass@slave:3306/db' }
4. 秒杀功能实现
4.1 核心流程设计
python复制def flash_sale(user_id, product_id):
# 1. 验证用户资格
if not check_user_qualification(user_id):
return {"code": 400, "msg": "用户不符合参与条件"}
# 2. Redis预减库存
stock_key = f"flash_sale_{product_id}_stock"
remaining = redis_client.decr(stock_key)
if remaining < 0:
redis_client.incr(stock_key) # 回滚
return {"code": 400, "msg": "秒杀已结束"}
# 3. 生成临时订单
order_key = f"flash_sale_order_{user_id}_{product_id}"
if redis_client.get(order_key):
return {"code": 400, "msg": "请勿重复提交"}
# 4. 异步处理
order_data = {
"user_id": user_id,
"product_id": product_id,
"timestamp": time.time()
}
redis_client.setex(order_key, 3600, json.dumps(order_data))
mq_client.publish("flash_sale_orders", order_data)
return {"code": 200, "msg": "秒杀成功,请10分钟内完成支付"}
4.2 关键技术实现
-
Redis库存预热:
python复制def preheat_flash_sale(): sales = FlashSale.query.filter( FlashSale.start_time <= datetime.now(), FlashSale.end_time >= datetime.now() ).all() for sale in sales: key = f"flash_sale_{sale.product_id}_stock" redis_client.set(key, sale.stock_count) -
分布式锁实现:
python复制def acquire_lock(lock_key, timeout=10): identifier = str(uuid.uuid4()) end = time.time() + timeout while time.time() < end: if redis_client.setnx(lock_key, identifier): redis_client.expire(lock_key, timeout) return identifier time.sleep(0.001) return False -
消息队列处理:
python复制def process_flash_sale_order(channel, method, properties, body): order_data = json.loads(body) # 创建数据库订单 try: with db.session.begin(): order = Order( user_id=order_data['user_id'], product_id=order_data['product_id'], status='pending' ) db.session.add(order) # 实际扣减数据库库存 FlashSale.query.filter_by( product_id=order_data['product_id'] ).update({ 'stock_count': FlashSale.stock_count - 1 }) except Exception as e: # 失败时恢复Redis库存 redis_client.incr(f"flash_sale_{order_data['product_id']}_stock") redis_client.delete(f"flash_sale_order_{order_data['user_id']}_{order_data['product_id']}")
5. 安全防护措施
5.1 基础安全配置
python复制# Flask安全配置
app.config.update(
SECRET_KEY=os.urandom(24),
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SECURE=True,
PERMANENT_SESSION_LIFETIME=timedelta(minutes=30)
)
# SQLAlchemy防注入
query = Product.query.filter(Product.name.like('%{}%'.format(escape_string(search_term))))
5.2 秒杀专用防护
-
限流实现:
python复制from flask_limiter import Limiter limiter = Limiter( app, key_func=get_remote_address, default_limits=["200 per minute", "50 per second"] ) @app.route('/api/flash_sale', methods=['POST']) @limiter.limit("10/second") def flash_sale_endpoint(): # 秒杀接口 -
验证码策略:
- 前置验证:活动开始前5分钟生成图形验证码
- 频控验证:同一IP连续请求3次后需要验证码
- 分布式计数:
python复制ip_key = f"flash_sale_ip_{remote_ip}" count = redis_client.incr(ip_key) redis_client.expire(ip_key, 60) if count > 3: return require_captcha()
6. 性能优化策略
6.1 多级缓存设计
-
热点数据缓存:
python复制@cache.memoize(timeout=60) def get_flash_sale_info(product_id): return FlashSale.query.get(product_id) -
本地缓存优化:
python复制from werkzeug.contrib.cache import SimpleCache local_cache = SimpleCache() def get_product_detail(product_id): key = f"product_{product_id}" result = local_cache.get(key) if result is None: result = Product.query.get(product_id) local_cache.set(key, result, timeout=10) return result
6.2 服务端优化
-
Gunicorn配置:
bash复制
gunicorn -w 8 -k gevent --worker-connections 1000 -b 0.0.0.0:8000 app:app -
Nginx优化:
nginx复制upstream flask { server 127.0.0.1:8000; keepalive 32; } server { listen 80; location / { proxy_pass http://flask; proxy_http_version 1.1; proxy_set_header Connection ""; } }
7. 测试与部署
7.1 压力测试方案
使用Locust模拟秒杀场景:
python复制from locust import HttpUser, task, between
class FlashSaleUser(HttpUser):
wait_time = between(1, 2.5)
@task
def participate_flash_sale(self):
self.client.post("/api/flash_sale", json={
"product_id": 1,
"user_id": random.randint(1, 10000)
})
关键指标:
- 成功率 > 99.9%
- 平均响应时间 < 500ms
- 99分位响应时间 < 1s
7.2 容器化部署
Docker-compose配置示例:
yaml复制version: '3'
services:
web:
image: flask-app
ports:
- "8000:8000"
environment:
- REDIS_HOST=redis
depends_on:
- redis
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- redis_data:/data
8. 踩坑经验分享
-
库存超卖问题:
- 现象:Redis预减库存成功但数据库最终扣减失败
- 解决方案:引入二次确认机制,异步处理时再次检查库存
-
消息堆积问题:
- 现象:高峰期订单消息堆积导致延迟
- 优化:动态增加消费者数量,设置消息TTL
-
缓存雪崩防护:
- 关键:Redis集群+本地缓存+随机过期时间
python复制def get_with_cache(key, ttl=60): base_ttl = ttl jitter = random.randint(0, 30) return redis_client.get(key, ttl=base_ttl+jitter) -
真实用户识别:
- 技巧:结合设备指纹+行为分析识别机器人
python复制def is_human_user(request): fingerprint = generate_fingerprint(request) behavior_score = analyze_behavior(request) return behavior_score > 0.7