一个Laravel队列引发的报警

赞助

如果你觉得我写得还行,并且愿意付费,那么我会更有动力写下去。

一台服务器报警了,内存占用过高,奇怪的是集群里其它的服务器都没问题。不过从以往的经验来看:每一个匪夷所思的问题背后,都隐藏着一个啼笑皆非的答案。

首先通过「free -m」确认一下内存情况,发现用掉了 6893M,还剩 976M:

free

free

然后通过「top」查看一下哪些进程占用内存多,通过「shift + m」按内存排序:

top

top

虽然通过 free 命令我们能确认系统可用内存不足,但是通过 top 命令我们却没有发现有内存占用大户的进程,那么内存到底都去哪里了呢?

开头我们提到过,集群里只有一台服务器有问题,其它服务器皆正常,于是我们比较了一下问题服务器和正常服务器的进程列表,结果发现问题服务器多了几个进程:

/usr/local/bin/php artisan queue:listen
/usr/local/bin/php artisan queue:work

经过确认,它们是 Laravel 队列,虽然直觉告诉我问题与其有关联,但是进程本身并没有占用多少内存,在不能立刻确诊原因的情况下,我们用排除法把队列换到另外一台正常的服务器上看看会不会重现问题,过了一会,果然再次出现同样问题。

既然 free,top 之类的命令不能确认内存的去向,那么我们不妨看看「meminfo」:

meminfo

meminfo

如上图所示,大量内存被 Slab 消耗了,更进一步讲是被 SReclaimable 消耗了,也就是说内存被一些可回收的 Slab 消耗了,详细信息可以通过「slabtop」获取:

slabtop

slabtop

基本都被 dentry 消耗了,如果你也跟我一样,搞不清楚它意味着什么,搜索吧,能翻墙用 Google,不能翻墙用 AOL,反正别用百度,我找到如下介绍:

  1. Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决
  2. Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决(续)

简而言之,内存 dentry 里缓存了最近访问过的文件信息,如果频繁的操作大量文件,那么 dentry 就会不断的增加,于是问题就变为确认 Laravel 队列有没有类似问题。

前面提到过,Laravel 队列有一个 listen 进程,还有一个 work 进程,从名字我们就能判断出来,前者是主进程,后者是子进程,子进程是干活的进程,可是当我直接 strace 跟踪子进程的时候,却提示我子进程不存在,进一步调试发现,原来子进程会不断重启!

既然我们不好直接跟踪子进程,那么不妨从父进程入手跟踪子进程的文件操作:

shell> strace -f -e trace=open,close,stat,unlink -p $(
    ps aux | grep "[q]ueue:listen" | awk '{print $2}'
)

可惜 Laravel 本身号称是巨匠框架,依赖一坨一坨的文件,所以跟踪结果里充斥着大量框架文件本身正常的 open,close,stat 操作,改为只跟踪 unlik 操作试试:

shell> strace -f -e trace=unlink -p $(
    ps aux | grep "[q]ueue:listen" | awk '{print $2}'
)

发现 Laravel 队列频繁的执行删除文件操作,每重启一次子进程就执行一次删除:

unlink(“/tmp/.ZendSem.aXaa3Z”)
unlink(“/tmp/.ZendSem.teQG0Y”)
unlink(“/tmp/.ZendSem.Bn3ien”)
unlink(“/tmp/.ZendSem.V4s8RX”)
unlink(“/tmp/.ZendSem.PnNuTN”)

于是乎消耗了大量的 dentry 缓存。查阅 Laravel 队列的文档,发现 Laravel 队列实际上也提供了不重启的进程模式,这样就不会频繁创建大量临时文件,进而也就不会消耗大量的 dentry 缓存,推荐使用。

如果频繁创建大量临时文件的情况无法避免,那么按照 Linux 文档的描述,我们可以通过设置 drop_caches 为 2 来删除可回收的 slab(包括 dentries 和 inodes),较粗野:

shell> echo 2 > /proc/sys/vm/drop_caches

此外还可以通过设置 vfs_cache_pressure 大于 100 来增加回收概率,较温柔:

shell> echo 10000 > /proc/sys/vm/vfs_cache_pressure

从测试结果看,vfs_cache_pressure 的作用有限,当然也可能是我姿势不对。补充:根据 Reducing dentry (slab) usage on machines with a lot of RAM 一文的说明,如果使用 vfs_cache_pressure 来控制 detry slab,那么需要有点耐心。

此外还有 min_free_kbytes,不过鉴于其有一定危险性,建议大家小心点。

实际上,通过设置 extra_free_kbytes 来确保系统最小可用内存的方式更方便,一旦可用内存降低到设定的阈值,kswapd 进程就会被唤醒,主动回收内存,篇幅所限具体介绍就不多说了,有兴趣的读者可以参考相关文章中的介绍。

一个Laravel队列引发的报警》上有6条评论

  1. 当时看到queue:listen和queue:work两个指令的时候就决定用queue:work了,queue:listen很慢,每次都重启。还好没掉这个坑。

  2. Pingback引用通告: Laravel队列的一些细枝末节 | 火丁笔记

  3. Pingback引用通告: 记一次内存使用率过高的报警 – Farll

  4. strace -f -e trace=open,stat,close,unlink -p $(ps aux | grep php |awk ‘{print $2}’) 怎么都无法执行,后来发现 -p 后面的pid需要用都好分隔,比如-p 635,2387,1133 但是上面的例子得到PID是空格分隔,大神要如何处理,求赐教

    • 我的目标进程就一个,所以可以这样写。如果你想让多个进程 pid 用逗号分隔,我测试了一下,可以这样:ps aux | grep [p]hp | awk -v ORS=”” ‘{if (NR>1) print “,”; print $2}’,不过我印象中 strace 多个 pid 的时候,语法是 -p pid -p pid 这样分开写的。

  5. Pingback引用通告: linux内存占用过多排查思路 – 阿里当当的博客

发表评论

电子邮件地址不会被公开。 必填项已用*标注