早些時候,一直有個疑問,就是比如你從前端發一個操作之后,后台為什么能夠及時處理你的東西呢?當然了,我說的不是,服務器為什么能夠立即接收到你的請求之類高大上的東西。而是,假設你用異步去做一個事情,而后台有一個處理程序在處理你的申請,你的目的自然是不想讓操作阻塞,所以處理肯定是處理程序主動觸發的過程。那么怎樣做能夠讓其能夠及時處理問題呢? 確實也困惑了我許久。(我相信也有不少同學會有類似疑問)
所以我覺得有必要解解惑。
需求示例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可以調度各進程的執行,當然進程數是有限的,只要在這有限有量以內,提供幾個死循環還是可以的。(注意,死循環是保持自身活躍的一種方式,但並非所有的服務都是靠死循環來保持自身的活躍的)
信號量?是一種有效地處理本機通知的一種機制,且聽下回分解。