hyperf 3.1 中不可直接 new guzzlehttp\client(),必须使用 coroutinehandler 封装并配置 connect_timeout、read_timeout 和 timeout,所有请求须在协程内发起,且需 try-catch 捕获异常释放资源。

Hyperf 3.1 中使用 Guzzle 发起 HTTP 请求时,**不能直接 new GuzzleHttp\Client()**,否则会因同步阻塞导致协程调度器挂起、QPS 断崖下跌甚至进程假死。真正安全的做法是通过 Hyperf 提供的协程适配层,配合显式超时配置,从根源上防止协程无限等待。
必须用 CoroutineHandler 封装 Guzzle
Hyperf 的 Hyperf\Guzzle\CoroutineHandler 是 Guzzle 与 Swoole 协程之间的桥梁。它底层调用的是 Swoole\Coroutine\Http\Client,而非 curl 或 stream,确保所有 I/O 操作可被协程调度器接管。
- 不写:
new Client(['timeout' => 3])—— 这仍是同步客户端,timeout参数在协程中无效 - 要写:
new Client(['handler' => new CoroutineHandler(), 'timeout' => 3.0]) -
timeout值单位为秒,建议设为 2–5 秒;设为 0 或省略等同于无限等待,极其危险
connect_timeout 和 read_timeout 需单独控制(推荐)
仅设 timeout 是全局超时,无法区分连接阶段和读取阶段。实际网络异常常发生在 DNS 解析失败或服务端迟迟不发响应头,此时应拆分设置:
-
'connect_timeout' => 2.0:限制 TCP 握手 + DNS 解析总耗时 -
'read_timeout' => 3.0:限制从建立连接后到收到完整响应体的最大时间 - 两者可同时存在,
timeout作为兜底(例如设为6.0),避免某一项漏配导致 hang 住
禁止在构造函数或非协程上下文中发起请求
常见错误是在 Service 的 __construct() 里直接调用 $this->client->get(),此时协程尚未启动,会报 Co::getuid() must be called in coroutine。
- 所有 HTTP 调用必须发生在协程内:控制器方法、@Async 注解方法、go() 匿名函数、或 defer 回调中
- 若需预加载 client 实例,只做初始化,不发请求;请求动作延迟到真正需要时
- 可在
config/autoload/dependencies.php中绑定 client 实例,但确保其构造不触发网络调用
超时后务必释放资源并捕获异常
Guzzle 在协程超时后会抛出 GuzzleHttp\Exception\ConnectException 或 GuzzleHttp\Exception\RequestException,但底层连接可能未及时关闭,尤其在 DNS 失败等场景下容易残留 WAITING 状态协程。
- 始终用 try-catch 包裹请求,并在 catch 块中记录日志、返回降级数据或抛出自定义业务异常
- 避免“吞掉异常”后继续执行后续逻辑,造成状态不一致
- 如需重试,使用
RetryMiddleware并限定次数(建议 ≤ 2),防止雪崩











