你知道Redis可以實現延遲佇列嗎?

_BKing發表於2020-07-02

最近,又重新學習了下Redis,深深被Redis的魅力所折服,我才知道Redis不僅能快還能慢(我想也這麼優秀o(╥﹏╥)o),簡直是個利器呀。

 咳咳咳,大家不要誤會,本文很正經的啦!

好了,接下來回到我們的話題,我們都知道Redis是一種基於記憶體的單程式單執行緒資料庫(Redis6.0開始之後支援多執行緒啦!),處理速度都非常快。那麼為何Redis又能慢呢?原來,這裡說的慢是指Redis可以設定一些引數達到慢處理的結果。(這就是為什麼Redis既能快又能慢啦!)

那接下來開始講講我們的楷模Redis在佇列中如何實現延時的情況:

在我們日常生活中,我們可以發現,

  • 在淘寶、京東等購物平臺上下單,超過一定時間未付款,訂單會自動取消。
  • 叫車的時候,在規定時間沒有車主接單,平臺會取消你的單並提醒你暫時沒有車主接單。
  • 點外賣的時候,如果商家在10分鐘還沒接單,就會自動取消訂單。
  • 收快遞的時候,如果我們沒有點確認收貨,在一段時間後程式會自動完成訂單。
  • 在平臺完成訂單後,如果我們沒有在規定時間評論商品,會自動預設買家不評論。
  • .......還有很多這樣的場景。

這時,我們可以想想為什麼要這樣做?

因為這樣可以保證商品的庫存可以釋放給其他人購買,你可以不用一直等待叫車卻得不到回覆,你可以及時換一家店點到外賣。

那麼這些情況都是如何實現的呢?

這時我們可以看看這個圖,來看看訊息延遲是如何處理的:

 當使用者傳送一個訊息請求給伺服器後臺的時候,伺服器會檢測這條訊息是否需要進行延時處理,如果需要就放入到延時佇列中,由延時任務檢測器進行檢測和處理,對於不需要進行延時處理的任務,伺服器會立馬對訊息進行處理,並把處理後的結果相應返會給使用者。

 對於在延時任務檢測器內部的話,有查詢延遲任務和執行延時任務兩個職能,任務檢測器會先去延時任務佇列進行佇列中資訊讀取,判斷當前佇列中哪些任務已經時間到期並將已經到期的任務輸出執行(具有實時性,會存在一定的時間誤差,因為這個是定時任務)。

這時,我們可以想一想在Redis的資料結構中有哪些能進行時間設定標誌的命令?

是不是想到的 zset 這個命令,具有去重有序(分數排序)的功能。沒錯,你想對了呀!

我們可以使用 zset(sortedset)這個命令,用設定好的時間戳作為score進行排序,使用 zadd score1 value1 ....命令就可以一直往記憶體中生產訊息。再利用 zrangebysocre 查詢符合條件的所有待處理的任務,通過迴圈執行佇列任務即可。也可以通過 zrangebyscore key min max withscores limit 0 1 查詢最早的一條任務,來進行消費。

總的來說,你可以通過以下兩種方式來實現((*^▽^*)如果你想到其他方法,也可以告訴我下呀~):

(1)使用zrangebyscore來查詢當前延時佇列中所有任務,找出所有需要進行處理的延時任務,在依次進行操作。

(2)查詢當前最早的一條任務,通過score值來判斷任務執行的時候是否大於了當前系統的時候,比如說:最早的任務執行時間在3點,系統時間在2點58分),表示這個應該需要立馬被執行啦,時間快到了(沖沖衝,他來了他來了,他帶著死神的步伐來了)。

我們可以想一想Redis來實現延時佇列有何優勢呢?

其實,Redis用來進行實現延時佇列是具有這些優勢的:

(1)Redis zset支援高效能的 score 排序。

(2)Redis是在記憶體上進行操作的,速度非常快。

(3)Redis可以搭建叢集,當訊息很多時候,我們可以用叢集來提高訊息處理的速度,提高可用性。

(4)Redis具有持久化機制,當出現故障的時候,可以通過AOF和RDB方式來對資料進行恢復,保證了資料的可靠性

這時候,會有小夥伴問了還有沒有其他實現延時佇列的方式呀!emmm....當然有的,只有想不到的沒有做不到(O(∩_∩)O哈哈~,開玩笑)

一、用訊息中介軟體實現延時佇列

(1)通過 RabbitMQ 來實現延時佇列,也就是用訊息中介軟體來實現延時佇列啦~

方法一:在MQ中我們可以對Queue設定 x-expires 過期時間或者對 Message設定超時時間x-message-ttl。

(這裡要注意下:延時相同的訊息我們要扔到同一個佇列中,對於每一個延時要建立一個與之對應的佇列—這是由於MQ的過期檢測是惰性檢測的。)

方法二:我們可以用RabbitMQ的外掛rabbitmq-delayed-message-exchange外掛來實現延時佇列。達到可投遞時間時並將其通過x-delayed-type型別標記的交換機型別投遞至目標佇列。

 

(2)RocketMQ實現延時佇列

rocketmq在傳送延時訊息時,是先把訊息按照延遲時間段傳送到指定的佇列中(把延時時間段相同的訊息放到同一個佇列中,保證了訊息處理的順序性,可以讓同一個佇列中訊息延時時間是相同的,整個RocketMQ中延時訊息時按照遞增順序排序,保證資訊處理的先後順序性。)。之後,通過一個定時器來輪詢處理這些佇列裡的資訊,判斷是否到期。對於到期的訊息會傳送到相應的處理佇列中,進行處理。

哈哈,目前RocketMQ只支援特定的延時時間段,1s,5s,10s,...2h,不能支援任意時間段的延時設定。有興趣的小夥伴可以去了解下它是相關知識呀~

 

二、Kafka實現延時隊

Kafka基於時間輪自定義了一個用於實現延遲功能的定時器(SystemTimer),Kafka中的時間輪(TimingWheel)是一個儲存定時任務的環形佇列,可以進行相關的延時佇列設定。

 

 

三、Netty實現延時佇列

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

 

四、DelayQueue來實現延時佇列

Java中有自帶的DelayQueue資料型別,我們可以用這個來實現延時佇列。DelayQueue是封裝了一個PriorityQueue(優先佇列),在向DelayQueue佇列中新增元素時,會給元素一個Delay(延遲時間)作為排序條件,佇列中最小的元素會優先放在隊首,對於佇列中的元素只有到了Delay時間才允許從佇列中取出。這種實現方式是資料儲存在記憶體中,可能面臨資料丟失的情況,同時它是無法支援分散式系統的。

 

 

 

 相關程式碼我之後會上傳到我的GitHub上呀~(沖沖衝,小夥伴們!)又是美好的一天。

相關文章