协程池不能直接new swoole\coroutine\mysql,因其不自带连接复用能力,每次new都会新建tcp连接,易导致time_wait堆积、端口耗尽或tls握手开销飙升;必须用swoole\coroutine\channel封装预分配、借用、归还及超时淘汰机制。

协程池为什么不能直接 new Swoole\Coroutine\MySQL()
协程客户端对象本身不带连接复用能力,每次 new 都会新建 TCP 连接(哪怕目标地址相同),在高并发下极易触发 TIME_WAIT 堆积、端口耗尽或 TLS 握手开销飙升。Swoole 的协程 MySQL 客户端默认是“一次一连”,不是连接池。
真正需要的是连接的预分配、借用、归还与超时淘汰机制。你得自己封装一层:用 Swoole\Coroutine\Channel 管理空闲连接,配合 max_idle_time 和 max_active 控制生命周期。
- 切忌在
onRequest中每次 new 一个客户端 —— 这等于放弃连接复用 - 不要把
$client->close()当成“释放连接”,它实际是销毁连接;归还应调用$channel->push($client) - 首次获取连接失败时,需判断是否因
Channel::pop()超时,而不是直接抛异常中断请求
如何用 Channel 实现最小可用协程池
核心就三步:预热、借、还。不需要第三方组件,Swoole\Coroutine\Channel 足够轻量可靠。
示例关键逻辑:
$pool = new \Swoole\Coroutine\Channel(10);
for ($i = 0; $i connect(['host' => '127.0.0.1', 'user' => 'root']);
$pool->push($client);
}
// 获取连接(带超时)
$client = $pool->pop(1.0);
if (!$client) {
throw new \Exception('MySQL pool timeout');
}
// 使用后归还(不是 close!)
$pool->push($client);
-
Channel容量即最大空闲连接数,设太小会导致排队阻塞,太大则浪费内存 - 归还前建议调用
$client->connected检查连接是否存活,断连则丢弃不 push - 不要在协程池外保留对
$client的长期引用,防止意外复用已归还连接
协程池必须配合 Runtime Hook 吗
必须。哪怕你用了池,如果底层 IO 函数没被协程化,$client->query() 这类调用仍会同步阻塞整个 Worker。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
典型漏配场景:
- 只启用了
SWOOLE_HOOK_ALL,但写在onWorkerStart之后 —— hook 必须在协程启动前生效 - 用了
file_get_contents()加载配置,该函数默认未被 hook,导致阻塞(应改用Co\Http\Client或启用SWOOLE_HOOK_FILE) - 连接池初始化代码放在
onRequest里,每次请求都重新 enableCoroutine —— hook 只需全局启用一次
验证方式:在池内连接上调用 sleep(1)(非 Co::sleep),若其他请求卡住,说明 hook 未生效。
协程池和 Swoole 内置连接池的区别在哪
Swoole 5.x+ 提供了 Swoole\ConnectionPool 类,但它只是 Channel 封装 + 生命周期管理的语法糖,**不自动处理连接健康检测、重连、多节点路由等业务逻辑**。
真实项目中容易忽略的点:
-
Swoole\ConnectionPool的get()方法返回的是原始对象,你仍需自行判断连接有效性(比如执行PING) - 它不感知数据库主从角色,读写分离需上层路由,不能靠池本身解决
- 当 Worker 进程重启(如
max_request触发)时,池内连接不会自动 close,需监听onWorkerStop手动清理
所以,与其依赖内置池的“便利性”,不如吃透 Channel 的行为边界——它更透明,也更可控。










