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