RabbitMQ、RocketMQ、Kafka延遲佇列實現
延遲佇列在實際專案中有非常多的應用場景,最常見的比如訂單未支付,超時取消訂單,在建立訂單的時候傳送一條延遲訊息,達到延遲時間之後消費者收到訊息,如果訂單沒有支付的話,那麼就取消訂單。
那麼,今天我們需要來談的問題就是RabbitMQ、RocketMQ、Kafka中分別是怎麼實現延時佇列的,以及他們對應的實現原理是什麼?
RabbitMQ
RabbitMQ本身並不存在延遲佇列的概念,在 RabbitMQ 中是透過 DLX 死信交換機和 TTL 訊息過期來實現延遲佇列的。
TTL(Time to Live)過期時間
有兩種方式可以設定 TTL。
1. 透過佇列屬性設定,這樣的話佇列中的所有訊息都會擁有相同的過期時間
2. 對訊息單獨設定過期時間,這樣每條訊息的過期時間都可以不同
那麼如果同時設定呢?這樣將會以兩個時間中較小的值為準。
針對佇列的方式透過引數x-message-ttl
來設定。
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-message-ttl", 6000);
channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);
針對訊息的方式透過setExpiration
來設定。
AMQP.BasicProperties properties = new AMQP.BasicProperties();
Properties.setDeliveryMode(2);
properties.setExpiration("60000");
channel.basicPublish(exchangeName, routingKey, mandatory, properties, "message".getBytes());
DLX(Dead Letter Exchange)死信交換機
一個訊息要成為死信訊息有 3 種情況:
1. 訊息被拒絕,比如呼叫
reject
方法,並且需要設定requeue
為false
2. 訊息過期
3. 佇列達到最大長度
可以透過引數dead-letter-exchange
設定死信交換機,也可以透過引數dead-letter- exchange
指定 RoutingKey(未指定則使用原佇列的 RoutingKey)。
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "exchange.dlx");
args.put("x-dead-letter-routing-key", "routingkey");
channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);
原理
當我們對訊息設定了 TTL 和 DLX 之後,當訊息正常傳送,透過 Exchange 到達 Queue 之後,由於設定了 TTL 過期時間,並且訊息沒有被消費(訂閱的是死信佇列),達到過期時間之後,訊息就轉移到與之繫結的 DLX 死信佇列之中。
這樣的話,就相當於透過 DLX 和 TTL 間接實現了延遲訊息的功能,實際使用中我們可以根據不同的延遲級別繫結設定不同延遲時間的佇列來達到實現不同延遲時間的效果。
RocketMQ
RocketMQ 和 RabbitMQ 不同,它本身就有延遲佇列的功能,但是開源版本只能支援固定延遲時間的訊息,不支援任意時間精度的訊息(這個好像只有阿里雲版本的可以)。
他的預設時間間隔分為 18 個級別,基本上也能滿足大部分場景的需要了。
預設延遲級別:1s、 5s、 10s、 30s、 1m、 2m、 3m、 4m、 5m、 6m、 7m、 8m、 9m、 10m、 20m、 30m、 1h、 2h。
使用起來也非常的簡單,直接透過setDelayTimeLevel
設定延遲級別即可。
setDelayTimeLevel(level)
原理
實現原理說起來比較簡單,Broker 會根據不同的延遲級別建立出多個不同級別的佇列,當我們傳送延遲訊息的時候,根據不同的延遲級別傳送到不同的佇列中,同時在 Broker 內部透過一個定時器去輪詢這些佇列(RocketMQ 會為每個延遲級別分別建立一個定時任務),如果訊息達到傳送時間,那麼就直接把訊息傳送到指 topic 佇列中。
RocketMQ 這種實現方式是放在服務端去做的,同時有個好處就是相同延遲時間的訊息是可以保證有序性的。
談到這裡就順便提一下關於訊息消費重試的原理,這個本質上來說其實是一樣的,對於消費失敗需要重試的訊息實際上都會被丟到延遲佇列的 topic 裡,到期後再轉發到真正的 topic 中。
Kafka
對於 Kafka 來說,原生並不支援延遲佇列的功能,需要我們手動去實現,這裡我根據 RocketMQ 的設計提供一個實現思路。
這個設計,我們也不支援任意時間精度的延遲訊息,只支援固定級別的延遲,因為對於大部分延遲訊息的場景來說足夠使用了。
只建立一個 topic,但是針對該 topic 建立 18 個 partition,每個 partition 對應不同的延遲級別,這樣做和 RocketMQ 一樣有個好處就是能達到相同延遲時間的訊息達到有序性。
原理
• 首先建立一個單獨針對延遲佇列的 topic,同時建立 18 個 partition 針對不同的延遲級別
• 傳送訊息的時候根據延遲引數傳送到延遲 topic 對應的 partition,對應的
key
為延遲時間,同時把原 topic 儲存到 header 中
ProducerRecord<Object, Object> producerRecord = new ProducerRecord<>("delay_topic", delayPartition, delayTime, data);
producerRecord.headers().add("origin_topic", topic.getBytes(StandardCharsets.UTF_8));
• 內嵌的
consumer
單獨設定一個ConsumerGroup
去消費延遲 topic 訊息,消費到訊息之後如果沒有達到延遲時間那麼就進行pause
,然後seek
到當前ConsumerRecord
的offset
位置,同時使用定時器去輪詢延遲的TopicPartition
,達到延遲時間之後進行resume
• 如果達到了延遲時間,那麼就獲取到
header
中的真實 topic ,直接轉發
這裡為什麼要進行pause
和resume
呢?因為如果不這樣的話,如果超時未消費達到max.poll.interval.ms
最大時間(預設300s),那麼將會觸發 Rebalance。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2929046/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- RabbitMQ 實現延遲佇列MQ佇列
- RabbitMQ實現延遲佇列MQ佇列
- 使用RabbitMq原生實現延遲佇列MQ佇列
- Golang 實現 RabbitMQ 的延遲佇列GolangMQ佇列
- 如何用RabbitMQ實現延遲佇列MQ佇列
- RabbitMQ實戰《延遲佇列》MQ佇列
- Delayed Message 外掛實現 RabbitMQ 延遲佇列MQ佇列
- 實現簡單延遲佇列和分散式延遲佇列佇列分散式
- 基於訊息佇列(RabbitMQ)實現延遲任務佇列MQ
- Spring Boot(十四)RabbitMQ延遲佇列Spring BootMQ佇列
- RabbitMQ 學習筆記 -- 12 死信佇列 DLX + TTL 方式實現延遲佇列MQ筆記佇列
- 使用 RabbitMQ 實現延時佇列MQ佇列
- 如何才能讓Spring Boot與RabbitMQ結合實現延遲佇列Spring BootMQ佇列
- 【RabbitMQ】一文帶你搞定RabbitMQ延遲佇列MQ佇列
- php+redis實現延遲佇列PHPRedis佇列
- 一張圖帶你理解和實現RabbitMQ的延遲佇列功能MQ佇列
- Node.js結合RabbitMQ延遲佇列實現定時任務Node.jsMQ佇列
- redis 延遲佇列Redis佇列
- Laravel 延遲佇列Laravel佇列
- 你知道Redis可以實現延遲佇列嗎?Redis佇列
- 高可用延遲佇列設計與實現佇列
- RabbitMQ 延遲佇列實現訂單支付結果非同步階梯性通知MQ佇列非同步
- RabbitMQ,RocketMQ,Kafka 幾種訊息佇列的對比MQKafka佇列
- 延時佇列(RabbitMQ)佇列MQ
- 延遲阻塞佇列 DelayQueue佇列
- hyperf redis延遲佇列Redis佇列
- Kafka 延時佇列&重試佇列Kafka佇列
- RabbitMQ:偽延時佇列MQ佇列
- rabbitMQ 延遲佇列外掛強制呼叫ReturnCallback裡returnedMessage方法MQ佇列
- [Redis]延遲訊息佇列Redis佇列
- 基於rabbitmq延遲外掛實現分散式延遲任務MQ分散式
- RabbitMQ延時佇列的使用MQ佇列
- RabbitMQ使用 prefetch_count優化佇列的消費,使用死信佇列和延遲佇列實現訊息的定時重試,golang版本MQ優化佇列Golang
- 你真的知道怎麼實現一個延遲佇列嗎?佇列
- RabbitMQ釋出訂閱實戰-實現延時重試佇列MQ佇列
- 訊息佇列-一篇讀懂rabbitmq(生命週期,confirm模式,延遲佇列,叢集)佇列MQ模式
- netcore下RabbitMQ佇列、死信佇列、延時佇列及小應用NetCoreMQ佇列
- C#通過rabbitmq實現定時任務(延時佇列)C#MQ佇列