靈感來襲,基於Redis的分佈式延遲隊列(續)
背景
上一篇(靈感來襲,基於Redis的分佈式延遲隊列)講述了基於Java DelayQueue和Redis實現了分佈式延遲隊列,這種方案實現比較簡單,應用於延遲小,消息量不大的場景是沒問題的,畢竟Java DelayQueue是佔用內存的。針對現用方案的不足,於是利用Redis的Sorted Set數據結構簡單實現分佈式延遲隊列。
Sorted Set
- Redis 有序集合和集合一樣也是string類型元素的集合,且不允許重複的成員。
- 不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。
- 有序集合的成員是唯一的,但分數(score)卻可以重複。
設計思路
- 使用Redis的Sorted Set作為中轉隊列,為防止延遲消息量過大,維護多個Sorted Set,將延遲消息按照hash值平均分佈到不同的Sorted Set中。
- 由於Sorted Set本身具備有序性,將延遲時間作為score值和延遲消息綁定到一起存入Sorted Set中。
- 另起Java定時任務,每隔一定時間掃描所有Sorted Set,並通過ZRANGEBYSCORE操作取出符合條件的延遲消息,然後放入目標隊列等待消費者消費。
代碼實現
延遲隊列創建
根據queueName分別創建中轉隊列(Sorted Set)和 目標隊列key值,其中queueSize是中轉隊列的大小。
延遲消息投遞
根據延遲消息的hash值,平均分配到不同的中轉隊列(Sorted Set)中去。
中轉定時任務
通過分佈式鎖來鎖定唯一的線程來執行延遲消息遷移到目標隊列的操作。遍歷全部的中轉隊列,因為延遲消息是和延遲時間戳關聯的,使用ZRANGEBYSCORE命令,取出延遲時間小於當前時間的50條消息並通過LPUSH命令放入目標隊列里。
延遲消息消費
通過RPOP命令不斷的從目標隊列獲取延遲消息,執行相應的消費邏輯。
總結
本文描述的實現方案還有諸多異常情況尚未考慮,比如生產者發送失敗、消費者消費失敗的情況,無法保證極端情況下生產者和消費者兩端的數據一致性。該方案可以滿足業務量不是很大、延遲時間較長、允許部分數據可能丟失的場景,比如用戶簽到提醒,可以根據用戶簽到的時間,第二天在相應的時間點推送消息提醒用戶繼續簽到。