swoole客户端connect()失败后不自动重连是设计使然,需业务层手动处理;应使用co::sleep()配合有限次数循环实现可控重连,避免阻塞协程或内存泄漏。

connect() 失败后不重试是默认行为
Swoole 的 Co\HTTP\Client、Co\Redis、Co\MySQL 等客户端,调用 connect() 返回 false 或抛出异常时,框架本身不会自动重连。这不是 bug,是设计使然——Swoole 把连接控制权完全交还给业务层。
常见错误现象:Connection refused 或 Operation timed out 后程序直接报错退出,日志里看不到第二次尝试;或者用了 while (! $client->connect()) { sleep(1); },但协程被阻塞,整个 worker 卡住。
- 必须手动捕获异常或检查返回值,不能依赖“连不上就等会儿再试”的直觉
-
sleep(1)在协程里会退化为同步等待,应改用co::sleep(1) - 重试前建议判断错误类型:比如
Connection refused可重试,而Too many connections重试只会雪上加霜
用 co::sleep + 有限次数循环实现可控重连
最轻量、最可控的重连写法,就是把重试逻辑收在一次连接操作内,不依赖外部定时器或全局状态。
以 Co\HTTP\Client 为例:
$client = new Co\HTTP\Client('api.example.com', 443, true);
$maxRetries = 3;
for ($i = 0; $i <= $maxRetries; $i++) {
if ($client->connect()) {
break;
}
if ($i === $maxRetries) {
throw new RuntimeException('Failed to connect after ' . $maxRetries . ' retries');
}
co::sleep(0.5 + $i * 0.3); // 指数退避雏形,避免抖动
}
- 每次
connect()前无需 new 新实例,但注意:若之前 connect 过失败,$client内部状态可能残留,建议失败后unset($client)或重新 new -
co::sleep()参数单位是秒,支持小数,别传整数 1 当作“1 秒”还觉得慢——实际是 1000ms,太长 - 不要无限制
while (true),必须设上限,否则服务端短暂不可用会导致协程永久 hang 住
onError / onClose 回调里触发重连容易踩内存泄漏
有人想在 Co\HTTP\Client 的 onError 回调里自动重建连接,这在常驻进程(如 WebSocket server)中看似合理,但极易出问题。
典型错误写法:
$client->on('error', function ($client) {
$client->close();
$client = new Co\HTTP\Client(...); // ❌ 变量作用域丢失,$client 不会更新到外层
$client->connect(); // ❌ 新 client 没人持有,很快被 GC,连接也立刻断
});
- 回调函数里的
$client是副本,重新赋值对外层无效 - 没地方存新 client 实例,等于“连上了又丢”,后续请求仍用已 close 的旧实例,报
Client is not connected - 更隐蔽的问题:如果在 onWorkerStart 里启动一个常驻协程做心跳+重连,但没用
go包裹,它会阻塞 worker 启动流程
连接池场景下重连逻辑要下沉到 acquire 阶段
如果你用的是 swoole/library 里的 Pool 或自建连接池,重连不能放在 get() 返回后做,而必须在 acquire() 内部完成——否则拿到的可能是刚断开但还没被标记失效的连接。
关键点:
- 每次从池中取出连接后,先调
$conn->isConnected()(如果有)或发个轻量 ping(如redis->ping()),失败则立即$pool->release($conn)并重试 acquire - 不要在业务代码里写
if (!$conn->ping()) { reconnect(); }—— 这样已经晚了,当前请求可能已因连接中断失败 - 连接池的
createFunc必须包含完整的重连兜底逻辑,而不是只做一次 connect
真正难的不是写几行重试代码,而是想清楚「谁该负责发现断连」「谁该决定重试时机」「重试失败后要不要降级」——这些决策一旦写死在底层,后面改起来比重写还累。










