redis 延遲佇列

hasome發表於2024-03-13

參考: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是按相關分數排序的唯一字串(成員)的集合。當多個字串具有相同的分數時,這些字串按字典順序排列。

實現思路

  1. 訊息體設定有效期,設定好score,然後放入zset中
  2. 透過排名拉取訊息
  3. 有效期到了,就把當前訊息從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]

  1. 返回有序集 key 中,所有 score 值介於 min 和 max 之間(包括等於 min 或 max )的成員。有序整合員按 score 值遞增(從小到大)次序排列。
  2. 具有相同 score 值的成員按字典序來排列
  3. 可選的 LIMIT 引數指定返回結果的數量及區間(就像SQL中的 SELECT LIMIT offset, count ),注意當 offset 很大時,定位 offset 的操作可能需要遍歷整個有序集,此過程最壞複雜度為 O(N) 時間。
  4. 可選的 WITHSCORES 引數決定結果集是單單返回有序集的成員,還是將有序整合員及其 score 值一起返回。

zrem

ZREM key member [member …]
移除有序集 key 中的一個或多個成員,不存在的成員將被忽略。當 key 存在但不是有序集型別時,返回一個錯誤。

redis延遲佇列優缺點

優點

  1. Redis zset支援高效能的 score 排序。
  2. Redis是在記憶體上進行操作的,速度非常快。
  3. 支援指定訊息 remove
  4. Redis可以搭建叢集,當訊息很多時候,我們可以用叢集來提高訊息處理的速度,提高可用性。
  5. Redis具有持久化機制,當出現故障的時候,可以透過AOF和RDB方式來對資料進行恢復,保證了資料的可靠性

缺點

  1. 使用 Redis 實現的延時訊息佇列也存在資料持久化, 訊息可靠性的問題
  2. 沒有重試機制 - 處理訊息出現異常沒有重試機制, 這些需要自己去實現, 包括重試次數的實現等
  3. 沒有 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)是一個儲存定時任務的環形佇列,可以進行相關的延時佇列設定。
image

Netty實現延時佇列

Netty也有基於時間輪演算法來實現延時佇列。Netty在構建延時佇列主要用HashedWheelTimer,HashedWheelTimer底層資料結構是使用DelayedQueue,採用時間輪的演算法來實現。
image

相關文章