必须改异步的i/o操作包括sleep、file_get_contents、curl_exec、mysqli::query、redis::get等底层阻塞调用;若未启用协程hook或未使用协程驱动(如pdo而非hyperf\dbconnection、原生redis类而非hyperf\redis\redis),仍会阻塞worker进程。

Hyperf 里哪些 I/O 操作必须改异步?
不是所有代码都适合协程化,关键看是否涉及 sleep、file_get_contents、curl_exec、mysqli::query、Redis::get 这类底层系统调用。这些操作在传统 PHP-FPM 下会直接阻塞整个进程;Hyperf 中若未启用协程 Hook 或用了非协程驱动,照样卡住整个 Worker。
常见误判点:
- 以为“用了 Hyperf 就自动异步”——错。Hyperf 默认不开启协程 Hook,
file_get_contents仍阻塞 - 数据库用了 PDO,但没换
Hyperf\DbConnection驱动——PDO 是同步阻塞的,execute()会等结果返回才继续 - Redis 客户端用原生
Redis类而非Hyperf\Redis\Redis——前者调用的是阻塞 socket,后者底层走 Swoole 协程 socket
怎么确认协程已真正生效?
光写 go(function () { ... }) 不等于异步执行成功。必须验证协程是否真的在 I/O 期间让出了控制权。最直接的办法是加日志 + 时间戳比对:
go(function () {
$start = microtime(true);
$data = Co::get('https://api.example.com');
var_dump('HTTP耗时: ' . (microtime(true) - $start));
});
<p>go(function () {
$start = microtime(true);
$db = di()->get(\Hyperf\DbConnection\Db::class);
$db->select('SELECT SLEEP(1)');
var_dump('DB耗时: ' . (microtime(true) - $start));
});
</p>
如果两个协程总耗时接近 1 秒(而非 2 秒),说明协程调度生效;若接近 2 秒,大概率是某个环节没走协程路径——检查是否漏了 Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL),或用了未被 Hook 的扩展(如某些 C 扩展的 curl 封装)。
异步任务提交后不执行?先查协程池和异常捕获
Hyperf 异步任务(#[AsyncTask])默认由 CoroutinePool 执行,但池子大小、异常处理、重试逻辑全靠手动配。任务堆积或静默失败,90% 出在这三处:
-
config/autoload/async_task.php中'max_coroutine_num'设太小(默认 10),高并发下任务排队;建议按 CPU 核数 × 2~4 设置 - 任务类
execute()方法里没 try/catch,一旦抛出异常(比如 Redis 连接超时),协程直接退出且无日志;必须显式捕获并记录Hyperf\ExceptionHandler\ExceptionHandler - 没配置重试策略,网络抖动导致一次失败就丢弃任务;可在注解里加
#[AsyncTask(maxRetryTimes: 3)],或在execute()内部手动重试
为什么本地跑得通,线上一压就卡死?
线上环境常忽略两个硬性约束:Swoole 的 max_coroutine 和 Linux 的 ulimit -n。Hyperf 默认允许单 Worker 启动 3000 个协程,但如果系统文件描述符限制只有 1024,协程一多就卡在 socket 创建阶段,表现为“任务不执行”“HTTP 请求超时”“Redis 连接 refused”。
必须同步检查:
-
ulimit -n是否 ≥ 65535(建议设为 100000) -
php --ri swoole输出中max_coroutine值是否与业务峰值匹配(例如每秒 500 并发请求,每个请求开 5 个协程 → 至少需 2500) - Hyperf 配置中
server.settings.max_coroutine是否显式覆盖了默认值(不设则继承 Swoole 全局值)
协程数量不是越多越好,超出物理资源后反而因调度开销导致延迟飙升——这个临界点必须通过压测确定,不能只看文档推荐值。











