worker_num应据业务类型与压测结果设定:IO密集型可设CPU核数×2,CPU密集型宜设核数或加1–2;过高易致OOM或调度抖动,过低则并发不足。

worker_num 设置多少才不浪费也不卡死
别盲目照搬 CPU 核数 × 2。实际并发连接数和业务逻辑复杂度才是决定因素。比如纯转发消息、无 DB/Redis 调用的轻量路由,worker_num 设为 8~16 就能稳扛 5 万连接;但若每个 message 事件都要查一次 MySQL + 写一次 Redis,那设太高反而导致上下文切换频繁、CPU cache 命中率下降。
建议做法:
- 压测前先用
swoole_server->stats()观察connection_num和tasking_num的波动趋势 - 把耗时操作(如用户鉴权、消息落库)全挪到
task_worker中执行,主worker只做协议解析和内存映射 - 如果日志里频繁出现
WARNING swWorker_loop: worker process exit, pid=xxx,大概率是worker_num过高 + 单 worker 承载过重
heartbeat_check_interval 和 heartbeat_idle_time 怎么配才不掉链又不占资源
这两个参数不是独立生效的:Swoole 每隔 heartbeat_check_interval 秒扫描一次所有连接,若某连接距离上次收包已超 heartbeat_idle_time 秒,就主动 close。常见错误是把 heartbeat_idle_time 设得太小(比如 30),导致弱网用户频繁断连;或设得太大(比如 1800),让僵尸连接长期占用 fd。
生产环境推荐组合:
-
heartbeat_check_interval = 30(每半分钟扫一次,开销可控) -
heartbeat_idle_time = 300(5 分钟无数据即断,兼顾移动网络抖动与资源回收) - 客户端必须实现 PING/PONG 心跳(不能只靠 TCP keepalive),否则服务端无法区分“真离线”和“暂时没发消息”
enable_coroutine = true 有什么隐藏代价
开启协程后,mysql_connect、file_get_contents、PDO 默认行为会自动协程化,看起来很省事——但问题出在第三方 SDK 上。比如某些支付回调验签库内部用了 openssl_pkey_get_private 这类阻塞函数,一旦被协程调度器包裹,就会卡住整个 worker 进程。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
容易踩的坑:
- 所有
sleep()、usleep()必须换成co::sleep(),否则直接阻塞当前协程所在 worker - 自定义的 Redis 客户端若没做协程适配(比如用 stream_socket_client 手动建连),会在高并发下出现连接池耗尽或超时异常
-
set_error_handler()在协程中不生效,错误会直接抛到onError回调,而不是你写的 try/catch 里
task_worker_num 不设或设太小会导致什么
只要你的 message 回调里有哪怕一行 file_put_contents 或 mysqli_query,就必须配 task_worker_num。否则所有 IO 都在 worker 进程里同步执行,一个慢查询就能让整个进程卡住,新连接进不来、心跳包发不出、其他用户消息全堵住。
配置建议:
- 纯转发场景(如客服分配、广播通知)可不启用 task worker
- 涉及 DB/Redis/HTTP 请求的,
task_worker_num至少设为worker_num × 1.5,且不低于 4 - 用
$server->taskwait()时要注意超时时间,别让前端等超过 3 秒,否则用户感知就是“发送失败”
最常被忽略的是:修改了 task_worker_num 后没重启服务,或者忘了在 onTask 回调里调用 $server->finish() —— 这会导致 task worker 一直卡在 pending 状态,后续任务永远得不到执行。

















