昨天晚上服务器开始报警一个内存泄漏的错误,直接导致页面出现500错误,所幸出现页面的地址只有一个,且此页面有做ats健康缓存,所以用户访问的始终是正常页面
错误描述:PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 976370274 bytes) in mc.php on line 31
出错的页面最近没有动到,由于是PHP Fatal error:级别,无法通过debug_backtrace拿到堆栈信息,给排查问题造成很大困扰。
排查问题:通过在测试环境复现问题,一行行调试,打印输出,最终定位到小编在录入一个id值时多复制了一个tab符号,代码中信任了此值
当它为int拼接为mc的key进行缓存获取到的数据,被缓存数据是一个数组,在set成功后get被缓存数据时内存溢出。
代码场景还原:
<?php
$m = new Memcached();
$host = '127.0.0.1';
$port = 11211;
$m->addServer($host, $port);
echo "=connect mc $host:$port\n";
$mcKey = "\t188883";
$mcKey2 = "test_" . $mcKey;
# 注意被设置的值一定是array才能复现内存溢出
$ntest = array (
'id' => '188883',
);
echo "=set mc\n";
var_dump($m->set($mcKey2, $ntest, 60));
echo "=set mc result\n";
var_dump($m->getResultMessage());
echo "=get mc\n";
var_dump($m->get($mcKey2));
echo "=get mc result\n";
var_dump($m->getResultMessage());
运行程序
#php -d "display_errors=On" test.php
=connect mc 127.0.0.1:11211
=set mc
bool(true)
=set mc result
string(7) "SUCCESS"
=get mc
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 976304738 bytes) in test.php on line 20
检查软件版本
php 5.4.25
libmemcached 1.0.18
memcached 2.2.0
为了找出底层问题和新版本修复情况找了一个php7.x的版本测试
#php -d "display_errors=On" test.php
=connect mc 127.0.0.1:11211
=set mc
bool(false)
=set mc result
string(46) "A BAD KEY WAS PROVIDED/CHARACTERS OUT OF RANGE"
=get mc
bool(false)
=get mc result
string(46) "A BAD KEY WAS PROVIDED/CHARACTERS OUT OF RANGE"
相关软件版本
php 7.0.7
libmemcached 1.0.18
memcached 3.0.3
由于是获取mc时出现的内存溢出,且从上面看由于新版本对mc的key做了增强的检查而避免的,所以
先下载两个memcached版本源码,进行查看
// memcached 2.2.0 可以看到对key的检查只限于空格和长度,tab是被认为合法的
if (key_len == 0 || strchr(key, ' ')) {
i_obj->rescode = MEMCACHED_BAD_KEY_PROVIDED;
RETURN_FROM_GET;
}
// memcached 3.0.3 这里的key验证相对来说复杂了许多
#define MEMC_CHECK_KEY(intern, key) \
if (UNEXPECTED(ZSTR_LEN(key) == 0 || \
ZSTR_LEN(key) > MEMC_OBJECT_KEY_MAX_LENGTH || \
(memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) \
? !s_memc_valid_key_binary(ZSTR_VAL(key)) \
: !s_memc_valid_key_ascii(ZSTR_VAL(key)) \
))) { \
intern->rescode = MEMCACHED_BAD_KEY_PROVIDED; \
RETURN_FALSE; \
}
#ifdef HAVE_MEMCACHED_PROTOCOL
小结:
memcached 2.2.0 是php5使用的最高版本了,memcached 3.x是给php7 使用的,如果要在php5项目中避免内存溢出只能在php代码层面对mckey进行检查或是提高代码书写质量,避免传递给扩展一个非法的key,对于有条件的用户也可以升级到php7