hyperf 中直接 new guzzlehttp\client 或用原生 meilisearch-php sdk 会阻塞协程,因其底层依赖同步 curl;必须通过 clientfactory 创建客户端并确保启用 swoole 协程运行时才能实现真正的协程 http 请求。

Hyperf 中直接 new GuzzleHttp\Client 或用原生 meilisearch-php SDK 发请求,一定会阻塞协程——这不是配置没开,而是底层根本没走协程 I/O 路径。
为什么 Guzzle 默认会阻塞协程
Guzzle 默认使用 cURL 作为传输处理器(curl handler),而 PHP 的 cURL 扩展是同步阻塞的。哪怕你运行在 Swoole 协程环境里,curl_exec() 一调用,当前协程就卡住,直到响应返回或超时,期间无法调度其他协程。
- 现象:高并发下响应时间陡增、协程 ID(
Co::getcid())不变、strace显示大量read/write阻塞调用 - 根源:Guzzle 没启用 Swoole 的协程 HTTP 处理器,也没被 Hyperf 的
hyperf/guzzle组件接管 - 误区:“装了
hyperf/guzzle就自动协程化”——错。必须通过ClientFactory创建实例,不能手动 new
正确开启协程处理器的三步操作
Hyperf 的 hyperf/guzzle 并不是“给 Guzzle 打补丁”,而是用 Swoole 协程 HTTP 客户端(Swoole\Coroutine\Http\Client)完全替代了 cURL,再套一层 Guzzle 接口兼容层。要生效,必须满足:
- 安装组件:
composer require hyperf/guzzle - 确保
Swoole\Runtime::enableCoroutine(true)已在bin/hyperf.php顶部执行(Hyperf 3.x 默认已做,但建议显式保留) - 客户端必须由
Hyperf\Guzzle\ClientFactory创建,且不能传入handler参数覆盖默认协程处理器
错误示例(仍会阻塞):$client = new GuzzleHttp\Client(['handler' => HandlerStack::create()]);
正确写法(自动协程):$client = $this->clientFactory->create(['timeout' => 5.0]);
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
常见超时与重试配置陷阱
协程环境下,超时行为和传统 FPM 完全不同。只设 timeout 不够,连接建立、DNS 解析、TLS 握手都可能单独卡死。
-
connect_timeout控制 TCP 连接建立耗时,建议 ≤ 3s;不设则依赖 Swoole 默认(通常 10s),太长 -
swoole数组里可细化控制:'swoole' => ['connect_timeout' => 2, 'timeout' => 4],优先级高于 Guzzle 层 - 重试必须用中间件,比如
GuzzleRetryMiddleware,不能靠 catch + for 循环——后者会在重试时反复阻塞协程 - 429(限流)、5xx 响应需自定义
$decider函数,否则默认不重试
Meilisearch 等第三方 SDK 怎么办
像 meilisearch-php 这类 SDK,内部硬编码依赖 GuzzleHttp\Client,且未开放 handler 注入点,直接 composer require 后调用,必然阻塞。
- 方案一(推荐):弃用官方 SDK,用
hyperf/guzzle+ 手动封装 REST 请求(POST /indexes/:uid/search等路径清晰,JSON body 简单) - 方案二:改写 SDK 的 Client 构造逻辑,强制注入
ClientFactory创建的实例(需 fork 后 patchMeiliSearch\Client::__construct) - 绝对避免:在协程中
new MeiliSearch\Client()或复用同一实例跨协程调用——连接复用冲突会导致 401/timeout 乱序
真正麻烦的从来不是“怎么配”,而是当一个 SDK 声称“支持 Hyperf”却没暴露 handler 入口时,你得判断它是否真的协程安全——看源码里有没有 new Client、有没有 curl_ 函数调用,比读文档快得多。










