在 laravel 队列任务中实时抓取 cpu 和内存需在 handle() 内用 getrusage()(unix)和 memory_get_usage(true) 手动打点,计算差值获取任务级资源消耗,避免依赖全局或事件钩子。

怎么在 Laravel 队列任务里实时抓 CPU 和内存使用量
不能靠 phpinfo() 或全局配置,得在任务执行中用系统级函数快照式采样。PHP 本身不提供跨平台的精确资源读取,得组合 getrusage()(Unix)和 memory_get_usage(),并注意 Laravel 的进程模型——队列工作进程是长生命周期的,单次任务的资源消耗必须隔离测量。
-
getrusage()返回的是自进程启动以来的累计值,不是当前任务独占值,必须在任务开始前、结束后各调一次,再做差值 -
memory_get_usage(true)比memory_get_usage()更准,它返回的是向 OS 申请的内存块总量(含未释放的碎片),适合监控泄漏趋势 - Windows 下
getrusage()不可用,得降级用memory_get_usage()+ 粗略时间戳,或引入ps命令调用(不推荐生产) - 别在
handle()开头直接写getrusage()—— 中间件、事件监听器、Eloquent 加载可能已占用资源,应在业务逻辑真正开始前打点
Laravel 队列任务中记录资源快照的可靠写法
最简可行方案是封装一个可复用的资源计量器,在任务 handle() 内手动触发 start/stop。不要依赖队列事件(如 JobProcessing),因为事件本身也耗资源,且无法覆盖失败重试场景下的多次执行。
- 用
microtime(true)记录起止时间,比Carbon::now()轻量且无时区开销 - CPU 时间建议用
ru_utime.tv_sec + ru_utime.tv_usec / 1e6(用户态时间),排除系统调用干扰;ru_stime只在排查 I/O 密集型问题时才需关注 - 内存快照建议每任务至少记两次:
before(DB 查询前)、after(响应生成后),否则容易把 ORM 缓存膨胀误判为任务泄漏 - 示例片段:
public function handle()
{
$start = getrusage();
$memBefore = memory_get_usage(true);
<pre class="brush:php;toolbar:false;">// ... 你的业务逻辑
$end = getrusage();
$memAfter = memory_get_usage(true);
$cpuSec = ($end['ru_utime.tv_sec'] - $start['ru_utime.tv_sec'])
+ ($end['ru_utime.tv_usec'] - $start['ru_utime.tv_usec']) / 1e6;
Log::info('job-resource', [
'cpu_sec' => round($cpuSec, 3),
'mem_before_kb' => (int) round($memBefore / 1024),
'mem_after_kb' => (int) round($memAfter / 1024),
'mem_delta_kb' => (int) round(($memAfter - $memBefore) / 1024),
]);}
为什么日志里看到的 CPU 时间总是接近 0
常见于本地开发环境或轻量任务,本质是 PHP 进程粒度太粗,getrusage() 最小精度约 10ms,而简单任务执行远低于该阈值。这不是代码错,是测量工具与场景不匹配。
- 别用 CPU 时间判断“快慢”,优先看实际耗时(
microtime差值)和内存变化量 - 若真要压测 CPU 密集型逻辑,加个
for ($i = 0; $i 再测,否则数据无意义 - 在 Docker 容器中,
getrusage()仍有效,但 cgroup 限频可能导致 CPU 时间统计失真,此时应以容器级监控(如cAdvisor)为准,而非 PHP 层快照 - Homestead/Valet 等虚拟化环境可能因时钟虚拟化导致
microtime()漂移,生产环境务必用物理机或云主机验证
把资源数据写进数据库 or 推到 Prometheus
直接写 DB 容易拖慢队列吞吐,尤其高并发场景;全推 Prometheus 又丢失上下文。折中做法是:只对超阈值任务落库,其余走轻量指标通道。
- 设两个硬阈值:
mem_delta_kb > 5120(5MB 增量)或cpu_sec > 0.5,满足其一才写入job_resources表 - Prometheus 推荐用
pushgateway,但注意它不支持标签动态更新,所以 job 名、队列名、尝试次数这些维度得拼进指标名,如laravel_job_cpu_seconds_total{queue="default",name="App\Jobs\SendEmail",attempts="1"} - 别在队列任务里同步调用
file_put_contents()写监控文件 —— NFS 或低配磁盘会阻塞整个 worker 进程 - 如果用了 Horizon,它的
Horizon::recordMetrics()是黑盒,不暴露原始资源数据,仅作参考,不能替代自主采样
监控这事,最难的不是采样代码,是分清「进程级」「任务级」「请求级」三者的资源归属边界。一个任务里调了三次 API、加载了两个大集合、又触发了模型事件——这些资源到底算谁的,得靠你手动拆解打点位置,框架不会替你决定。











