Workerman可通过长连接+内存队列+定时推送实现排队进度实时更新:onMessage轻量入队并返回初始位置,Timer定时计算并主动广播每人实时序位,onClose与超时机制确保及时出队,多进程需Redis或GatewayWorker同步状态。

Workerman 本身不提供“排队进度”功能,但能用长连接 + 内存状态 + 主动推送,把排队逻辑从 HTTP 短连接里彻底解耦出来。关键不是“怎么排队”,而是“怎么让每个用户实时看到自己排第几位、前面还有几人、是否快轮到了”。
用 onMessage 接收抢购请求并入队
客户端点击“抢购”后,WebSocket 连接直接发一条消息(比如 {"action":"join_queue","sku_id":"1001"}),服务端在 onMessage 回调里处理:
- 不直接查库或执行业务,只做轻量级入队:把
$connection->uid和商品 ID 存进一个全局队列数组(如$worker->queue[1001][] = $connection) - 记录入队时间戳,用于后续超时清理和顺序判定(避免靠 PHP 数组键序决定先后)
- 立刻回传初始状态:
$connection->send(json_encode(['status'=>'queued','position'=>count($worker->queue[1001])])) - 注意别在
onMessage里做耗时操作(如 DB 查询、Redis 操作),否则会阻塞整个事件循环
用 Timer::add 定时轮询并广播进度
排队不是静态的,得持续更新每个人的位置。不能等用户来问,要主动推:
- 在
onWorkerStart里启动一个定时器,比如每 500ms 扫一次各商品队列:Timer::add(0.5, function() use ($worker) { ... }) - 对每个
$sku_id队列,遍历所有连接,计算当前连接在队列中的索引(即前面还有几人),再减去已释放/超时的连接数 - 只给还在连接中的用户发更新:
$conn->send(json_encode(['position'=>$pos,'remaining'=>$pos-1])) - 务必加连接有效性判断:
if ($conn->isClosed() || !$conn->uid) { unset(...); continue; },否则会触发 warning 或 crash
用 onClose 和超时机制及时出队
用户关页、断网、切后台都会导致连接中断,必须及时清理,否则队列会“卡死”:
- 在
onClose里遍历所有商品队列,把该$connection从对应数组中unset掉 - 单独维护一个
$worker->queue_timeout记录每个连接入队时间,定时器里检查是否超时(比如 2 分钟没响应),超时则强制出队 - 不要依赖前端发“退出排队”消息——它根本不可靠;服务端必须有兜底的被动清理逻辑
- 如果用 Redis 做分布式队列(多 Worker 进程时必需),就别用 PHP 数组,改用
lpush/rpop+llen,但要注意llen返回的是总长度,得用lrange查用户位置,性能会略降
前端用 ws.onmessage 渲染进度条
客户端收到 JSON 后,不直接更新 DOM,而是走统一状态管理:
- 解析
position,若为 1 表示“即将开始”,可播放提示音、高亮按钮 - 用 CSS transition 做平滑进度条变化:
width: calc((1 - ${pos} / ${total}) * 100%)(total 可预估或由后端返回) - 收到
{"status":"success","order_id":"xxx"}才跳转下单页;在此之前所有 UI 都是“排队中”状态 - 别信
event.currentTarget.readyState === WebSocket.OPEN—— 网络抖动时它可能还是 OPEN,但实际已断,得配合心跳保活
最易被忽略的点:单个 Worker 进程只能维护自己内存里的队列。多进程部署时,$worker->queue 是隔离的,必须用 Redis 或 GatewayWorker 的 sendToUid 跨进程通信,否则用户可能连到 A 进程入队,却被 B 进程的定时器忽略掉。

















