了解一下Redis隊列【緩兵之計-延時隊列】
https://www.cnblogs.com/wt645631686/p/8454021.html
我們平時習慣於使用 Rabbitmq 和 Kafka 作為消息隊列中間件,來給應用程序之間增加 異步消息傳遞功能。這兩個中間件都是專業的消息隊列中間件,特性之多超出了大多數人的理 解能力。
使用過 Rabbitmq 的同學知道它使用起來有多復雜,發消息之前要創建 Exchange,再創 建 Queue,還要將 Queue 和 Exchange 通過某種規則綁定起來,發消息的時候要指定 routing- key,還要控制頭部信息。消費者在消費消息之前也要進行上面一系列的繁瑣過程。但是絕大 多數情況下,雖然我們的消息隊列只有一組消費者,但還是需要經歷上面這些繁瑣的過程。
有了 Redis,它就可以讓我們解脫出來,對於那些只有一組消費者的消息隊列,使用 Redis就可以非常輕松的搞定。Redis 的消息隊列不是專業的消息隊列,它沒有非常多的高級特性, 沒有 ack 保證,如果對消息的可靠性有著極致的追求,那麽它就不適合使用。
異步消息隊列
Redis 的 list(列表) 數據結構常用來作為異步消息隊列使用,使用rpush/lpush操作入隊列, 使用 lpop 和 rpop 來出隊列。
> rpush notify-queue apple banana pear (integer) 3 > llen notify-queue (integer) 3 > lpop notify-queue "apple" > llen notify-queue (integer) 2 > lpop notify-queue "banana" > llen notify-queue (integer) 1 > lpop notify-queue "pear" > llen notify-queue (integer) 0 > lpop notify-queue (nil)
上面是 rpush 和 lpop 結合使用的例子。還可以使用 lpush 和 rpop 結合使用,效果是一 樣的。這裏不再贅述。
隊列空了怎麽辦?
客戶端是通過隊列的 pop 操作來獲取消息,然後進行處理。處理完了再接著獲取消息, 再進行處理。如此循環往復,這便是作為隊列消費者的客戶端的生命周期。
可是如果隊列空了,客戶端就會陷入 pop 的死循環,不停地 pop,沒有數據,接著再 pop, 又沒有數據。這就是浪費生命的空輪詢。空輪詢不但拉高了客戶端的 CPU,redis 的 QPS 也 會被拉高,如果這樣空輪詢的客戶端有幾十來個,Redis 的慢查詢可能會顯著增多。
通常我們使用 sleep 來解決這個問題,讓線程睡一會,睡個 1s 鐘就可以了。不但客戶端 的 CPU 能降下來,Redis 的 QPS 也降下來了。
隊列延遲
用上面睡眠的辦法可以解決問題。但是有個小問題,那就是睡眠會導致消息的延遲增大。 如果只有 1 個消費者,那麽這個延遲就是 1s。如果有多個消費者,這個延遲會有所下降,因 為每個消費者的睡覺時間是岔開來的。
有沒有什麽辦法能顯著降低延遲呢?你當然可以很快想到:那就把睡覺的時間縮短點。這 種方式當然可以,不過有沒有更好的解決方案呢?當然也有,那就是 blpop/brpop。
這兩個指令的前綴字符 b 代表的是 blocking,也就是阻塞讀。
阻塞讀在隊列沒有數據的時候,會立即進入休眠狀態,一旦數據到來,則立刻醒過來。消 息的延遲幾乎為零。用 blpop/brpop 替代前面的 lpop/rpop,就完美解決了上面的問題。.
空閑連接自動斷開
你以為上面的方案真的很完美麽?先別急著開心,其實他還有個問題需要解決。 什麽問題?—— 空閑連接的問題。
鎖沖突處理
上節課我們講了分布式鎖的問題,但是沒有提到客戶端在處理請求時加鎖沒加成功怎麽辦。 一般有 3 種策略來處理加鎖失敗:
如果線程一直阻塞在哪裏,Redis 的客戶端連接就成了閑置連接,閑置過久,服務器一般
會主動斷開連接,減少閑置資源占用。這個時候 blpop/brpop 會拋出異常來。 所以編寫客戶端消費者的時候要小心,註意捕獲異常,還要重試。...
1、直接拋出異常,通知用戶稍後重試
這種方式比較適合由用戶直接發起的請求,用戶看到錯誤對話框後,會先閱讀對話框的內 容,再點擊重試,這樣就可以起到人工延時的效果。如果考慮到用戶體驗,可以由前端的代碼 替代用戶自己來進行延時重試控制。它本質上是對當前請求的放棄,由用戶決定是否重新發起 新的請求。
2、sleep 一會再重試
sleep 會阻塞當前的消息處理線程,會導致隊列的後續消息處理出現延遲。如果碰撞的比 較頻繁或者隊列裏消息比較多,sleep 可能並不合適。如果因為個別死鎖的 key 導致加鎖不成 功,線程會徹底堵死,導致後續消息永遠得不到及時處理。
3、將請求轉移至延時隊列,過一會再試;
這種方式比較適合異步消息處理,將當前沖突的請求扔到另一個隊列延後處理以避開沖突。
摘自《Redis深度歷險:核心原理和應用時間》
了解一下Redis隊列【緩兵之計-延時隊列】