訊息佇列批次收發訊息,請避開這 5 個坑!
來源:君哥聊技術
大家好,我是君哥。
使用訊息佇列時,為了提高生產和消費的效能,有時會開啟批次處理。
在生產端,生產者傳送的訊息先傳送到一個訊息列表,積累到一定的訊息量之後再批次傳送給 Broker,如下圖:
在消費端,消費者拉取訊息後先不立即處理,而是把訊息轉存到一個記憶體佇列或資料庫,由業務執行緒去處理,如下圖:
無論是生產者做批次傳送,還是消費者做批次處理,都需要考慮使用批次訊息的業務場景,避免踩坑。下面看一下批次操作可能會遇到哪些坑。
批次大小
當生產者採用批次傳送的方式來提高傳送效能時,一定要考慮傳送訊息的批次大小。下面是 RocketMQ 批次傳送的官方示例:
String topic = "BatchTest";
List<Message> messages = new ArrayList<>();
messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes()));
try {
producer.send(messages);
} catch (Exception e) {
e.printStackTrace();
//handle the error
}
RocketMQ 預設訊息大小是 4M,由 maxMessageSize 引數控制,如果批次訊息大小超過 maxMessageSize,則會丟擲異常。
如果遇到訊息大小超過 maxMessageSize 的情況時,可以用下面方法進行處理:
把這個引數改大,但需要考慮 Broker 的效能和網路頻寬; 將訊息進行拆分後分批傳送; 對訊息進行壓縮處理。
RabbitMQ 相關的 API 則提供了更加靈活的批次控制,對訊息數量和訊息大小都做了控制,下面看一下原始碼:
冪等
消費端可以批次拉取訊息進行消費,這樣可以減少拉取訊息時的 RPC 次數,提升消費效能。比如在 RocketMQ 中,可以透過 Consumer 中的 pullBatchSize 來設定一次拉取的訊息數量,透過 consumeMessageBatchMaxSize 引數來設定一次消費的訊息數量。
但需要注意的是,如果批次訊息中一條訊息消費失敗了,這一批訊息都需要進行重試,已經消費成功的訊息會被重複消費,帶來業務問題。
為了不對業務造成影響,必須考慮冪等。一個簡單的方法是在訊息中增加全域性唯一 id 屬性,對訊息消費結果進行記錄,消費成功後儲存 id。這樣在消費訊息之前先查詢是否存在消費成功的記錄,如果存在則直接返回處理成功。
時延
在使用訊息佇列進行批次操作時,必須要考慮到時延問題。比如我們設定一個批次 100 條訊息,積累夠 100 條訊息後再傳送,在訊息量小的情況下,可能積累夠 100 條訊息會很長時間,導致消費端拉取到一條訊息時延很大。
雖然訊息佇列的一個重要作用是削峰填谷,但在一些場景下,對訊息的實時性也有要求。比如在車聯網的充電場景,車聯網平臺需要實時感知充電樁的狀態,如果充電樁積累夠一批訊息再上報平臺,平臺獲取到的狀態會不準確,如果心跳訊息延時太久,平臺會認為充電樁離線。
對於有時延要求又需要批次操作的場景,可以設定一個超時時間,超時後即使訊息數量不夠,也會傳送出去。看下 RabbitMQ 的處理:
public synchronized void send(String exchange, String routingKey, Message message, CorrelationData correlationData)
throws AmqpException {
if (correlationData != null) {
//...
super.send(exchange, routingKey, message, correlationData);
}
else {
if (this.scheduledTask != null) {
this.scheduledTask.cancel(false);
}
MessageBatch batch = this.batchingStrategy.addToBatch(exchange, routingKey, message);
if (batch != null) {
super.send(batch.getExchange(), batch.getRoutingKey(), batch.getMessage(), null);
}
//這裡獲取到超時時間,到達超時時間後使用定時器將訊息傳送出去
Date next = this.batchingStrategy.nextRelease();
if (next != null) {
this.scheduledTask = this.scheduler.schedule((Runnable) () -> releaseBatches(), next);
}
}
}
可靠性
使用批處理一定要考慮可靠性的問題。
在消費端,消費者批次拉取一批訊息後把訊息暫存到一個記憶體臨時佇列,然後多執行緒去臨時佇列消費訊息,如果服務當機,臨時佇列中的訊息會丟失。
為了避免當機引發的損失,可以拉取一批訊息後儲存到資料庫,然後給 Broker 返回 ACK,之後業務程式碼去資料庫查詢訊息並消費,不過要考慮資料庫大事務、鎖競爭等問題。
當然,對於一些訊息丟失不敏感的場景,比如日誌收集之類的,可靠性這個指標是不用太關注的。
特殊場景
因為批次訊息有一些複雜性,訊息佇列的部分特性不支援。
事務訊息
批次訊息會增加訊息重試的難度,所以對於事務訊息,建議使用單條訊息,一條訊息對應一個事務。
順序訊息
順序訊息的實現思路一般是生產者將訊息傳送到同一個分割槽,消費者繫結這個分割槽並使用單執行緒消費這個分割槽的訊息。如果對同一個 Topic 下的同一個分割槽來實現批次傳送,難度會增大。所以建議順序訊息使用單條訊息進行傳送。
延時訊息
如果延時訊息使用批次進行傳送,這一批訊息的延時時間必須相同,同時要考慮批次訊息的超時時間,超時時間太大會影響延時時間的準確性,生產端實現複雜度大大增加。
總結
使用批次訊息,在一定程度上可以提高效能和吞吐量,但是確實也會存在一些問題,使用的時候要結合業務場景避開這些坑。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2998585/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 訊息佇列系列一:訊息佇列應用佇列
- 訊息佇列佇列
- RabbitMQ訊息佇列(五):Routing 訊息路由MQ佇列路由
- 訊息機制篇——初識訊息與訊息佇列佇列
- kafka 訊息佇列Kafka佇列
- 訊息佇列(MQ)佇列MQ
- [Redis]訊息佇列Redis佇列
- [訊息佇列]rocketMQ佇列MQ
- [訊息佇列]RabbitMQ佇列MQ
- Kafka訊息佇列Kafka佇列
- RabbitMQ訊息佇列MQ佇列
- 沒用過訊息佇列?一文帶你體驗RabbitMQ收發訊息佇列MQ
- 全面理解Handler-1:理解訊息佇列,手寫訊息佇列佇列
- rabbitmq訊息佇列原理MQ佇列
- 訊息佇列之 RocketMQ佇列MQ
- 訊息佇列二三事佇列
- MQ訊息佇列_RabbitMQMQ佇列
- 訊息佇列設計佇列
- 訊息佇列深入解析佇列
- 訊息佇列之 ActiveMQ佇列MQ
- 訊息佇列之RocketMQ佇列MQ
- 訊息佇列雜談佇列
- 訊息佇列之RabbitMQ佇列MQ
- 訊息佇列簡史佇列
- RabbitMQ 訊息佇列之佇列模型MQ佇列模型
- 程式間通訊--訊息佇列佇列
- 兩個專案用訊息佇列通訊佇列
- RabbitMQ訊息佇列(六):使用主題進行訊息分發MQ佇列
- 如何設計一個訊息佇列?佇列
- 訊息佇列常見的 5 個應用場景佇列
- 訊息佇列常見的5個應用場景佇列
- 訊息佇列——數十萬級訊息的消費方案佇列
- 什麼是訊息佇列?佇列
- 訊息佇列mq總結佇列MQ
- Python使用RocketMQ(訊息佇列)PythonMQ佇列
- SpringBoot:初探 RabbitMQ 訊息佇列Spring BootMQ佇列
- 使用Redis做訊息佇列Redis佇列
- PHP Kafka 訊息佇列使用PHPKafka佇列