先甩一行日志,那天凌晨的:
Sep 9 03:14:27 web04 kernel: Out of memory: Killed process 20517 (php-fpm) total-vm:4194304kB, anon-rss:3190120kB, file-rss:0kB两分钟之后,客户电话进来,说网站白屏。我摸黑爬起来,点开七亿批量远程管理工具——这批机器平时都挂在里面,SSH 终端连上 web04:nginx 活着,php-fpm 7.4 的进程池没了。systemctl restart php-fpm,页面回来了。当时觉得是偶发,关电脑接着睡。
第二天凌晨三点零七,又响。
这回没法当偶发处理了。白天先看内存:
$ free -h
total used free shared buff/cache available
Mem: 31G 27G 412M 380M 3.6G 2.9G
Swap: 0B 0B 0Bused 27 个 G,free 剩 400 多兆。available 还有 2.9G,看着好像扛得住,可这个数在内存分配紧张的时候不能全信。swap 干脆是 0——装系统的人当初给关了,说是云主机磁盘快,用不着。
第一反应是 MySQL。这台机器上 MySQL 独占 innodb_buffer_pool_size=12G,我先问客户最近有没有人动过配置,没有;自己又把 buffer pool 从 12G 压到 8G,重启。第三天凌晨,该响还是响。
弯路得说。中间我一度怀疑是被塞了挖矿,top 拉到 CPU 那栏盯了半天,us 才个位数,安静得很,不是。也数过 nginx 的 worker 数,正常。
真正露头是白天趁不忙,把内存占用排了个序:
$ ps -eo pid,rss,cmd --sort=-rss | head -5
20517 987312 php-fpm: pool www
20533 961044 php-fpm: pool www
20601 903772 php-fpm: pool www几个 php-fpm worker 的 RSS 都接近一个 G,正常一个 worker 也就二三十兆。看 pid,全在跑同一个东西——客户那个报表导出。点一下"导出全部订单",代码是 $rows = $db->getAll($sql),十几万行一次性读进数组,再扔给 PHPExcel 排版,一个请求的内存开销直接到 1.5 个 G 上下。
那 memory_limit 呢,设的 512M,怎么没拦住?这个坎我卡了好一会儿才想通:导出走的是队列,由一台常驻的 CLI worker 消费,CLI 下 memory_limit 是 -1,根本不设限。web 那侧限了,真干活的 CLI 这侧没人管。以前我一直以为 memory_limit 是个全局的东西。
池子配置也有份。pm=dynamic,max_children 按默认公式算出来五十几个,五十几乘以接近 1G,这台 31G 的机器几个大请求并发就顶穿了。OOM killer 挑 score 最高的那个杀,第一个倒的就是吃内存最狠的 php-fpm。
处理分三块。导出的 SQL 改成按 id 分批,一次两千行,fputcsv 直接往输出流写,不再整个塞进数组;CLI worker 补上 memory_limit,pm.max_requests 设成 500,worker 活够 500 个请求退掉重开,内存涨不上去;再给这台机器补 4G swap,vm.swappiness 压到 10。上线盯了两周,内存峰值从 27G 掉到 14G 上下,凌晨那通电话没再来。
客户后来问我一句:不是上个月刚给你们加过内存吗。我说加了内存没错,但桶底那个洞得先堵,不然加多少都是往里灌。这话本来是想拿来说他们的——差一点脱口而出"是你们自己的导出程序写的",想了想没说。代维这边平时巡检只看磁盘和 CPU,内存没进监控,这个口子是我们一起漏掉的,账不能全算人家头上。
这台机器现在也加了内存告警,阈值是 used 超过 85%,跟磁盘那个一个待遇。说实话我之前真没料到,一台"内存够用"的机器能死得这么干脆。
那阵子我天天开七亿批量远程管理工具一台台连过去看 free 和 dmesg,几台机在一个窗口里切着看,比来回换终端省事。工具免费,说明在 七亿批量远程管理工具 这页。
上一回也差不多是半夜机器出事——那晚手机响了三十七次,不过那是告警刷屏,不是 OOM。半夜这两个字,现在听着就烦。
评论