參考:https://blog.csdn.net/weixin_42128977/article/details/126152834
https://cloud.tencent.com/developer/article/2310463?areaId=106001
場景
-
定時任務,比如任務A和任務B是同條流水線上的,當任務A完成了,一個小時後執行任務B
-
- 我們叫車,在規定時間內,沒有車主接單,那麼平臺就會推送訊息給你,提示暫時沒有車主接單。
-
- 網上支付場景,下單了,如果沒有在規定時間付款,平臺通常會發訊息提示訂單在有效期內沒有支付完成,此訂單自動取消之類的資訊。
-
- 我們買東西,如果在一定時間內,沒有對該訂單進行評分,這時候平臺會給一個預設分數給此訂單。
-
重試業務,比如業務A需要呼叫其它服務,而服務出現問題,這時候就需要做業務重試
-
- 當分散式鎖加鎖失敗時,將訊息放入到延遲佇列中處理
實現方式
- 基於訊息的延遲:指為每條訊息設定不同的延遲時間,那麼每當佇列中有新訊息進入的時候就會重新根據延遲時間排序,或者定義時間輪,新訊息落在指定位置;
- 基於佇列的延遲: 設定不同延遲級別的佇列,比如5s、1min、30mins、1h等,每個佇列中訊息的延遲時間都是相同的。
- 基於第一種不少元件都有實現方案,比如redis的sortset間接實現,kafka內部時間輪,rmq可安裝外掛實現。
- 第一種實時性高,不過主觀看會比較依賴元件本身,但自己實現就得考慮持久化、高可用等問題,建議直接使用元件本身;
- 第二種方案可以基於元件去實現,通用性會高點,不過實時性不高,更適合用於重試業務場景。
- 當然Redis本身並不支援延遲佇列,所以我們只是實現一個比較簡單的延遲佇列,而且Redis不太適合大量訊息堆積,所以只適合比較簡單的場景,然假如我們對訊息的實時性以及可靠性要求非常高,可能就需要使用MQ或kafka來實現了。
redis實現延遲佇列
基本原理
- 延遲佇列可以透過 zset 來實現,因為 zset 中有一個 score,我們可以把時間作為 score,將 value 存到 redis 中,然後透過輪詢的方式,去不斷的讀取訊息出來
- zset是按相關分數排序的唯一字串(成員)的集合。當多個字串具有相同的分數時,這些字串按字典順序排列。
實現思路
- 訊息體設定有效期,設定好score,然後放入zset中
- 透過排名拉取訊息
- 有效期到了,就把當前訊息從zset中移除
zadd
ZADD key score member [[score member][score member] …]
將一個或多個 member 元素及其 score 值加入到有序集 key 當中。如果 key 不存在,則建立一個空的有序集並執行 ZADD 操作。如果某個 member 已經是有序集的成員,那麼更新這個 member 的 score 值,並透過重新插入這個 member 元素,來保證該 member 在正確的位置上。score 值可以是整數值或雙精度浮點數。
zrangebyscore
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
- 返回有序集 key 中,所有 score 值介於 min 和 max 之間(包括等於 min 或 max )的成員。有序整合員按 score 值遞增(從小到大)次序排列。
- 具有相同 score 值的成員按字典序來排列
- 可選的 LIMIT 引數指定返回結果的數量及區間(就像SQL中的 SELECT LIMIT offset, count ),注意當 offset 很大時,定位 offset 的操作可能需要遍歷整個有序集,此過程最壞複雜度為 O(N) 時間。
- 可選的 WITHSCORES 引數決定結果集是單單返回有序集的成員,還是將有序整合員及其 score 值一起返回。
zrem
ZREM key member [member …]
移除有序集 key 中的一個或多個成員,不存在的成員將被忽略。當 key 存在但不是有序集型別時,返回一個錯誤。
redis延遲佇列優缺點
優點
- Redis zset支援高效能的 score 排序。
- Redis是在記憶體上進行操作的,速度非常快。
- 支援指定訊息 remove
- Redis可以搭建叢集,當訊息很多時候,我們可以用叢集來提高訊息處理的速度,提高可用性。
- Redis具有持久化機制,當出現故障的時候,可以透過AOF和RDB方式來對資料進行恢復,保證了資料的可靠性
缺點
- 使用 Redis 實現的延時訊息佇列也存在資料持久化, 訊息可靠性的問題
- 沒有重試機制 - 處理訊息出現異常沒有重試機制, 這些需要自己去實現, 包括重試次數的實現等
- 沒有 ACK 機制 - 例如在獲取訊息並已經刪除了訊息情況下, 正在處理訊息的時候客戶端崩潰了, 這條正在處理的這些訊息就會丟失, MQ 是需要明確的返回一個值給 MQ 才會認為這個訊息是被正確的消費了
訊息中介軟體實現延時佇列
Rabbitmq 延時佇列
優點:訊息持久化,分散式
缺點:延時相同的訊息必須扔在同一個佇列,每一種延時就需要建立一個佇列。因為當後面的訊息比前面的訊息先過期,還是隻能等待前面的訊息過期,這裡的過期檢測是惰性的。
使用: RabbitMQ 可以針對 Queue 設定 x-expires 或者針對 Message 設定 x-message-ttl ,來控制訊息的生存時間(可以根據 Queue 來設定,也可以根據 message 設定), Queue 還可以配置 x-dead-letter-exchange 和 x-dead-letter-routing-key (可選)兩個引數, 如果佇列內出現了 dead letter ,則按照這兩個引數重新路由轉發到指定的佇列,此時就可以實現延時佇列了。
RocketMQ實現延時佇列
rocketmq在傳送延時訊息時,是先把訊息按照延遲時間段傳送到指定的佇列中(把延時時間段相同的訊息放到同一個佇列中,保證了訊息處理的順序性,可以讓同一個佇列中訊息延時時間是相同的,整個RocketMQ中延時訊息時按照遞增順序排序,保證資訊處理的先後順序性。)。之後,透過一個定時器來輪詢處理這些佇列裡的資訊,判斷是否到期。對於到期的訊息會傳送到相應的處理佇列中,進行處理。
Kafka實現延時佇列
Kafka基於時間輪自定義了一個用於實現延遲功能的定時器(SystemTimer),Kafka中的時間輪(TimingWheel)是一個儲存定時任務的環形佇列,可以進行相關的延時佇列設定。
Netty實現延時佇列
Netty也有基於時間輪演算法來實現延時佇列。Netty在構建延時佇列主要用HashedWheelTimer,HashedWheelTimer底層資料結構是使用DelayedQueue,採用時間輪的演算法來實現。