Hyperf协程服务必须全链路协程化才能真正生效:需确认enable_coroutine为true、Swoole≥4.8.0、所有I/O操作替换为协程等价实现(如HttpClient、hyperf/database、Co::sleep等),并验证Co::getcid()和Co::stats()证明协程真实运行。

Hyperf 协程风格服务必须显式启用协程运行时,并确保所有 I/O 操作在协程上下文中执行;否则看似“协程化”的代码仍会阻塞主线程。
确认 enable_coroutine 已开启且生效
这是协程化的前提,但很多人只改了配置却没验证是否真正生效。Hyperf 默认在 config/autoload/server.php 的 settings 中设置该选项,但若被其他配置覆盖或 Swoole 版本不兼容,它可能无效。
- 检查
settings.enable_coroutine是否为true(不是字符串"true") - 确保 Swoole 版本 ≥ 4.8.0(Hyperf 3.x 推荐 ≥ 5.0.3),旧版即使设为 true 也不支持全协程 API
- 运行
php bin/hyperf.php server:info查看输出中enable_coroutine对应值是否为1 - 避免在
onWorkerStart回调里手动调用Swoole\Runtime::enableCoroutine()—— Hyperf 已在启动阶段封装处理,重复调用可能引发不可预知行为
替换所有同步 I/O 为协程等价实现
协程风格 ≠ 加个 #[AsyncTask] 就完事。只要链路中存在一个同步调用(如 file_get_contents、curl_exec、原生 PDO 查询),整个协程就会退化为阻塞模式。
- HTTP 请求必须用
Hyperf\HttpClient\Client,禁用file_get_contents或cURL扩展直连 - 数据库操作必须走
hyperf/database组件,它底层使用Swoole\Coroutine\MySQL,而非 PDO 或 mysqli - 文件读写必须用
Swoole\Coroutine\System::readFile()/writeFile(),不能用fopen/file_put_contents - 延时操作必须用
Swoole\Coroutine::sleep(),而非sleep()或usleep() - Redis 必须通过
hyperf/redis连接池访问,直接 new Redis() 会阻塞
合理设置 max_coroutine 和 worker_num
这两个参数共同决定单 worker 进程内能并发多少协程任务。设得过大反而导致调度延迟升高、内存占用激增;过小则无法压满 CPU。
-
worker_num建议设为swoole_cpu_num() * 2(非 *4),高负载下过多 worker 会加剧进程间切换开销 -
max_coroutine推荐从10000起步,按压测结果逐步上调;超过50000需谨慎,协程栈内存消耗会线性增长 - 注意:协程数 × 单协程平均内存 ≈ worker 进程 RSS,若发现
memory_limit频繁触发或 OOM,优先降max_coroutine而非加memory_limit - 不要盲目复用 Laravel 的连接池配置逻辑——Hyperf 的协程连接池是 per-worker 独立的,
min_connections设太低会导致冷启动时协程排队等待连接
验证协程是否真正在跑
最常被忽略的一环:你以为协程在跑,其实只是“伪协程”。关键看两点:是否无锁并发、是否能观测到协程 ID 切换。
- 在请求中调用
Co::getcid(),对比多次请求或同一请求内不同异步步骤的返回值是否变化 - 用
Co::stats()查看coroutine_num是否随并发请求上升,coroutine_peak_num是否接近你设的max_coroutine - 日志中打印
getmypid()+Co::getcid()组合,确认同一进程 PID 下多个 CID 并发存在 - 若所有请求都共享同一个 CID,说明协程未启用或被同步调用中断了协程上下文
协程风格服务真正的难点不在配置开关,而在于整条调用链是否“纯净”——任何一处遗漏的同步调用,都会让前面所有协程优化归零。上线前务必用真实流量路径做端到端协程穿透验证,而不是只测单个接口。


















