能,但需规避默认配置缺陷:红包扣减须用redis lua原子脚本,广播应分级通过pub/sub实现,防刷依赖服务端setnx/incrby限流,库存须预热。

Workerman 能扛住万人红包雨的并发压力吗?
能,但前提是不用默认配置硬扛。Workerman 本身是常驻内存的异步 I/O 框架,单机轻松支撑 10k+ 长连接,但「抢红包」不是纯推送——它包含高竞争的原子扣减、实时排名、防刷校验、消息广播,这些环节稍有设计偏差就会在 5000+ 用户同时点击时崩在 Redis::decr 或 file_put_contents 上。
关键不是 Workerman 多快,而是你把哪部分逻辑塞进了它的主循环。红包库存扣减必须交给 Redis 原子操作,排行榜更新建议用 ZINCRBY + ZREVRANGE,所有涉及磁盘写、HTTP 请求、sleep 的操作一律剥离到独立进程或延后处理。
怎么用 Redis + Workerman 实现红包原子发放?
别用 PHP 的 if ($stock > 0) { $redis->decr('redpack:stock'); ... } —— 这在并发下必然超发。必须用 Lua 脚本保证「读-判-扣」三步不可拆分:
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) > 0 then
redis.call('DECR', KEYS[1])
return 1
else
return 0
end
Workerman 中调用方式:
- 把脚本内容存为
redpack_claim.lua,首次加载时用$redis->eval($script, 1, 'redpack:stock') - 返回 1 表示抢到,立刻触发广播;返回 0 直接丢弃请求,不走任何后续逻辑
- 务必设置 Redis 连接为
pconnect,避免每次新建连接拖慢响应
为什么不能在 onMessage 里直接广播给所有人?
万人在线时,一个红包触发的 foreach ($connections as $conn) { $conn->send(...); } 会瞬间吃光内存和带宽,且 TCP 发送缓冲区溢出会导致大量连接假死。Workerman 的 $connection->send() 是非阻塞的,但底层仍依赖系统 socket 缓冲区,缓冲区满时连接会被 Workerman 自动踢掉。
正确做法是分级广播:
- 用
Redis pub/sub将红包结果推到频道redpack:live:1001(1001 是直播间 ID) - 每个 Worker 进程起一个单独的 Redis 订阅协程(用
tick+redis->subscribe),收到消息后只推送给本 Worker 管理的连接 - 前端用 WebSocket 心跳保活,服务端对 30 秒无响应的连接主动
close
用户重复点击、脚本刷包怎么拦?
Workerman 层面只能做轻量级拦截,重逻辑必须下沉。例如:
- 每个用户每轮红包活动只允许成功领取 1 次:用
SETNX redpack:user:1001:uid_12345 1 EX 300,失败直接返回「已领过」 - 限制单 IP 每秒最多 3 次请求:用
INCRBY ip_limit:192.168.1.100 1+EXPIRE组合,超限则throw new Exception('请求太频繁') - 前端按钮点击后立即置灰 + 倒计时,但别信它——所有校验必须在服务端重做
最易被忽略的是「红包雨持续期间的库存预热」:不要等第一个请求进来才从 DB 查库存,启动时就用 SET redpack:stock 10000,并监听运营后台的「补货」指令实时更新。否则第一波抢完库存变 0,后面所有请求都走失败路径,用户体验断层。











