PHP常驻进程内存泄漏排查
最近写了某个网站的爬虫(具体爬取什么内容不是此篇的重点哈。。),使用的是PHP的 hyperf 框架。进程运行起来发现内存一直在涨,如图:
如图,我使用 hyperf 的 process 模块,开启了四个常驻进程,用来持续地遍历爬取页面。但运行起来以后发现 process 进程内存持续增长,这台 2C4G 的机器大约持续爬取 半个小时,整台机器就直接 ssh 不上了,查看阿里云后台内存占用和 CPU 均占用 100%,top 负载高达 60+.
解决思路:
1.首先详细过了一遍逻辑部分,发现没有持续追加赋值的变量,那这部分累积的内存是什么地方占用了呢?
2.尝试使用 top 观看占用,process 进程的 cpu 占用并不高,变化在 8-50% 之间,主要是网络请求和解析 json 部分。
3.尝试使用 swoole 官方提供的调试工具 swoole_tracker,看能不能找到疑似内存的代码,无果。
4.strace -p [Process进程ID] ,截图如下:
基本都是 socket 请求收发,看不出什么异常。
5.此时心里大致有个数了,一定是代码里有哪块的静态变量在持续赋值申请内存。由于我使用的是 GuzzleHttp
的协程网络库,尝试在 $httpClient = new Client()
使用完之后主动释放内存 unset($client)
。发现并不解决问题。
6.忙活一上午,突然想到最近用了一个 php 的 html DOM 解析包 phpQuery
(https://github.com/phpquery/phpquery),这个库开发时针对的是 php5.3+ 版本,已经两年没维护了。这个包在传统 php-fpm 模式下是没啥问题的,但在 swoole 常驻进程下就不行了,里面创建的解析对象用到了一些静态变量:
由于我采用的某种循环遍历的方式不断扩散爬取的页面入口 url,这个 process 进程会持续请求新的页面,对于静态变量,只要我的进程没主动退出,将永远不会被销毁。基本就是这里的内存在占用了。
7.验证一下:
我们可以在代码处打上log,使用内存统计 memory_get_usage()
,在 new 这个静态对象的前后几处埋上 log。
发现前后对比,内存稳定增加的。
8.解决一下:
静态变量在 php 的 GC 下一般不会被主动回收,我这里在使用完之后直接 unset()
。重启服务,发现进程内存不再增长了。运行半天没变化,问题解决。