最近在業務中開始大量使用RocketMQ,記錄一下心得。
Producer最佳實踐
一、發送消息注意事項
- 一個應用盡可能用一個Topic,消息子類型用tags來標識,tags可以由應用自由設置。只有發送消息設置了tags,消費方在訂閱消息時,才可以利用tags在broker做消息過濾。
message.setTags("TagA");
- 每個消息在業務層面的唯一標識碼,要設置到keys字段,方便將來定位消息丟失問題。服務器會為每個消息創建索引(哈希索引),應用可以通過topic,key來查詢這條消息內容,以及消息被誰消費。由于是哈希索引,請務必保證key盡可能唯一,這樣可以避免潛在的哈希沖突。
// 訂單Id
String orderId = "20034568923546";
message.setKeys(orderId);
- 消息發送成功或者失敗,要打印消息日志,務必要打印sendresult和key字段。
- send消息方法,只要不拋異常,就代表發送成功。但是發送成功會有多個狀態,在sendResult里定義。
- SEND_OK
消息發送成功 - FLUSH_DISK_TIMEOUT
消息發送成功,但是服務器刷盤超時,消息已經進入服務器隊列,只有此時服務器宕機,消息才會丟失 - FLUSH_SLAVE_TIMEOUT
消息發送成功,但是服務器同步到Slave時超時,消息已經進入服務器隊列,只有此時服務器宕機,消息才會丟失 - SLAVE_NOT_AVAILABLE
消息發送成功,但是此時slave不可用,消息已經進入服務器隊列,只有此時服務器宕機,消息才會丟失
- 對于消息不可丟失應用,務必要有消息重發機制
例如如果消息發送失敗,存儲到數據庫,能有定時程序嘗試重發,或者人工觸發重發。
二、消息發送失敗如何處理
Producer的send方法本身支持內部重試,重試邏輯如下:
- 至多重試3次。
- 如果發送失敗,則輪轉到下一個Broker。
- 這個方法的總耗時時間不超過sendMsgTimeout設置的值,默認10s。
所以,如果本身向broker發送消息產生超時異常,就不會再做重試。
以上策略仍然不能保證消息一定發送成功,為保證消息一定成功,建議應用這樣做
如果調用send同步方法發送失敗,則嘗試將消息存儲到db,由后臺線程定時重試,保證消息一定到達Broker。
Consumer最佳實踐
如下:
- 消費過程要做到冪等(即消費端去重);
- 盡量使用批量方式消費方式,可以很大程度上提高消費吞吐量;
- 優化每條消息消費過程。
RocketMQ消費端去重方法
RocketMQ無法避免消息重復,所以如果業務對消費重復非常敏感,務必要在業務層面去重,有以下幾種去重方式:
- 將消息的唯一鍵,可以是msgId,也可以是消息內容中的唯一標識字段,例如訂單Id等,消費之前判斷是否在Db或Tair(全局KV存儲)中存在,如果不存在則插入,并消費,否則跳過。(實際過程要考慮原子性問題,判斷是否存在可以嘗試插入,如果報主鍵沖突,則插入失敗,直接跳過);
- 使用業務層面的狀態機去重
三、其他配置
1、線上應該關閉autoCreateTopicEnable,即在配置文件中將其設置為false。
RocketMQ在發送消息時,會首先獲取路由信息。如果是新的消息,由于MQServer上面還沒有創建對應的Topic,這個時候,如果上面的配置打開的話,會返回默認Topic的(RocketMQ會在每臺broker上面創建名為TBW102的Topic)路由信息,然后Producer會選擇一臺Broker發送消息,選中的broker在存儲消息時,發現消息的Topic還沒有創建,就會自動創建Topic。后果就是:以后所有該Topic的消息,都將發送到這臺broker上,達不到負載均衡的目的。
所以基于目前RocketMQ的設計,建議關閉自動創建Topic的功能,然后根據消息量的大小,手動創建Topic。
2、關于RocketMQ 版本
官方推薦使用RocketMQ 3.4.6及以后版本。
參考資料
RocketMQ 最佳實踐:https://github.com/vintagewang/document/blob/master/rocketmq/RocketMQ%20%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5.docx