metaq最佳實踐
1. 前言
本文件旨在描述RocketMQ使用過程中的一些最佳實踐,建議使用者這樣做,但是非必須。
2. Producer最佳實踐
2.1 傳送訊息注意事項
-
一個應用盡可能用一個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不可用, 訊息已經進入伺服器佇列,只有此時伺服器當機,訊息才會丟失
對於精衛傳送順序訊息的應用,由於順序訊息的侷限性,可能會涉及到主備自動切換問題,所以如果sendresult中的status欄位不等於SEND_OK,就應該嘗試重試。對於其他應用,則沒有必要這樣。
- 對於訊息不可丟失應用,務必要有訊息重發機制
例如如果訊息傳送失敗,儲存到資料庫,能有定時程式嘗試重發,或者人工觸發重發。
2.2訊息傳送失敗如何處理
Producer的send方法本身支援內部重試,重試邏輯如下:
- 至多重試3次。
- 如果傳送失敗,則輪轉到下一個Broker。
- 這個方法的總耗時時間不超過sendMsgTimeout設定的值,預設10s。所以,如果本身向broker傳送訊息產生超時異常,就不會再做重試。
以上策略仍然不能保證訊息一定傳送成功,為保證訊息一定成功,建議應用這樣做如果呼叫send同步方法傳送失敗, 則嘗試將訊息儲存到db,由後臺執行緒定時重試,保證訊息一定到達Broker。
上述db重試方式為什麼沒有整合到MQ客戶端內部做,而是要求應用自己去完成,我們基於以下幾點考慮
- MQ的客戶端設計為無狀態模式,方便任意的水平擴充套件,且對機器資源的消耗僅僅是cpu、記憶體、網路。
- 如果MQ客戶端內部整合一個KV儲存模組,那麼資料只有同步落盤才能較可靠,而同步落盤本身效能開銷較大,所以通常會採用非同步落盤,又由於應用關閉過程不受MQ運維人員控制,可能經常會發生kill -9這樣暴力方式關閉,造成資料沒有及時落盤而丟失。
- Producer所在機器的可靠性較低,一般為虛擬機器,不適合儲存重要資料。
綜上,建議重試過程交由應用來控制。
2.3選擇oneway形式傳送
一個RPC呼叫,通常是這樣一個過程
- 客戶端傳送請求到伺服器
- 伺服器處理該請求
- 伺服器向客戶端返回應答所以一個RPC的耗時時間是上述三個步驟的總和,而某些場景要求耗時非常短,但是對可靠性要求並不高,例如日誌收集類應用,此類應用可以採用oneway形式呼叫,oneway形式只傳送請求不等待應答,而傳送請求在客戶端實現層面僅僅是一個os系統呼叫的開銷,即將資料寫入客戶端的socket緩衝區,此過程耗時通常在微秒級。
3 Consumer最佳實踐
3.1消費過程要做到冪等(即消費端去重)
如《RocketMQ 原理簡介》中所述,RocketMQ無法避免訊息重複,所以如果業務對消費重複非常敏感,務必要在業務層面去重,有以下幾種去重方式
- 將訊息的唯一鍵,可以是msgId,也可以是訊息內容中的唯一標識欄位,例如訂單Id等,消費之前判斷是否在Db或Tair(全域性KV儲存)中存在,如果不存在則插入,並消費,否則跳過。(實際過程要考慮原子性問題,判斷是否存在可以嘗試插入,如果報主鍵衝突,則插入失敗,直接跳過)
msgId一定是全域性唯一識別符號,但是可能會存在同樣的訊息有兩個不同msgId的情況(有多種原因),這種情況可能會使業務上重複消費,建議最好使用訊息內容中的唯一標識欄位去重。 - 使用業務層面的狀態機去重
3.3消費速度慢處理方式
3.3.1提高消費並行度
絕大部分訊息消費行為屬於IO密集型,即可能是運算元據庫,或者呼叫RPC,這類消費行為的消費速度在於後端資料庫或者外系統的吞吐量,通過增加消費並行度,可以提高總的消費吞吐量,但是並行度增加到一定程度,反而會下降,如圖所示,呈現拋物線形式。所以應用必須要設定合理的並行度。CPU密集型應用除外。
3.3.2批量方式消費
某些業務流程如果支援批量方式消費,則可以很大程度上提高消費吞吐量,例如訂單扣款類應用,一次處理一個訂單耗時1秒鐘,一次處理10個訂單可能也只耗時2秒鐘,這樣即可大幅度提高消費的吞吐量,通過設定consumer的consumeMessageBatchMaxSize這個引數,預設是1,即一次只消費一條訊息,例如設定為N,那麼每次消費的訊息數小於等於N。
3.3.3跳過非重要訊息
發生訊息堆積時,如果消費速度一直追不上傳送速度,可以選擇丟棄不重要的訊息
如何判斷消費發生了堆積?
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
longoffset = msgs.get(0).getQueueOffset();
String maxOffset = msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET);
longdiff = Long.parseLong(maxOffset) - offset;
if (diff > 100000) {
// TODO訊息堆積情況的特殊處理
returnConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
// TODO正常消費過程
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
如以上程式碼所示,當某個佇列的訊息數堆積到100000條以上,則嘗試丟棄部分或全部訊息,這樣就可以快速追上傳送訊息的速度。
3.3.4優化每條訊息消費過程
舉例如下,某條訊息的消費過程如下
1.根據訊息從DB查詢資料1
2.根據訊息從DB查詢資料2
3.複雜的業務計算
4.向DB插入資料3
5.向DB插入資料4
這條訊息的消費過程與DB互動了4次,如果按照每次5ms計算,那麼總共耗時20ms,假設業務計算耗時5ms,那麼總過耗時25ms,如果能把4次DB互動優化為2次,那麼總耗時就可以優化到15ms,也就是說總體效能提高了40%。
對於Mysql等DB,如果部署在磁碟,那麼與DB進行互動,如果資料沒有命中cache,每次互動的RT會直線上升,如果採用SSD,則RT上升趨勢要明顯好於磁碟。個別應用可能會遇到這種情況:線上下壓測消費過程中,db表現非常好,每次RT都很短,但是上線執行一段時間,RT就會變長,消費吞吐量直線下降。
主要原因是線下壓測時間過短,線上執行一段時間後,cache命中率下降,那麼RT就會增加。建議線上下壓測時,要測試足夠長時間,儘可能模擬線上環境,壓測過程中,資料的分佈也很重要,資料不同,可能cache的命中率也會完全不同。3.4消費列印日誌如果訊息量較少,建議在消費入口方法列印訊息,方便後續排查問題。
3.4 消費列印日誌
如果訊息量較少,建議在消費入口方法列印訊息,方便後續排查問題。
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
log.info("RECEIVE_MSG_BEGIN: " + msgs.toString());
// TODO正常消費過程
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}
}
如果能列印每條訊息消費耗時,那麼在排查消費慢等線上問題時,會更方便。
相關文章
- metaq雜記
- AutoMapper 最佳實踐APP
- 《.NET最佳實踐》
- Django 最佳實踐Django
- springDataJpa 最佳實踐Spring
- KeyPath 最佳實踐
- Pika最佳實踐
- JavaScript 最佳實踐JavaScript
- SnapKit 最佳實踐APK
- JDBC 最佳實踐JDBC
- Kafka最佳實踐Kafka
- Iptables 最佳實踐 !
- Serilog 最佳實踐
- Flutter 最佳實踐Flutter
- Java最佳實踐Java
- MongoDB 最佳實踐MongoDB
- Gradle最佳實踐Gradle
- metaq原理簡介
- 【譯】VueJS 最佳實踐VueJS
- App瘦身最佳實踐APP
- Android MVP 最佳實踐AndroidMVP
- OpenResty 最佳實踐 (1)REST
- Android SharedPreferences最佳實踐Android
- mysqldump的最佳實踐MySql
- [筆記]最佳實踐筆記
- OpenResty 最佳實踐 (2)REST
- RESTful API 最佳實踐RESTAPI
- HTTPS安全最佳實踐HTTP
- Go HttpServer 最佳實踐GoHTTPServer
- Android Emoji 最佳實踐Android
- Rocketmq原理&最佳實踐MQ
- restful api最佳實踐RESTAPI
- Dockerfile 安全最佳實踐Docker
- MongoDB最佳安全實踐MongoDB
- RocketMQ的最佳實踐MQ
- Java null最佳實踐JavaNull
- 冪等最佳實踐
- Kubernetes Deployment 最佳實踐