延遲隊列淺析

        早期需要延遲處理的業務場景,更多的是通過定時任務掃表,然後執行滿足條件的記錄,具有頻率高、命中低、資源消耗大的缺點。隨着消息中間件的普及,延遲消息可以很好的處理這種場景,本文主要介紹延遲消息的使用場景以及基於常見的消息中間件如何實現延遲隊列。

一、使用場景

1、有效期:限時活動、拼團。。。
2、超時處理:取消超時未支付訂單、超時自動確認收貨。。。
3、延遲處理:機器人點贊/觀看數/評論/關注、等待依賴條件。。。
4、重試:網絡異常重試、打車派單、依賴條件未滿足重試。。。
5、定時任務:智能設備定時啓動。。。

二、常見延遲隊列對比

1、RabbitMQ
1)簡介:基於AMQP協議,使用Erlang編寫,實現了一個Broker框架
        a、Broker:接收和分發消息的代理服務器
        b、Virtual Host:虛擬主機之間相互隔離,可理解爲一個虛擬主機對應一個消息服務
        c、Exchange:交換機,消息發送到指定虛擬機的交換機上
        d、Binding:交換機與隊列綁定,並通過路由策略和routingKey將消息投遞到一個或多個隊列中
        e、Queue:存放消息的隊列,FIFO,可持久化
        f、Channel:信道,消費者通過信道消費消息,一個TCP連接上可同時創建成百上千個信道,作爲消息隔離
在這裏插入圖片描述
2)延遲隊列實現:RabbitMQ的延遲隊列基於消息的存活時間TTL(Time To Live)和死信交換機DLE(Dead Letter Exchanges)實現
        a、TTL:RabbitMQ支持對隊列和消息各自設置存活時間,取二者中較小的值,即隊列無消費者連接或消息在隊列中一直未被消費的過期時間
        b、DLE:過期的消息通過綁定的死信交換機,路由到指定的死信隊列,消費者實際上消費的是死信隊列上的消息
在這裏插入圖片描述
3)缺點:
        a、配置麻煩,額外增加一個死信交換機和一個死信隊列的配置
        b、脆弱性,配置錯誤或者生產者消費者連接的隊列錯誤都有可能造成延遲失效
2、RocketMQ
1)簡介:來源於阿里,目前爲Apache頂級開源項目,使用Java編寫,基於長輪詢的拉取方式,支持事務消息,並解決了順序消息和海量堆積的問題
        a、Broker:存放Topic並根據讀取Producer的提交日誌,將邏輯上的一個Topic分多個Queue存儲,每個Queue上存儲消息在提交日誌上的位置
        b、Name Server:無狀態的節點,維護Topic與Broker的對應關係以及Broker的主從關係
在這裏插入圖片描述
2)延遲隊列實現:RocketMQ發送延時消息時先把消息按照延遲時間段發送到指定的隊列中(rocketmq把每種延遲時間段的消息都存放到同一個隊列中),然後通過一個定時器進行輪訓這些隊列,查看消息是否到期,如果到期就把這個消息發送到指定topic的隊列中
在這裏插入圖片描述
3)缺點:延遲時間粒度受限制(1s/5s/10s/30s/1m/2m/3m/4m/5m/6m/7m/8m/9m/10m/20m/30m/1h/2h)
3、Kafka
1)簡介:來源於Linkedin,目前爲Apache頂級開源項目,使用Scala和Java編寫,基於zookeeper協調的分佈式、流處理的日誌系統,升級版爲Jafka
在這裏插入圖片描述
2)延遲隊列實現:Kafka支持延時生產、延時拉取、延時刪除等,其基於時間輪和JDK的DelayQueue實現
        a、時間輪(TimingWheel):是一個存儲定時任務的環形隊列,底層採用數組實現,數組中的每個元素可以存放一個定時任務列表
        b、定時任務列表(TimerTaskList):是一個環形的雙向鏈表,鏈表中的每一項表示的都是定時任務項
        c、定時任務項(TimerTaskEntry):封裝了真正的定時任務TimerTask
        d、層級時間輪:當任務的到期時間超過了當前時間輪所表示的時間範圍時,就會嘗試添加到上層時間輪中,類似於鐘錶就是一個三級時間輪
        e、JDK DelayQueue:存儲TimerTaskList,並根據其expiration來推進時間輪的時間,每推進一次除執行相應任務列表外,層級時間輪也會進行相應調整
在這裏插入圖片描述
在這裏插入圖片描述
3)缺點:
        a、延遲精度取決於時間格設置
        b、延遲任務除由超時觸發還可能被外部事件觸發而執行
4、ActiveMQ
1)簡介:基於JMS協議,Java編寫的Apache頂級開源項目,支持點對點和發佈訂閱兩種模式。
        a、點對點(point-to-point):消息發送到指定的隊列,每條消息只有一個消費者能夠消費,基於拉模型
        b、發佈訂閱(publish/subscribe):消息發送到主題Topic上,每條消息會被訂閱該Topic的所有消費者各自消費,基於推模型
在這裏插入圖片描述
在這裏插入圖片描述
2)延遲隊列實現:需要延遲的消息會先存儲在JobStore中,通過異步線程任務JobScheduler將到達投遞時間的消息投遞到相應隊列上
        a、Broker Filter:Broker中定義了一系列BrokerFilter的子類構成攔截器鏈,按順序對消息進行相應處理
        b、ScheduleBroker:當消息中指定了延遲相關屬性,並且jobId爲空時,會生成調度任務存儲到JobStore中,此時消息不會進入到隊列
        c、JobStore:基於BTree存儲,key爲任務執行的時間戳,value爲該時間戳下需要執行的任務列表
        d、JobScheduler:取JobStore中最小的key執行(調度時間最早的),執行時間<=當前時間,將該任務列表依次投遞到所屬的隊列,對於需要重複投遞和投遞失敗的會再次存入JobStore中。
        注:此處JobScheduler的執行時間間隔可動態變化,默認0.5s,有新任務時會立即執行(Object->notifyAll())並設置時間間隔爲0.1s,沒有新任務後,下次執行時間爲最近任務的調度執行時間
在這裏插入圖片描述
3)缺點:投遞到隊列失敗,將消息重新存入JobStore,消息調度執行時間=系統當前時間+延遲時間,會導致消息被真實投遞的時間可能爲設置的延遲時間的整數倍
5、Redis
1)簡介:基於Key-Value的NoSQL數據庫,由於其極高的性能常被當作緩存來使用,其數據結構支持:字符串、哈希、列表、集合、有序集合
2)延遲隊列實現:Redis的延遲隊列基於有序集合,score爲執行時間戳,value爲任務實體或任務實體引用
在這裏插入圖片描述
3)缺點:
        a、實現複雜,本身不支持
        b、完全基於內存,延遲時間長浪費內存資源
6、消息隊列對比
在這裏插入圖片描述

三、小結

1、無論是基於死信隊列還是基於數據先存儲後投遞,本質上都是將延遲待發送的消息數據與正常訂閱的隊列分開存儲,從而降低耦合度
2、無論是檢查隊頭消息TTL還是調度存儲的延遲數據,本質上都是通過定時任務來完成的,但是定時任務的觸發策略以及延遲數據的存儲方式決定了不同中間件之間的性能優劣

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章