废话不多说,总共分三步
1、初步定为泄漏:
- 迫于 996ICU 的压力,广大的 PHPer 一般不会关注泄漏问题,都是在看到报错
才发现泄漏问题,此时我们一般会通过查看进程的RSS
占用来确定内存占用,例如这样
但一定要注意的是,此处查看的RSS
是包含共享内存的(共享的内存会重复计算多次),并不是进程真正占用的内存,USS
才是我们 PHP 代码申请的内存,我们更应该关注的是USS
指标。(感兴趣的小伙伴可以看这个视频)。
- 怎么看
USS
,这里推荐smem
这个命令,用法和结果如下:
RSS 占用特别多,USS 特别少,证明大部分 RSS 都是共享内存占用,此时大概率是你的Swoole Table
或者Apcu
用的有问题,因为这两个底层是基于共享内存的。
2、定位泄漏的代码:
可以用Swoole Tracker
提供的工具来定位,具体参考这篇文章
3、清理内存碎片:
如果 Tracker 发现不了泄漏,内存还一直涨,八成是遇到了 PHP 的内存碎片问题,内存碎片问题也是我想写这篇文章的原因,社区里面有个小伙伴用了Swoole Tracker没有发现泄漏,但是通过 smem 命令查看内存确实在涨,即使unset
了所有变量,内存仍然无法降下去,代码如下:
咋回事呢?根本原因是产生了内存碎片,和 PHP 的内存分配算法有关,这里不展开讲,大概原理是:小于 3072 字节的内存申请 PHP 会认为是小内存,PHP 会把所有申请的小内存块缓存起来,即使释放了也不归还给操作系统,以保证内存管理的效率
在 FPM 下请求结束后会释放所有内存,大部分归还给系统,只保留一小部分,但是在 Cli 下没有这样的机制,我们该怎么办?
我研究了一天,貌似只能给 php-src 提 pr 了,写了一天发现”咦?”怎么有段代码怎么和我的思路这么相似,仔细一看”日哦”,在 php 高版本提供了一个gc_mem_caches()
函数(网上没有任何文章介绍这个函数),可以手动清理这种小内存的碎片问题 - -!
我们可以设置一个 Swoole 的定时器,定期调用gc_mem_caches()
即可。
注意gc_mem_caches()
能极大的缓解PHP下面的内存碎片导致的内存增长问题,但是由于zend mm的设计机制问题,长时间运行还是会缓慢增长,这里我们也可以通过替换 PHP 的内存管理模块(比如采用 jemalloc)来彻底避免这种问题,具体方法是自己编译一个jemalloc的so然后启动PHP的时候这样export USE_ZEND_ALLOC=0 && LD_PRELOAD='/usr/local/lib/libjemalloc.so' php /home/guoxinhua/swoole_server.php
即可。
总结
第一步正确的发现泄漏,第二步使用 tracker 定位泄漏代码并 fix 它,第三步如果还是泄漏,尝试调用gc_mem_caches()
清理碎片。
希望此文章能帮到你^_^