京东秒杀系统崩溃,本质是瞬时流量洪峰击穿了数据库。要扛住百万并发,核心思路是:前端限流削峰、Redis内存预扣、后端异步落库。下面用三招Redis核心策略 + Python源码,带你彻底解决高并发秒杀难题。
一、 秒杀崩溃的根源与架构总览
为什么传统架构会崩?
- 数据库瓶颈:瞬间百万QPS直接打满数据库连接池,导致服务雪崩。
- 超卖现象:单纯的
if stock > 0: stock -= 1在并发下会扣成负数。 - 库存热点:单行数据(如
product_id=666)成为热点,所有请求都在竞争同一把锁。
抗崩架构核心:分层过滤
京东/淘宝的秒杀架构,本质上是一个漏斗模型,90%的无效请求在到达Redis前就被丢弃了。
graph TD A[100万并发请求] --> B[网关层: 限流/风控] B --> C[秒杀服务: Redis预扣库存] C --> D[消息队列: 削峰填谷] D --> E[数据库: 最终落库]
二、 扛住百万并发的三招Redis策略
第一招:原子防超卖(Lua脚本)
痛点:
get和 decr是两个操作,高并发下会超卖。解法:利用Redis单线程特性,将“查库存”和“扣库存”打包成一个原子操作。
Python源码:Lua脚本防超卖
import redis
# 连接Redis(生产环境用连接池)
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# 初始化库存(秒杀开始前执行)
redis_client.set("seckill:stock:1001", 1000)
# 定义Lua脚本(核心:判断+扣减在Redis端原子执行)
SECKILL_SCRIPT = """
local stock_key = KEYS[1]
local stock = tonumber(redis.call('GET', stock_key))
if stock and stock > 0 then
redis.call('DECR', stock_key)
return 1 -- 成功
else
return 0 -- 失败
end
"""
seckill_script = redis_client.register_script(SECKILL_SCRIPT)
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
def handle_seckill(user_id, product_id):
"""处理秒杀请求(核心逻辑)"""
stock_key = f"seckill:stock:{product_id}"
# 执行Lua脚本(原子操作)
result = seckill_script(keys=[stock_key])
if result == 1:
# 秒杀成功,发送消息到MQ,异步创建订单
# send_to_mq(user_id, product_id)
return {"code": 200, "msg": "抢购成功"}
else:
return {"code": 400, "msg": "已售罄"}关键点:Lua脚本在Redis服务器端一次性执行,不会被其他请求打断,彻底解决超卖。
第二招:库存分片(解决热点Key)
痛点:所有请求都打向
seckill:stock:1001这一个Key,单分片CPU扛不住。解法:借鉴京东架构,将库存分片到多个Key中,分散压力。
Python源码:库存分片路由
def get_shard_key(product_id, user_id, shard_count=10):
"""根据用户ID哈希取模,路由到不同的库存分片"""
shard_index = hash(user_id) % shard_count
return f"seckill:stock:{product_id}:shard_{shard_index}"
def init_shard_stock(product_id, total_stock, shard_count=10):
"""初始化分片库存(总库存1000,分10片,每片100)"""
base_stock = total_stock // shard_count
for i in range(shard_count):
key = f"seckill:stock:{product_id}:shard_{i}"
redis_client.set(key, base_stock)
def sharded_seckill(user_id, product_id):
"""分片秒杀逻辑"""
shard_key = get_shard_key(product_id, user_id)
# 使用同样的Lua脚本,但操作的是分片Key
result = seckill_script(keys=[shard_key])
if result == 1:
return {"code": 200, "msg": "抢购成功"}
else:
# 该分片没了,可以尝试其他分片或直接返回售罄
return {"code": 400, "msg": "已售罄"}关键点:通过
user_id哈希路由,将100万QPS的流量分散到10个Redis Key上,性能提升10倍。第三招:令牌桶限流(保护Redis)
痛点:Redis虽然快,但百万并发依然可能打满网络带宽。
解法:在网关层或应用层使用令牌桶算法,只放行系统能处理的请求量,多余的直接返回“排队中”。
Python源码:Redis实现令牌桶限流
import time
def token_bucket_limiter(user_id, max_requests=100, refill_rate=10):
"""令牌桶限流(Redis实现)"""
key = f"rate_limit:{user_id}"
now = time.time()
# 使用Pipeline减少网络往返
pipe = redis_client.pipeline()
pipe.hgetall(key)
pipe.hset(key, 'last_time', now)
pipe.expire(key, 60)
data, _, _ = pipe.execute()
if not data:
# 第一次请求,初始化
tokens = max_requests - 1
redis_client.hset(key, 'tokens', tokens)
return True
last_time = float(data.get('last_time', now))
tokens = float(data.get('tokens', max_requests))
# 计算这段时间应该补充的令牌
elapsed = now - last_time
tokens = min(max_requests, tokens + elapsed * refill_rate)
if tokens >= 1:
tokens -= 1
redis_client.hset(key, 'tokens', tokens)
return True # 放行
else:
return False # 限流
# 在秒杀入口添加限流
def seckill_entry(user_id, product_id):
if not token_bucket_limiter(user_id):
return {"code": 429, "msg": "手速太快,请稍后再试"}
return handle_seckill(user_id, product_id)关键点:在请求到达Lua脚本前,通过令牌桶过滤掉80%的无效请求,保护Redis不被冲垮。
三、 完整秒杀流程与压测建议
1. 完整秒杀时序图
sequenceDiagram participant U as 用户 participant G as 网关(限流) participant A as 秒杀服务(Python) participant R as Redis(Lua) participant M as 消息队列 participant D as 数据库 U->>G: 点击秒杀 G->>A: 放行(令牌桶) A->>R: 执行Lua脚本(原子扣减) R->>A: 成功/失败 A->>M: 发送成功消息(异步) A->>U: 返回“抢购成功” M->>D: 消费消息,创建订单
2. 压测与部署建议
- 压测工具:使用
locust或wrk模拟百万并发。 - Redis配置:必须开启持久化(AOF),防止重启丢数据。
- 连接池:Python端务必使用
redis.ConnectionPool,避免频繁创建连接。 - 监控:实时监控Redis的
QPS和内存,设置库存告警。
四、 避坑指南与总结
⚠️ 三大坑点
- 不要用事务:Redis事务(MULTI)不是原子回滚,高并发下性能差,必须用Lua。
- 不要先查后写:任何“先查数据库再更新”的逻辑都会超卖。
- 不要忘记预热:秒杀开始前,必须把库存加载到Redis,不能临时查DB。
✅ 三招总结
策略 | 解决的问题 | 核心工具 |
|---|---|---|
Lua原子脚本 | 防超卖、数据一致性 | Redis单线程 |
库存分片 | 热点Key性能瓶颈 | 哈希路由 |
令牌桶限流 | 流量洪峰、保护Redis | 令牌桶算法 |
最后提醒:本文代码仅供学习架构思想。生产环境请务必加入风控防刷(如人机验证)、熔断降级等机制,并确保Redis集群高可用。