系列文章
非同步訊息佇列
說道訊息佇列,你肯定會想到Kafka
、Rabbitmq
等訊息中介軟體,這些專業的訊息中介軟體提供了很多功能特性,當然他的部署使用維護都是比較麻煩的。如果你對訊息佇列沒那麼高要求,想要輕量級的,使用Redis就沒錯啦。
Redis通過list
資料結構來實現訊息佇列.主要使用到如下命令:
- lpush和rpush入佇列
- lpop和rpop出佇列
- blpop和brpop阻塞式出佇列
廢話補不多說上程式碼:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
//傳送訊息
$redis->lPush($list, $value);
//消費訊息
while (true) {
try {
$msg = $redis->rPop($list);
if (!$msg) {
sleep(1);
}
//業務處理
} catch (Exception $e) {
echo $e->getMessage();
}
}
上面程式碼會有個問題如果佇列長時間是空的,那pop就不會不斷的迴圈,這樣會導致redis的QPS升高,影響效能。所以我們使用sleep
來解決,當沒有訊息的時候阻塞一段時間。但其實這樣還會帶來另一個問題,就是sleep
會導致訊息的處理延遲增加。這個問題我們可以通過blpop/brpop
來阻塞讀取佇列。
blpop/brpop
在佇列沒有資料的時候,會立即進入休眠狀態,一旦資料到來,則立刻醒過來。訊息的延遲幾乎為零。用blpop/brpop替代前面的lpop/rpop,就完美解決了上面的問題。
還有一個需要注意的點是我們需要是用try/catch
來進行異常捕獲,如果一直阻塞在那裡,Redis伺服器一般會主動斷開掉空連結,來減少閒置資源的佔用。
你是否在做電商專案的時候會遇到如下場景:
- 訂單下單後超過一小時使用者未支付,需要關閉訂單
- 訂單的評論如果7天未評價,系統需要自動產生一條評論
這個時候我們就需要用到延時佇列了,顧名思義就是需要延遲一段時間後執行。Redis可通過zset
來實現。我們可以將有序集合的value設定為我們的訊息任務,把value的score設定為訊息的到期時間,然後輪詢獲取有序集合的中的到期訊息進行處理。
實現程式碼如下:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->zAdd($delayQueue,$tts, $value);
while(true) {
try{
$msg = $redis->zRangeByScore($delayQueue,0,time(),0,1);
if($msg){
continue;
}
//刪除訊息
$ok = $redis.zrem($delayQueue,$msg);
if($ok){
//業務處理
}
} catch(\Exception $e) {
}
}
這裡又產生了一個問題,同一個任務可能會被多個程式取到之後再使用 zrem 進行爭搶,那些沒搶到的程式都是白取了一次任務,這是浪費。解決辦法:將 zrangebyscore
和zrem
使用lua指令碼進行原子化操作,這樣多個程式之間爭搶任務時就不會出現這種浪費了。
本文亦在微信公眾號【小道資訊】釋出,歡迎掃碼關注!