根本原因是crontab-dispatcher进程不参与Swoole生命周期、持续new实例且不回收,导致内存缓慢泄漏;应改用beforeStart中启动的协程定时器,并调低oom_score_adj、禁用PDO缓冲查询、加强RSS内存监控。

Hyperf 定时任务进程被 OOM 杀死,根本原因不是“任务写错了”,而是 crontab-dispatcher 进程长期驻留、不参与 Swoole 生命周期、持续 new 实例且不回收,导致内存缓慢累积泄漏。几天后 RSS 内存突破限制(如 >1.5GB),内核触发 OOM Killer 强制 kill 掉占用内存最高的 php 进程(常为 Worker0 或 dispatcher)。防护需从规避泄漏源头 + 降低被杀优先级 + 监控兜底三方面入手。
改用协程定时器替代 @Crontab
这是最根本的解决方式。@Crontab 注解会 fork 出独立的 crontab-dispatcher 进程,它每秒轮询、反复实例化任务类,对象无法被 GC 回收,内存只增不减。
- 在
beforeStart生命周期中启动协程定时器:Co::delay(1000, function () { while (true) { // 执行逻辑 Co::sleep(1); } }); - 确保所有定时逻辑在单个协程内完成,避免闭包 use 捕获 $container、DB 连接等大对象
- 若需多任务,用
Co::create()分离协程,但每个协程内必须主动管理资源(如连接池归还、大数组 unset)
调低 OOM 被杀优先级
即使内存增长,也可让系统优先杀其他进程,保住 Hyperf 主服务。关键参数是 oom_score_adj,范围 -1000 ~ +1000,-1000 表示永不被杀。
- 查当前主进程 PID(如
ps aux | grep "php start.php"),再查其 oom 分值:cat /proc/1234/oom_score_adj - 临时保护(重启失效):
echo -500 | sudo tee /proc/1234/oom_score_adj - 永久保护(推荐):在 systemd 服务文件中添加:
[Service]<br>OOMScoreAdjust=-500
然后执行sudo systemctl daemon-reload && sudo systemctl restart hyperf
关闭缓冲查询 + 避免连表全量加载
定时任务常做数据清理、统计、同步,一旦涉及关联查询(如订单+明细),PDO 默认开启缓冲查询,整张结果集(几十万行)直接载入内存,瞬间引爆 OOM。
- 数据库配置中强制禁用缓冲:
'options' => [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false] - 禁用 ORM 的
get()和all(),改用游标遍历:$statement = $this->db->query("SELECT ...");<br>while ($row = $statement->fetch()) { /* 处理单行 */ } - 对大表操作加
LIMIT分批,配合 WHERE 条件推进(如id > ? ORDER BY id LIMIT 1000)
监控与快速定位
光靠防护不够,得提前发现异常趋势。重点盯三个指标:
- Worker 进程 RSS 内存(非 VIRT):持续 >800MB 就该预警
- crontab-dispatcher 进程是否存在、RSS 是否随时间单调上升(用
ps -eo pid,comm,rss --sort=-rss | head -10) - 宿主机可用内存是否在凌晨时段持续跌破 10%(OOM 高发时段)
- 配合
sudo dmesg -T | grep -i "killed process"确认是否真被 OOM Killer 干掉


















