1. 程式人生 > >while(true)應用 之 實現自己的消息隊列

while(true)應用 之 實現自己的消息隊列

怎樣 到來 郵件 body gpo amp 出了 理解 val

  早些時候,一直有個疑問,就是比如你從前端發一個操作之後,後臺為什麽能夠及時處理你的東西呢?當然了,我說的不是,服務器為什麽能夠立即接收到你的請求之類高大上的東西。而是,假設你用異步去做一個事情,而後臺有一個處理程序在處理你的申請,你的目的自然是不想讓操作阻塞,所以處理肯定是處理程序主動觸發的過程。那麽怎樣做能夠讓其能夠及時處理問題呢? 確實也困惑了我許久。(我相信也有不少同學會有類似疑問)

  所以我覺得有必要解解惑。

  需求示例1: 用戶請求某操作時,需要與此同時給用戶發個郵件,但是這個郵件可能會很耗時,在不使用異步線程的情況下(事實上不是所有的語言都支持多線程),怎麽樣讓用戶得到快速響應,且郵件稍後即可送達?

  需求示例2:在高並發情況下,需要對某數據進行減操作(比如商品庫存,如果超出了庫存值,則將無貨可發),怎樣保證用戶先到先得,後到就沒有?

  針對這兩個問題,解決辦法自然是多的。但是本文只說一個思路,那就是主題,消息隊列!

    當需要給用戶發送郵件時,只需將該請求發送到後臺進程中,後臺進程進行逐個發送即可。

    當大量用戶進來搶商品時,將該請求放入隊列中,後臺進程逐個相減,減到0時,後續用戶將提示搶不到了。(當然,這有很多後續問題要處理,也看起來不一定是個好方案,但並不影響咱們發揮)

  看起來,前面的解決方案很合理。但是,具體怎麽樣做呢?前臺申請,與後臺處理之間,總得有個什麽東西聯系起來吧。沒錯,就是消息隊列了。消息隊列自然需要消息中間件,簡單的,咱們就使用redis做中間件吧,簡單快速搞得定。

  具體實施方案就是:1. 各處的用戶進行相應的操作請求,然後順便將消息寫入redis,(以list形式寫入,天然的隊列); 2. 後臺進程依次從redis中讀取消息,進行相應數據處理(註意如何依次處理是關鍵)。 3. 將結果通知給用戶或者不通知。(本處將不通知)

  示例代碼如下(php實現):

<?php
// send 請求方,寫入消息
$redis = new Redis();
$redis->connect("127.0.0.1", "6379", 3);

$msgKey = "my.test.msgKey";
$value = "hello,world." . rand(0, 99999999);
$redis->lPush($msgKey, $value); // 將請求送入隊列中,等後臺消費 echo "lPush {$msgKey} -> {$value}";

  後臺進程進行依次處理,一般來說有兩個方案: 1. 通過系統進行定時調度,每次調度,則執行一段消息處理(此種方案的缺點明顯,需依賴系統處理,且將會是不及時的); 2. 通過自身調度,使自己一直處理運行中狀態,當發現有新消息到來時,立即進行處理(本處討論的是此種方案)。處理代碼如下:

<?php
// recieve 處理程序
$redis = new Redis();
$redis->connect("127.0.0.1", "6379", 3);

$msgKey = "my.test.msgKey";

while(true) {
    $stackTop = $redis->rPop($msgKey);
    if($stackTop) {
        // do sth useful
        echo $stackTop . "\r\n";
    } else {
        usleep(200000);         // 如果沒有消息需要處理,則睡眠0.2秒等待
    }
}

  寫好後,只要在命令行將該腳本跑起來即可:

php  recieve.php &

  其實原理很簡單,就是一個while死循環,然後一直在查詢 消息狀態,有就處理,沒有就稍微等一下再查。

  如果啟動多個後臺程序,那麽,就相當於有多個消費者了,從而加快了處理速度。(生產者 -> 消費者 簡單模型)

  那麽,問題在哪裏?為什麽剛開始的時候沒想到這樣處理呢?

  我有兩個疑問:

     1. 用戶while死循環不會導致死機嗎?

     2. 我想修改代碼裏的東西,怎樣才能生效?

  針對第一個問題,如果是沒有 sleep 限制的話,機器是有可能死機的。在調用 sleep後,cpu會轉到其他進程上進行事務處理,從而不會有問題。sleep時間過長,則會有明顯的時間停頓現象,即用戶操作無法得到及時響應。sleep時間過短,則會導致cpu占用過高,從而引發其他一系列問題。因此設置一個合適的sleep時間是有必要的。本處設置的0.2秒,經查看cpu狀態,占用為0%,所以沒問題。而且0.2秒在用戶看來,是沒有什麽影響的。

技術分享圖片

  第二個問題,修改了代碼,如何生效?重啟就好了嘛。

ps -ef | grep php      # 找出運行代碼的pid
kill -9 123            # 將進程kill掉
php recieve.php        # 重新運行代碼即可

  通過該種自身輪詢的方式,從而達到了及時處理任務的方式。

  死循環廣泛應用於各服務中,只是我們都沒發現。

  這也換一個現實的問題角度理解,只有自己一直活著,才有可能服務於別人。

  那麽,假如每個程序跑起來後,都一直存活著,CPU不就完蛋了? 是的,這就是計算機所能運行的服務有限的原因。CPU可以調度各進程的執行,當然進程數是有限的,只要在這有限有量以內,提供幾個死循環還是可以的。(註意,死循環是保持自身活躍的一種方式,但並非所有的服務都是靠死循環來保持自身的活躍的)

  信號量?且聽下回分解。

while(true)應用 之 實現自己的消息隊列