PHP 疑難雜癥:解決守護進程時 Redis 假死

背景:公司業(yè)務(wù)有一個常駐后臺運行的守護進程秒赤。在這個守護進程當(dāng)中使用了 Redis List 結(jié)構(gòu)保存業(yè)務(wù)數(shù)據(jù)進行隊列消費剿涮。結(jié)果運行過程中链韭,有時候半個月陪白,有時候幾個月就會突然不再消費隊列里面的數(shù)據(jù)缎玫。當(dāng)時懷疑是 PHP 不適合編寫這種常駐后臺運行的守護程序硬纤。后來,我們發(fā)現(xiàn)進行心跳檢測之后碘梢,程序的穩(wěn)定性大大提高咬摇。至今沒有出現(xiàn)過假死。

一煞躬、一個簡單的守護進程示例

<?php
$redis = new \Redis();
$redis->connect('localhost', 6379);
$redis->auth('xxxxx'); // Redis 密碼如果沒有設(shè)置為空字符串肛鹏。
$redis->select(1);

$queueKey    = 'redis_queue_services_key';     // 業(yè)務(wù)數(shù)據(jù)隊列。
$queueIngKey = 'redis_queue_services_ing_key'; // 處理中的隊列恩沛。

try {
    while (true) {
        $element = $redis->bRPopLPush($queueKey, $queueIngKey, 60);
        if ($element) {
            $data = json_decode($element, true);
            /**
             *
             ...... 此處省略業(yè)務(wù)邏輯 ......
             *
             */
        } else {
            usleep(100000); // 睡眠 0.1 秒在扰。
        }
    }
} catch (\Exception $e) {
    exit("Error:{$e->getMessage()}");
}

這段代碼我們很容易看懂。
它就是通過 Redis 的阻塞方法 bRPopLPush 循環(huán)從 Redis 隊列中取出數(shù)據(jù)并處理雷客。如果沒有取到數(shù)據(jù)就休眠一秒芒珠。之所以休眠是為了保證 CPU 能得到充分的利用。因為搅裙,我們已經(jīng)使用了阻塞方法阻塞 60 秒皱卓。所以裹芝,這個位置休眠與否并不重要。

當(dāng)我們的業(yè)務(wù)出現(xiàn)任何錯誤娜汁,我們通過 try catch 進行異常捕獲然后將錯誤信息直接輸出并退當(dāng)前腳本嫂易。

博主寒冰第一次編寫常駐后臺運行的守護進程時,就是如上這種方式寫的代碼掐禁。結(jié)果怜械,這段代碼運行到 30s 的時候報錯了。提示我們 socket 流超時傅事。于是我在這個腳本頭部加了如下代碼:

ini_set('default_socket_timeout', -1);

這樣我們的 PHP 就不會主動段掉我們與 Redis 的 socket 連接了缕允。

但是,好景不長蹭越。過了一段時間障本,大概半個月吧。運維同學(xué)告訴我 Redis 隊列的數(shù)據(jù)出現(xiàn)了未消費的情況响鹃。然后彼绷,我查看了消費日志。的確沒有產(chǎn)生新的消費日志茴迁。因為我有一個習(xí)慣寄悯,每個消費消費的時候都會把成功消費的日志寫到文件中。消費失敗的也寫入日志文件中堕义。這樣猜旬,我就知道失敗的具體原因。

但是倦卖,這次我真的沒有發(fā)現(xiàn)有任何的錯誤發(fā)生洒擦。

  • 常駐后臺進程處理存活狀態(tài)。并沒有變成孤兒進程怕膛。
  • 常駐后臺進程內(nèi)存也沒有出現(xiàn)泄漏熟嫩。
  • 系統(tǒng) CPU/內(nèi)存 資源都處理正在狀態(tài)。
  • 系統(tǒng)打開的句柄資源也是低消狀態(tài)褐捻。
  • 帶寬也處理低消狀態(tài)掸茅。
  • 其它常駐進程也處理正常消費的工作狀態(tài)。也就排除了 Redis 故障的問題柠逞。

鄙人當(dāng)時很氣餒昧狮。

我當(dāng)時也懷疑過是不是像 MySQL 一樣常時間連接不進行任何操作,服務(wù)器端會主動斷開連接板壮。但是逗鸣,MySQL 服務(wù)器端主動段掉連接會提示:MySQL server has gone away 的錯誤。但是,我們的 Redis 服務(wù)器端沒有給我們報任何錯誤信息呀撒璧。

我們公司用的是阿里云的 Redis 產(chǎn)品透葛。我懷疑是不是 Redis 版本太低造成的這個隱性 BUG。于是卿樱,我們將阿里云的 Redis 服務(wù)升級到了阿里云支持的最新版本获洲。

結(jié)果還是失敗了。我們的 Redis 還是假死了殿如。或者說我們的 Redis 處于偽活狀態(tài)最爬。

你認(rèn)為 Redis 活著涉馁,其實它早已經(jīng)死了。你認(rèn)為 Redis 死了爱致,但是它卻沒有死亡的特征烤送。

最后,我冷靜下來糠悯。

我假定此時的 Redis 已經(jīng)死了帮坚。只是沒有告訴客戶端而已。那么我只需要每次檢測一下 Redis 連接是否存活就好了互艾。

于是试和,我翻看了 Redis 的 API。發(fā)現(xiàn)它提供了一個 ping() 的方法來檢測連接是否存活纫普。

于是阅悍,我迫不及待把這個代碼加上去了。

代碼如下:

二昨稼、一個不再假死(偽活)的 Redis 常駐進程示例

<?php

$redis = new \Redis();
$redis->connect('localhost', 6379);
$redis->auth('xxxxx'); // Redis 密碼如果沒有設(shè)置為空字符串节视。
$redis->select(1);

$queueKey    = 'redis_queue_services_key';     // 業(yè)務(wù)數(shù)據(jù)隊列。
$queueIngKey = 'redis_queue_services_ing_key'; // 處理中的隊列假栓。

try {
    while (true) {
        $element = $redis->bRPopLPush($queueKey, $queueIngKey, 60);
        if ($element) {
            $data = json_decode($element, true);
            /**
             *
             ...... 此處省略業(yè)務(wù)邏輯 ......
             *
             */
        } else {
            $pong = $redis->ping();
            if ($pong != '+PONG') {
                throw new \Exception('Redis ping failure!', 500);
            }
            usleep(100000); // 睡眠 0.1 秒寻行。
        }
    }
} catch (\Exception $e) {
    exit("Error:{$e->getMessage()}");
}

通過代碼對比,我們在第一版代碼的基礎(chǔ)上加了如下代碼:

$pong = $redis->ping();
if ($pong != '+PONG') {
    throw new \Exception('Redis ping failure!', 500);
}

我們向 Redis 服務(wù)器發(fā)送 ping 的時候匾荆,服務(wù)器會返回 +PONG 字符串拌蜘。當(dāng)然,這個是 Redis 擴展封裝過的方法牙丽。真正的 ping 是不會有 + 號的拦坠。

當(dāng)我們每次 ping 的時候,Redis 服務(wù)器就會認(rèn)為我們的 Redis 客戶端連接處于存活狀態(tài)剩岳。就不會斷掉我們的連接了贞滨。

把代碼進行改造之后,假死頭痛的問題再也沒出現(xiàn)了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晓铆,一起剝皮案震驚了整個濱河市勺良,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骄噪,老刑警劉巖尚困,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異链蕊,居然都是意外死亡事甜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門滔韵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逻谦,“玉大人,你說我怎么就攤上這事陪蜻“盥恚” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵宴卖,是天一觀的道長滋将。 經(jīng)常有香客問我,道長症昏,這世上最難降的妖魔是什么随闽? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮肝谭,結(jié)果婚禮上橱脸,老公的妹妹穿的比我還像新娘。我一直安慰自己分苇,他們只是感情好添诉,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著医寿,像睡著了一般栏赴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靖秩,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天须眷,我揣著相機與錄音,去河邊找鬼沟突。 笑死花颗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惠拭。 我是一名探鬼主播扩劝,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼庸论,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棒呛?” 一聲冷哼從身側(cè)響起聂示,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎簇秒,沒想到半個月后鱼喉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡趋观,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年扛禽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皱坛。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡编曼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出麸恍,到底是詐尸還是另有隱情,我是刑警寧澤搀矫,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布抹沪,位于F島的核電站,受9級特大地震影響瓤球,放射性物質(zhì)發(fā)生泄漏融欧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一卦羡、第九天 我趴在偏房一處隱蔽的房頂上張望噪馏。 院中可真熱鬧,春花似錦绿饵、人聲如沸欠肾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刺桃。三九已至,卻和暖如春吸祟,著一層夾襖步出監(jiān)牢的瞬間瑟慈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工屋匕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留葛碧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓过吻,卻偏偏與公主長得像进泼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容