必须用redis+lua原子操作,因php变量和数据库事务无法应对高并发超卖;webman需启用'use_lua'=>true,脚本须封装校验、扣减、中奖判定,keys/argv顺序不可错,禁用redis.log()。

Webman 里做抽奖,高并发下防超卖不能靠 PHP 层变量或简单数据库事务——必须把「库存校验 + 扣减 + 中奖判定」压进原子操作,否则压测一跑就超发。
Webman 调用 Redis Lua 脚本必须启用 use_lua
Webman 默认的 Redis 连接不自动启用 Lua 支持,eval 会退化成多次网络请求,失去原子性。不改配置,脚本再写得再好也白搭。
- 在
config/redis.php中确认已开启:'use_lua' => true - 若用的是
webman/redis插件,检查其底层是否封装了eval;推荐直接用Predis\Client或原生Redis实例调用,避免中间层干扰 - 调用时第一个参数是
KEYS数组(如['stock:1001', 'user:lock:1001:123']),第二个是ARGV数组(如[1, 'abc123']),顺序反了脚本里取不到值
decr 返回值判断库存比 get + set 更可靠
别写「先 get 库存,再 if > 0 就 decr」——中间有毫秒级窗口,多个请求能同时读到 stock=1,然后一起扣成 -1。
- 直接用
redis.call('decr', KEYS[1]),它返回扣减后的值,一步到位 - 脚本里立刻判断:
if new_stock ,不是靠 PHP 层 if 判断 - 返回整数(如
1表示成功,-1表示库存不足),别返回 JSON 字符串,省去 PHP 解析开销
用户维度限参与 + 活动维度限中奖,得拆成两个 Redis key
一个用户对同一活动只能抽一次?中过奖不能再中?这些规则不能堆在同一个 key 里查,否则 Lua 脚本逻辑臃肿、难维护、易出错。
- 用户参与记录用:
lottery:participated:{activity_id}:{user_id},SETNX写入,过期时间设为活动截止后 1 小时 - 中奖名额控制用:
lottery:win_quota:{activity_id}:{prize_id},配合decr原子扣减 - Lua 脚本里用
redis.call('exists', KEYS[2])和redis.call('decr', KEYS[3])分别检查,避免把「未参与」和「 quota 耗尽」混成一种失败原因
Webman 的协程特性反而放大了锁误用风险
Webman 默认运行在 Swoole 协程下,flock 或文件锁完全失效——协程共享进程内存,flock 是进程级系统调用,协程间不感知。
- 别在 Webman 里用
flock("lock.txt", LOCK_EX),压测时看似排队,实则大量协程同时穿透锁进入业务逻辑 - Redis 分布式锁也慎用:Webman 多 worker 进程共用一个 Redis 连接池,但
SETNX+DEL组合没加 value 校验,容易 A 进程删掉 B 进程的锁 - 真要用锁,必须走 Lua 解锁脚本:
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock_key abc123
最常被忽略的一点:Lua 脚本里不能调用 redis.log(),Webman 的 Redis 驱动不支持,报错信息是 @user_script:1: attempt to call a nil value,而不是明确提示「log 不可用」——这个错误会卡住整个 eval 流程,且极难定位。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











