在高性能服务中,Swoole 常被用于构建 TCP/HTTP 长连接服务。但最近我们在实际业务(撮合引擎)运行中,遇到一个隐蔽但影响极大的问题:客户端 TCP 请求出现粘包/串包现象,导致撮合逻辑异常。
经过多轮排查,最终定位到问题根因竟是我们为了防止内存泄漏设置的一个“自杀式”机制——worker 每 6 小时定时 kill 重启。以下是详细的分析过程与解决方案。
背景
在撮合系统中,核心引擎是一个基于 Swoole 的 TCP 长连接服务。由于部分逻辑存在小量内存泄漏,为防止单个 worker 长时间运行导致内存持续膨胀,我们在 worker 中加入了定时 kill 逻辑:
\Swoole\Timer::after(6 * 3600 * 1000, function () use ($pid) {
\swoole_process::kill($pid, SIGKILL);
});
每 6 小时主动结束进程,由主进程重新拉起新 worker。
问题现象
在部署上述机制后,服务运行一段时间后开始出现:
- 撮合撮单失败、交易撮合顺序异常
- TCP 粘包、乱序读取
- 服务日志显示某些连接数据内容解析出错,甚至出现跨连接数据错乱
这些问题表现为概率性、重启后恢复、但持续重现,并严重影响业务正确性。
初步排查
首先排除典型 TCP 粘包原因:
- 包格式问题(有明确包头+长度)
- 客户端发送速率过快?
- 服务端解析逻辑有问题?
但问题不是出现在协议设计或解析,而是worker 被 kill 后再次拉起的进程中,继承到了旧连接的 socket,接收到的 TCP 包并不是新发起连接,而是“断头续传”的旧数据。
问题定位
Swoole 在 TCP 模式下,worker 进程直接处理连接,每个连接都绑定在具体的 worker 上。如果 worker 被 SIGKILL
杀掉:
- 不触发
onWorkerExit
清理逻辑 - 未关闭 fd/socket 连接
- 内核仍保持连接句柄(TIME\_WAIT)
- 新 worker 重启后,部分 fd 被重复使用,导致连接状态错乱
最终表现为:一个新连接的 fd 实际复用了一个已死掉但未释放的旧连接状态,进而发生串包、错包、脏读现象。
解决方案
彻底解决问题的关键在于避免强杀 worker,改为优雅重启机制。
✅ 正确做法:使用 max_request
自动重启
Swoole 提供原生的 worker 生命周期控制方式,可在 worker 处理完一定数量请求后,自动触发优雅退出并重启:
$server->set([
'max_request' => 100000, // 每个 worker 处理 10 万个请求后重启
'max_wait_time' => 5, // 等待协程退出最大时间(秒)
'reload_async' => true, // 异步 reload 避免阻塞
]);
这一方式的优势:
- worker 在处理完请求后再退出,不中断连接
- 可触发
onWorkerExit
做清理工作 - 内存、连接、资源均能被安全释放
🛑 禁止使用 SIGKILL
\swoole_process::kill($pid, SIGKILL);
该方法属于强制终止行为,不会触发任何事件回调或资源回收机制,极易引发以下问题:
- 文件描述符未关闭
- 内存未释放
- Socket 状态未清理
- Reactor 层混乱
- 导致 TCP 粘包/串包/连接混乱
实际验证效果
我们在生产环境中将原本定时 kill worker 的逻辑移除,并仅依赖 max_request
机制进行周期性重启。
上线后观察超过 7 天:
- TCP 粘包/串包问题完全消失
- worker 内存曲线稳定(结合调优也可使用
memory_limit
) - 撮合撮单行为恢复正常
总结
🧠 教训
在基于 Swoole 的 TCP 服务中,worker 生命周期的管理需格外谨慎:
- 千万不要强制 kill worker
- TCP 长连接服务尤其敏感,任何未清理的 socket 都可能影响新连接
- 优雅退出是保障服务稳定的核心原则
🔧 推荐配置(TCP 服务通用)
[
'max_request' => 100000,
'max_wait_time' => 5,
'reload_async' => true,
'enable_coroutine' => true,
'dispatch_mode' => 2, // 防止连接错乱(粘包时建议关闭抢占式调度)
]
💬 最后
这次问题虽源于一个看似“合理”的自动重启方案,但也提醒我们:高并发下的服务维护,不能忽视任何一个“异常退出”的边界场景。
优雅重启,不仅是性能优化,也是稳定性的基本保障。
如你也在使用 Swoole 构建 TCP 服务,建议立即排查是否存在 SIGKILL 相关逻辑,及时修正,防患于未然。