hyperf pod被oomkilled主因是limits.memory未设或过低,叠加swoole协程栈默认2mb、php memory_limit未限制及gc不及时;需设合理limits、调小coroutine.stack_size(如512kb)、强制memory_limit并周期性gc_collect_cycles。

Hyperf Pod被OOMKilled,先看limits.memory有没有设
没设 limits.memory 就等于没设内存红线——K8s 不会触发 OOM 控制,但节点内核会。一旦容器实际内存超过节点可用量,OOMKilled 就是秒杀,Exit Code 137,不打招呼。
常见错误配置:
-
requests.memory设了(比如512Mi),limits.memory没写 → K8s 调度能过,运行必翻车 -
limits.memory设得太低(比如1Gi),但 Hyperf 启动后光 Swoole worker + 协程栈 + 模型缓存就占掉 1.4Gi → 瞬间超限
实操建议:
- 用
kubectl describe pod <pod-name> -n <ns></ns></pod-name>确认limits.memory是否存在且合理 - 对 Hyperf 服务,
limits.memory至少要比kubectl top pod观测到的峰值高 30%~50%,留出 GC、元空间、协程栈增长余量 - 不要让
requests.memory和limits.memory差距过大(如 512Mi / 4Gi),否则调度器可能把多个“虚胖” Pod 打包塞进同一节点,引发连锁 OOM
Hyperf 的协程栈不是无限的,coroutine.stack_size 必须显式调小
默认 Swoole 协程栈是 2MB(coroutine.stack_size = 2097152),一个 PHP 进程开 100 个协程,光栈就吃掉 200MB。Hyperf 默认并发高、协程多,再叠加大模型推理或批量任务,内存直接爆表。
这不是“协程泄漏”,是“栈分配失控”。JVM 有 -Xss,Swoole 也得管住 coroutine.stack_size。
实操建议:
- 在
swoole.php或启动参数中显式设置:'coroutine' => ['stack_size' => 524288](即 512KB) - 若业务无深度递归或大局部变量,可进一步压到
262144(256KB);压太低(如 64KB)会导致Segmentation fault - 配合
swoole.max_coroutine一起调:栈小了,才能安全放开协程数上限,否则容易触发coroutine limit reached
memory_limit 和 gc_collect_cycles 在容器里必须重设
PHP 的 memory_limit 默认是 -1(不限制)或 128M,但在容器里它和 cgroup 内存限制是两套体系。PHP 层不限制,Swoole 就敢一直 malloc,直到撞上 limits.memory 被 OOMKilled。
更隐蔽的是 GC:Hyperf 高频创建/销毁协程对象,若 gc_collect_cycles() 不主动触发,引用计数迟迟不清理,内存只增不减。
实操建议:
- 在
php.ini或启动命令中强制设memory_limit = 512M(建议 ≤limits.memory的 60%) - 在 Hyperf 的
ServerProcess或全局中间件里加周期性 GC:if (gc_enabled() && gc_status()['runs'] % 10 === 0) { gc_collect_cycles(); } - 禁用
opcache.enable_cli=1(Hyperf 常驻进程不需要 CLI opcache,反而占内存)
别忽略 Swoole 的 max_request 和 reload_async
max_request = 0(永不 reload)看似省事,实则危险。长期运行下,PHP 内存碎片、Swoole 共享内存膨胀、未释放的资源句柄全堆在一个 worker 进程里,最终表现为内存缓慢爬升,直到某次请求触发 OOMKilled。
而 reload_async = true 能避免 reload 期间请求阻塞,但若没配好 reload_async_timeout,worker 可能卡在旧状态不退出,新旧进程共存导致内存翻倍。
实操建议:
- 设
max_request = 3000~5000(视单请求内存增长速率调整),配合max_request_grace_time = 30安全退出 -
reload_async = true必须搭配reload_async_timeout = 60,防止僵死进程滞留 - 用
swoole.server_stats暴露指标,监控worker_request_count和worker_memory_usage,提前发现老化 worker
真正容易被忽略的点:Hyperf 的 Coroutine::create() 是轻量,但它的生命周期和栈分配完全由 Swoole 控制;你写的代码没 leak,Swoole 的默认参数却在 quietly burn memory。调参不是玄学,是必须对着 kubectl top 和 cat /sys/fs/cgroup/memory/.../memory.usage_in_bytes 一起看的闭环动作。











