golang 實現 redis 延時任務 case 2 - zset

redis的ZSET會儲存一個score和一個value,可以將value按照score進行排序。

redis的ZSET是通過跳躍表來實現的,複雜度爲O(logN),N是存放在ZSET中元素的個數。可以依賴於redis自身的持久化來實現持久化,redis的集羣來支持高併發和高可用。整個數據處理過程延時較小。

相關命令

  1. Redis ZADD 命令將一個或多個 member 元素及其 score 值加入到有序集 key 當中。

如果某個 member 已經是有序集的成員,那麼更新這個 member 的 score 值,並通過重新插入這個 member 元素,來保證該 member 在正確的位置上。

ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
// 返回被成功添加的新成員的數量,不包括那些被更新的,或者已經存在的成員。

redis zset

  1. Redis ZRANGEBYSCORE 命令返回有序集合 key 中,所有 score 值介於 min 和 max 之間(包括等於 min 或 max )的成員。有序集成員按 score 值遞增(從小到大)順序排列。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
// 指定區間內,帶有 score 值(可選)的有序集成員的列表。

可選參數 LIMIT 指定返回結果的數量及區間(就像SQL中的 SELECT LIMIT offset, count ),注意當 offset 很大時,定位 offset 的操作可能需要遍歷整個有序集,此過程最壞複雜度爲 O(N) 時間。

無限區間:min 和 max 也可以是 -inf 和 +inf ,這樣一來,您就可以在不知道有序集的最低和最高 score 值的情況下,使用 ZRANGEBYSCORE 命令。

默認情況下,區間的取值使用閉區間 (小於等於或大於等於),通過給參數前增加(符號來使用可選的開區間,也就是小於或大於。示例如下:

ZRANGEBYSCORE zset (1 10    #表示 1<score<=10
ZRANGEBYSCORE zset (5 (20  #1<score<20

查詢zset的score分數最小的元素,可以用ZRANGEBYSCORE key -inf +inf limit 0 1 withscores命令來實現。

  1. Redis ZREM 命令移除有序集 key 中的一個或多個成員,不存在的成員將被忽略。當 key 存在但不是有序集類型時,返回一個錯誤。
ZREM key member [member ...]
// 被成功移除的成員的數量,不包括被忽略的成員。

實現原理

延時任務的實現分爲以下幾步來實現:

  1. 將任務的執行時間作爲score(訂單生成時間 + 延時時長),要執行的任務數據作爲value(如:訂單編號),存放在zset中;

  2. 啓動一個進程定時查詢zset的score分數小於等於當前時間戳的延時任務並執行;可以用ZRANGEBYSCORE key 0 當前時間戳 [WITHSCORES] [LIMIT offset count]命令來實現;然後刪除zset的這些任務。

架構圖

redis 延時任務 zset

  1. 爲了避免一個key存儲在數據量變多以後,首先會導致查詢速度變慢,因爲其時間複雜度爲O(logN);其次如果在同一個時間點有多個任務時,一個key會分發造成擁堵。因此,設計爲多個key來存儲,通過uuid進行hash路由到對應的key中,如果任務量增長,可以快速擴容redis key的數量來扛住增長的數量;
  2. 建立與多個key相同的進程或者線程數,每個event進程一個編號分別對應一個key,不斷輪詢相應的key;
  3. event進程只查詢出任務,但是不處理業務,將該任務寫入到消息隊列中,然後刪除zset的這些任務,另外有work取消息然後執行業務。這樣work進行可以分佈式部署,event只需做分發,這樣可以把併發做到非常高,即使同一時間有大量的任務,也能很小的延時內完成任務。當然如果任務量小也可以不用消息隊列直接在event處理任務。
  • 可擴展部分:
  1. 爲了避免event進程單機部署,在機器宕機後導致無法取消息,redis儲存的數據還會被積壓。可以多機部署event進程並使用zookeeper或者etcd選主,只有leader主機上的進程才從redis取消息。leader主機宕機後會自動選擇新的leader;
  2. 在實際的業務中還依賴DB寫入數據。延時任務產生是先修改DB然後再向redis寫入數據,那麼就存在DB更新成功,然後redis寫失敗的場景,這個時候首先是通過重試來減少redis寫入失敗的概率,如果重試任然不能成功,就發送一條消息給其他進程進行異步補償;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章