編寫 PHP 守護(hù)進(jìn)程程序

守護(hù)進(jìn)程(daemon),又稱為常駐后臺(tái)進(jìn)程依疼。該進(jìn)程持續(xù)在后臺(tái)運(yùn)行痰腮,處理系統(tǒng)業(yè)務(wù)。它沒有控制終端律罢,不與前臺(tái)交互诽嘉。要么手動(dòng)殺死該進(jìn)程,要么系統(tǒng)關(guān)閉的時(shí)候被關(guān)閉弟翘。通常在小項(xiàng)目當(dāng)中 PHP 沒有此類需求虫腋。都是通過(guò)編寫定時(shí)腳本來(lái)執(zhí)行。

今天稀余,我們以完成異步發(fā)送短信來(lái)編寫 PHP 守護(hù)進(jìn)程程序悦冀。會(huì)講到編寫守護(hù)進(jìn)程程序中會(huì)遇到的一些問(wèn)題。以及這些問(wèn)題的解決方案睛琳。

一盒蟆、PHP CLI 模式###

PHP CLI 即 命令行模式踏烙。這是編寫常駐后臺(tái)程序必須掌握的知識(shí)點(diǎn)。關(guān)于 PHP CLI 相關(guān)的技術(shù)細(xì)節(jié)历等√殖停可以查看博主之前寫的一篇文章《PHP 命令行模式》

我們主要用了 PHP CLI 模式的運(yùn)行 PHP 腳本的功能寒屯。

如:

$ php test.php

二荐捻、實(shí)例代碼

為了避免空洞的理論。我們直接上代碼寡夹,然后對(duì)代碼進(jìn)行抽絲剝繭般分析处面。再一步一步優(yōu)化代碼,達(dá)到我們要求的守護(hù)進(jìn)程級(jí)別菩掏。

首先魂角,我們要理解異步發(fā)送短信的需求涉及的流程。

(1)用戶登錄/注冊(cè)等需求短信驗(yàn)證碼的位置智绸。點(diǎn)擊獲取驗(yàn)證碼野揪。

(2)服務(wù)器收到用戶的發(fā)送短信請(qǐng)求。將手機(jī)號(hào)碼以及待發(fā)送的短信內(nèi)容放入 Redis 隊(duì)列瞧栗。

(3)后臺(tái)進(jìn)程持續(xù)監(jiān)聽 Redis 隊(duì)列當(dāng)中是否有待處理的短信發(fā)送囱挑。有則發(fā)送。無(wú)則持續(xù)監(jiān)聽沼溜。

通過(guò)這三步平挑,我們清晰知道。這個(gè)異步短信發(fā)送的需求會(huì)涉及到三個(gè)技術(shù)點(diǎn):

(1)隊(duì)列:存儲(chǔ)待發(fā)送短信的數(shù)據(jù)系草。

(2)把用戶短信發(fā)送請(qǐng)求寫入隊(duì)列通熄。

(3)從 Redis 隊(duì)列取出數(shù)據(jù)進(jìn)行短信發(fā)送。

假設(shè)我們的 Redis 隊(duì)列名稱為:sms_list 找都。

則寫入隊(duì)列的程序如下:

PushQueue.php 腳本代碼如下:

<?php
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$redis->select(1);

$sms = [
    'mobile'  => '14800001234',
    'content' => '您的驗(yàn)證碼為:888888唇辨。請(qǐng)及時(shí)使用,10 分鐘后失效能耻∩兔叮【IT訪談】'
];

$ok = $redis->lPush('sms_list', json_encode($sms, JSON_UNESCAPED_UNICODE));
if ($ok) {
    echo "寫入短信隊(duì)列 sms_list 成功\n";
}

SmsConsume.php 后臺(tái)消費(fèi)進(jìn)程代碼如下:

<?php
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$redis->select(1);

$queueKey = 'sms_list';     // 短信隊(duì)列。
$queueIng = 'sms_list_ing'; // 短處中的隊(duì)列晓猛。

while (true) {
    $content = $redis->bRPopLPush($queueKey, $queueIng, 60);
    if (!empty($content)) {
        $arrCxt = json_decode($content, true);
        /**
         * 調(diào)用短信發(fā)送接口饿幅。
         * 由于是演示代碼,此處直接打印輸出即可戒职。
         * 真實(shí)場(chǎng)景請(qǐng)調(diào)用短信發(fā)送的接口栗恩。
         */
        echo "mobile:{$arrCxt['mobile']}\n";
        echo "content:{$arrCxt['content']}\n\n";
    } else {
        // 暫停 0.1 秒。
        usleep(100000);
    }
}

啟動(dòng)生產(chǎn)端/消費(fèi)端

(1)啟動(dòng)消費(fèi)端

$ php SmsConsume.php

啟動(dòng)完成之后洪燥,命令終端會(huì)一直等待數(shù)據(jù)寫入 Redis 隊(duì)列磕秤。接下來(lái)乳乌,我們運(yùn)行生產(chǎn)端往 Redis 隊(duì)列寫入數(shù)據(jù)。

(2)啟動(dòng)生產(chǎn)端

我們另起一個(gè)命令終端執(zhí)行如下命令:

$ php PushQueue.php

運(yùn)行成功會(huì)輸出如下內(nèi)容:

寫入短信隊(duì)列 sms_list 成功

說(shuō)明市咆,我們已經(jīng)成功向 Redis sms_list 隊(duì)列寫入了短信發(fā)送的數(shù)據(jù)汉操。

同時(shí),在我們的消費(fèi)端命令終端輸出了如下內(nèi)容:

mobile:14800001234
content:您的驗(yàn)證碼為:888888蒙兰。請(qǐng)及時(shí)使用磷瘤,10 分鐘后失效●海【IT訪談】

問(wèn)題與缺點(diǎn):

(1)Redis 讀取數(shù)據(jù)錯(cuò)誤

在運(yùn)行消費(fèi)端 SmsConsume.php 程序的時(shí)候,如果我們的生產(chǎn)端超過(guò) 60 秒沒有向隊(duì)列寫入數(shù)據(jù)梭伐。消費(fèi)端在空閑 60 秒之后痹雅,會(huì)提示類似錯(cuò)誤:

...... Uncaught RedisException: read error on connection ......

錯(cuò)誤分析:

之所以出現(xiàn)這個(gè)錯(cuò)誤。是因?yàn)樵谖覀兊?PHP 配置里面默認(rèn)限制了一個(gè) socket 連接在 60 秒內(nèi)沒有任何操作就會(huì)斷開糊识。斷開的 socket 連接再去讀取數(shù)據(jù)肯定會(huì)報(bào)錯(cuò)绩社。此錯(cuò)誤依然會(huì)出現(xiàn)在 MySQL、Kafka赂苗、Memcache 等 socket 連接的系統(tǒng)愉耙。

解決方案:

知道了問(wèn)題所在,剩下的就是更改 PHP 這個(gè)默認(rèn)的配置拌滋。

default_socket_timeout = 60

雖然朴沿,我們可以直接在 php.ini 文件中修改此值。但是败砂,我們不建議這樣做赌渣。因?yàn)椋@個(gè)配置不僅會(huì)影響 PHP CLI 模式昌犹,同時(shí)也會(huì)影響 PHP CGI 模式(Web 訪問(wèn))坚芜。所以,我們只推薦在代碼當(dāng)中修改斜姥。

我們修改 SmsConsume.php 腳本代碼之后如下:

<?php

// 防止 Socket 連接空閑超時(shí)退出報(bào)錯(cuò)鸿竖。
ini_set('default_socket_timeout', -1);

$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$redis->select(1);

$queueKey = 'sms_list';     // 短信隊(duì)列。
$queueIng = 'sms_list_ing'; // 短處中的隊(duì)列铸敏。

while (true) {
    $content = $redis->bRPopLPush($queueKey, $queueIng, 60);
    if (!empty($content)) {
        $arrCxt = json_decode($content, true);
        /**
         * 調(diào)用短信發(fā)送接口缚忧。
         * 由于是演示代碼,此處直接打印輸出即可杈笔。
         * 真實(shí)場(chǎng)景請(qǐng)調(diào)用短信發(fā)送的接口搔谴。
         */
        echo "mobile:{$arrCxt['mobile']}\n";
        echo "content:{$arrCxt['content']}\n\n";
    } else {
        // 暫停 0.1 秒。
        usleep(100000);
    }
}

通過(guò)這樣修改之后桩撮,我們?cè)偃ミ\(yùn)行這個(gè)腳本敦第。就會(huì)發(fā)現(xiàn)不再出現(xiàn)這個(gè)錯(cuò)誤了峰弹。

(2)代碼報(bào)錯(cuò)進(jìn)程退出

因?yàn)闀?huì)發(fā)生類似 Redis 讀取數(shù)據(jù)錯(cuò)誤或其他 PHP 錯(cuò)誤。此時(shí)芜果,PHP 消費(fèi)端進(jìn)程就會(huì)終止執(zhí)行鞠呈。如果我們把這個(gè)消費(fèi)端程序設(shè)置為后端運(yùn)行的守護(hù)進(jìn)程。這顯然是不滿足常駐后臺(tái)運(yùn)行的目的右钾。

所以蚁吝,我們需要捕獲這些錯(cuò)誤。然后寫日志或打印到命令行終端舀射。

解決方案:

PHP 提供了 try catch 來(lái)解決異常窘茁。但是,有時(shí)候脆烟,PHP 并只是拋出異常山林,也有可能拋出 Notice、warning 等錯(cuò)誤邢羔。此時(shí)驼抹,我們最好的做法是把這些錯(cuò)誤轉(zhuǎn)成異常來(lái)處理。

在很多成熟的框架都已經(jīng)將錯(cuò)誤轉(zhuǎn)成異常來(lái)處理了拜鹤。所以框冀,我們唯一要做的就是使用 try catch 來(lái)捕獲異常就行了。

SmsConsume.php 腳本修改之后的代碼如下:

<?php

// 防止 Socket 連接空閑超時(shí)退出報(bào)錯(cuò)敏簿。
ini_set('default_socket_timeout', -1);

$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$redis->select(1);

$queueKey = 'sms_list';     // 短信隊(duì)列明也。
$queueIng = 'sms_list_ing'; // 短處中的隊(duì)列。

while (true) {
    try {
        $content = $redis->bRPopLPush($queueKey, $queueIng, 60);
        if (!empty($content)) {
            $arrCxt = json_decode($content, true);
            /**
             * 調(diào)用短信發(fā)送接口惯裕。
             * 由于是演示代碼诡右,此處直接打印輸出即可。
             * 真實(shí)場(chǎng)景請(qǐng)調(diào)用短信發(fā)送的接口轻猖。
             */
            echo "mobile:{$arrCxt['mobile']}\n";
            echo "content:{$arrCxt['content']}\n\n";
        } else {
            // 暫停 0.1 秒帆吻。
            usleep(100000);
        }
    } catch (\Exception $e) {
        echo "出錯(cuò)了!\n";
        echo "ErrorMsg:" . $e->getMessage() . "\n\n";
    } catch (\Throwable $e) {
        echo "出錯(cuò)了!\n";
        echo "ErrorMsg:" . $e->getMessage() . "\n\n";
    }
}

三、設(shè)置消費(fèi)端為后臺(tái)運(yùn)行

我們現(xiàn)在程序已經(jīng)寫好了×撸現(xiàn)在就需要將程序設(shè)置為后臺(tái)運(yùn)行猜煮。設(shè)置為后臺(tái)運(yùn)行的方案有很多種。

(1)Linux nohup 命令

關(guān)于該命令如何使用败许,大家可以通過(guò) Google 搜索得到相當(dāng)全的資料王带。這里就不用去 Google 搬運(yùn)了。

(2)Supervisor 管理

這是本博主寒冰推薦的方式市殷。Supervisor 是一款非常優(yōu)秀的進(jìn)程管理工具愕撰。關(guān)于如何使用,可以查看我之前寫的一篇文章:CentOS7 安裝和使用 Supervisor 工具 。非常詳盡怎樣使用 Supervisor 這款工具搞挣。

四带迟、總結(jié)

本篇文章只是一個(gè)精簡(jiǎn)版的守護(hù)進(jìn)程程序。核心的點(diǎn)都已經(jīng)涉及到囱桨。技術(shù)的細(xì)節(jié)方面還需要結(jié)合實(shí)際的業(yè)務(wù)進(jìn)行考量仓犬。如果,你在使用本篇文章提到的相關(guān)功能時(shí)有任何問(wèn)題舍肠,可以留言或者加群(168159147)咨詢搀继。謝謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翠语,一起剝皮案震驚了整個(gè)濱河市叽躯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肌括,老刑警劉巖点骑,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異们童,居然都是意外死亡畔况,警方通過(guò)查閱死者的電腦和手機(jī)鲸鹦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門慧库,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人馋嗜,你說(shuō)我怎么就攤上這事齐板。” “怎么了葛菇?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵甘磨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我眯停,道長(zhǎng)济舆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任莺债,我火速辦了婚禮滋觉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘齐邦。我一直安慰自己椎侠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布措拇。 她就那樣靜靜地躺著我纪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浅悉,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天趟据,我揣著相機(jī)與錄音,去河邊找鬼仇冯。 笑死之宿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的苛坚。 我是一名探鬼主播比被,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泼舱!你這毒婦竟也來(lái)了等缀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤娇昙,失蹤者是張志新(化名)和其女友劉穎尺迂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冒掌,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡噪裕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了股毫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膳音。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铃诬,靈堂內(nèi)的尸體忽然破棺而出祭陷,到底是詐尸還是另有隱情,我是刑警寧澤趣席,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布兵志,位于F島的核電站,受9級(jí)特大地震影響宣肚,放射性物質(zhì)發(fā)生泄漏想罕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一霉涨、第九天 我趴在偏房一處隱蔽的房頂上張望按价。 院中可真熱鬧,春花似錦嵌纲、人聲如沸俘枫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸠蚪。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茅信,已是汗流浹背盾舌。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蘸鲸,地道東北人妖谴。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像酌摇,于是被迫代替她去往敵國(guó)和親膝舅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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