记一次内存泄漏排查过

1. 问题由来

        使用php脚本查询mysql数据库时,脚本占用内存持续增长。发现这个问题后,解决办法是memory_limit设置为一个较大的值,也凑合用了一年多的时间。
        以前在ODP hi群询问过ODP内存泄漏的问题,有部分同学反映也遇到过,解决办法是定时重启脚本或通过调子脚本来避免这个问题。
        近期有一次数据导入,虽然数据比较少,但是查询次数比较多,内存泄漏了2G+,因此,借这个机会彻底排查这个问题。
       如果是Php脚本或php扩展的内存泄漏,可以用get_memroy_usage()判断出来,如果是php VM的原因,则这个函数就无能为力了。由于get_memory_usage()的返回值一直递增,可以排除php VM本身的原因。
       脚本用的是 Bd_DB::query()方法查询的,在测试脚本里,将这段代码注释掉后,内存泄漏现象消失了,因此从query()方法开始排查。
       由于php 5.2有循环引用的问题,这会导致GC不释放循环引用的内存,5.3及高版本的php中,这个问题已经解决(http://php.net/manual/zh/features.gc.collecting-cycles.php)。在使用php 5.4执行脚本后,内存泄漏现象依然存在,故排除php版本的原因。
       Bd_DB的实现是对mysqli 做了一层封装,下层是通过调用mysqli来执行查询的。Php中同样执行数据库查询的,还是mysql和PDO模块。那么,测试一下直接使用mysqli做查询,看情况如何。测试结果是,直接使用原生mysqli查询,没有内存泄漏,这样也排除了mysqli的原因。
       接下来的工作,就是深入Bd_DB的query()函数,查出问题代码。通过二分法,定位到Bd_DbRALL::writelog()方法,最终确认问题出在ral_write_log()函数上。
       Bd_DB将查询过程中的日志记录在RAL模块的日志文件中,它是通过RAL模块提供的函数来实现的(感觉很没有必要仅仅为了写日志去调用ral扩展)。注释掉写日志这行函数后,内存泄漏现象没有了。
       那么怎么排查RAL扩展的问题呢? 如果能直接排查扩展的问题,就不用上面手动定位问题代码了。下一节,就来看看如何排查php扩展的内存泄漏。
2. 环境及程序

       Valgrind是一个排查内存泄漏的工具,它主要原理是通过拦截内存管理函数,在程序退出时检查哪里有内存分配但是却没有内存释放来定位问题代码。针对一些提供了自定义内存管理函数的程序,valgrind无能为力。
       对PHP扩展来说,PHP本身提供了emalloc和efree管理内存。但是如果将环境变量 USE_ZEND_ALLOC设置为0,php扩展不会使用内置的内存管理函数,valgrind就派上用场了。
       此时,排查内存泄漏很简单:
              a) 使用php及扩展的debug版本.
              b) 设置环境变量:
              export USE_ZEND_ALLOC=0 
              export ZEND_DONT_UNLOAD_MODULES=1,它是用来定位php扩展源程序的.
              c) 执行测试命令:
              valgrind --leak-check=full --track-origins=yes –log-file=mleak.log php/bin/php ./test_ral_write_log.php
==8682== 1,490 bytes in 4 blocks are definitely lost in loss record 126 of 160
==8682== at 0x4908512: realloc (vg_replace_malloc.c:662)
==8682== by 0xAB7FE0E: zif_ral_write_log (php_ral.cpp:1016)
==8682== by 0x8A63A7: zend_do_fcall_common_helper_SPEC (zend_vm_execute.h:643)
==8682== by 0x8A5701: execute (zend_vm_execute.h:410)
==8682== by 0x87ABB4: zend_execute_scripts (zend.c:1329)
==8682== by 0x80D657: php_execute_script (main.c:2502)
==8682== by 0x916879: do_cli (php_cli.c:989)
==8682== by 0x917B55: main (php_cli.c:1365)
       RAL扩展在scm上是开源的,拿到程序看了一下,问题出在一个 smart_str buf 变量上,在函数既退出之前,并没有释放。
       又在odp群里问这个问题,ral维护同学回复最近的版本( 2015-11-19, 2.0.13.1)已经修复这个问题了(http://man.baidu.com/inf/odp/ral2/#升级记录)。
       整个问题排查的过程比较辛苦,最终找到了一种方法,简洁有效。Ral_write_log这个bug存在了很长时间,解决办法是及时最新的ral.so。
       http://php.net/manual/zh/features.gc.collecting-cycles.php
       http://www.ibm.com/developerworks/cn/linux/l-cn-valgrind/

      


你可能感兴趣的:(操作系统)