昨天晚上服務(wù)器開始報(bào)警一個(gè)內(nèi)存泄漏的錯(cuò)誤,直接導(dǎo)致頁(yè)面出現(xiàn)500錯(cuò)誤侄非,所幸出現(xiàn)頁(yè)面的地址只有一個(gè)片拍,且此頁(yè)面有做ats健康緩存,所以用戶訪問的始終是正常頁(yè)面
錯(cuò)誤描述:PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 976370274 bytes) in mc.php on line 31
出錯(cuò)的頁(yè)面最近沒有動(dòng)到昔榴,由于是PHP Fatal error:級(jí)別辛藻,無法通過debug_backtrace拿到堆棧信息,給排查問題造成很大困擾互订。
排查問題:通過在測(cè)試環(huán)境復(fù)現(xiàn)問題吱肌,一行行調(diào)試,打印輸出仰禽,最終定位到小編在錄入一個(gè)id值時(shí)多復(fù)制了一個(gè)tab符號(hào)氮墨,代碼中信任了此值
當(dāng)它為int拼接為mc的key進(jìn)行緩存獲取到的數(shù)據(jù),被緩存數(shù)據(jù)是一個(gè)數(shù)組吐葵,在set成功后get被緩存數(shù)據(jù)時(shí)內(nèi)存溢出规揪。
代碼場(chǎng)景還原:
<?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;
# 注意被設(shè)置的值一定是array才能復(fù)現(xiàn)內(nèi)存溢出
$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());
運(yùn)行程序
#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
為了找出底層問題和新版本修復(fù)情況找了一個(gè)php7.x的版本測(cè)試
#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"
相關(guān)軟件版本
php 7.0.7
libmemcached 1.0.18
memcached 3.0.3
由于是獲取mc時(shí)出現(xiàn)的內(nèi)存溢出,且從上面看由于新版本對(duì)mc的key做了增強(qiáng)的檢查而避免的温峭,所以
先下載兩個(gè)memcached版本源碼猛铅,進(jìn)行查看
// memcached 2.2.0 可以看到對(duì)key的檢查只限于空格和長(zhǎng)度,tab是被認(rèn)為合法的
if (key_len == 0 || strchr(key, ' ')) {
i_obj->rescode = MEMCACHED_BAD_KEY_PROVIDED;
RETURN_FROM_GET;
}
// memcached 3.0.3 這里的key驗(yàn)證相對(duì)來說復(fù)雜了許多
#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
小結(jié):
memcached 2.2.0 是php5使用的最高版本了诚镰,memcached 3.x是給php7 使用的奕坟,如果要在php5項(xiàng)目中避免內(nèi)存溢出只能在php代碼層面對(duì)mckey進(jìn)行檢查或是提高代碼書寫質(zhì)量,避免傳遞給擴(kuò)展一個(gè)非法的key清笨,對(duì)于有條件的用戶也可以升級(jí)到php7