Webman防超卖必须启用'use_lua'=>true,否则eval退化为多次请求失去原子性;须用Redis原生实例调用,KEYS/ARGV顺序严格对应,库存扣减须用decr返回值一步判断,禁用redis.log()。

Webman里用Redis Lua脚本防超卖必须开use_lua
Webman默认的Redis连接不启用Lua支持,eval会退化成多次网络请求,原子性直接失效。不改配置,脚本写得再严谨也没用。
检查config/redis.php,确认已设置:'use_lua' => true
- 如果用的是
webman/redis插件,它底层可能没封装eval,建议绕过插件,直接用Predis\Client或原生Redis实例调用 -
KEYS和ARGV顺序不能错:脚本里KEYS[1]对应PHP传入的$keys[0],ARGV[1]对应$args[0] - 禁用
redis.log()——Swoole协程下会报错
库存扣减别先get再decr,直接用decr返回值判断
写成if ($redis->get('stock:1001') > 0) { $redis->decr('stock:1001'); }是典型错误。两个请求同时读到1,都会执行decr,结果变成-1。
正确做法是让decr一步到位:local new_stock = redis.call('decr', KEYS[1])
然后立刻判断:if new_stock
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 返回整数(如
1成功、-1失败),别返回JSON字符串,省去PHP解析开销 - 不要在PHP层做
if判断库存是否够用,校验必须压进Lua脚本内部 - 如果需要扣减多件,用
decrby并检查返回值是否≥0
用户参与+中奖名额要拆成两个Redis key,别堆在一个脚本里
一个活动要限制“每人只能抽一次”+“某个奖品最多中10次”,这两个规则逻辑不同、生命周期不同、失效时间也不同。硬塞进同一个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耗尽”两种失败 - 避免用
INCR后判断——INCR本身不带条件,超限后还得回滚,不如decr返回值直接可信
Webman协程下flock完全失效,分布式锁必须带value校验
flock("lock.txt", LOCK_EX)在Webman里毫无意义。Swoole协程共享进程内存,flock是进程级系统调用,协程间根本感知不到彼此加锁。
真要用锁,必须用Redis的SET key value NX EX原子命令加锁,且value必须唯一(比如uniqid('', true).getmypid()拼接),解锁时必须用Lua脚本先校验value再DEL。
- 禁用
setnx()+expire()两步操作——中间崩溃会导致key永不过期,形成死锁 - 别直接
$redis->del($key),A进程锁超时释放后,B拿到锁;A执行完却删掉B的锁,后续所有请求都绕过锁 - 队列消费场景下,优先考虑无锁方案(如结构化队列+快照比对),锁只是兜底,不是首选

















