在高并发秒杀场景中,数据库往往成为性能瓶颈,直接使用数据库事务扣减库存极易出现超卖或连接耗尽。借助Redis的高性能与原子特性,可以用队列接收请求,再用Lua脚本完成库存判断与扣减,从而平稳应对大并发。

为什么用Redis队列和Lua脚本
秒杀开始瞬间会有大量请求同时到达。如果让每个请求都去访问数据库,系统很容易崩溃。Redis队列可以把这些请求先缓存起来,后端按能力消费。而库存扣减必须保证原子性,Lua脚本在Redis中执行时不会被其他命令插入,正好适合做判断加扣减。
- Redis队列:异步削峰,控制处理速率
- Lua脚本:原子操作,避免并发超卖
- Python:编写业务逻辑与调用Redis
用Redis队列接收秒杀请求
下例使用Redis的list结构作为队列,将用户秒杀请求推入seckill_queue。
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)
def push_seckill(user_id, item_id):
# 将请求放入队列,实际中可加限流
r.rpush('seckill_queue', f'{user_id}:{item_id}')
return True
push_seckill(1001, 50)
用Lua脚本原子扣减库存
库存key为stock:item_id。Lua脚本先判断库存是否大于0,满足才减一并返回成功。
-- 秒杀扣减库存脚本
-- KEYS[1]为库存key
-- ARGV[1]为扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if not stock then
return -1
end
if stock < tonumber(ARGV[1]) then
return 0
end
redis.call('DECRBY', KEYS[1], ARGV[1])
return 1
在Python中加载并执行该脚本:
lua_script = """
local stock = tonumber(redis.call('GET', KEYS[1]))
if not stock then
return -1
end
if stock < tonumber(ARGV[1]) then
return 0
end
redis.call('DECRBY', KEYS[1], ARGV[1])
return 1
"""
cmd = r.register_script(lua_script)
def consume():
# 从队列取任务
task = r.lpop('seckill_queue')
if not task:
return
user_id, item_id = task.decode().split(':')
res = cmd(keys=[f'stock:{item_id}'], args=[1])
if res == 1:
print(f'用户{user_id}秒杀成功')
else:
print(f'用户{user_id}秒杀失败')
consume()
注意事项
| 问题 | 说明 |
|---|---|
| 库存预热 | 秒杀前用set命令把库存写入Redis |
| 消息丢失 | 队列任务消费前可落盘或写日志 |
| 脚本阻塞 | Lua不要写复杂循环,保持短小 |
通过上述方式,Python配合Redis队列与Lua脚本可以高效处理大并发秒杀,避免数据库压力与超卖风险。