前景介紹:
在看這篇文章之前我先說說幾個我在RocketMq使用中總結的問題,如有錯誤,請聯系我洗掉謝謝,
-
訊息的發送失敗和消費失敗大多數都是網路原因和服務器記憶體問題,
-
但是消費者消費訊息失敗除了以上原因外,還關系到我們在消費的方法中是否會產生例外,一旦產生例外,消費者就會給broker回傳ack為0的應答,那么這個訊息就會存在broker里面不會被剔除,并且會觸發訊息的重試機制,只到回傳ack為1的應答,
知道上面這個知識后我們來思考一個問題,消費者消費失敗后觸發重試機制,這樣就產生了重復消費問題,因此,使用MQ時應該對一些關鍵訊息進行冪等去重的處理,那,如何進行訊息的冪等性處理呢?
? 博主最近遇到了一個業務流程,上圖:

其實有很多業務流程一樣的,比如支付成功后發送訊息更改 “某些資料“ 的狀態,一旦不做冪等性處理就會出現實際已付款,但是c端顯示已識訓,,,
1.為什么會出現訊息被重復消費?
在解決訊息的冪等性之前我們需要知道為什么訊息會被重復消費?
我們通常會認為,訊息中間件是一個可靠的組件,這里所謂的可靠是指,只要我把訊息成功投遞到了訊息中間件,訊息就不會丟,即訊息至少會被消費者成功消費一次,這是訊息中間件最基本的特性之一,
然而這種可靠的特性會導致訊息可能被多次的投遞,
看幾個可能出現的場景:
- 優惠券微服務 接受到這個訊息 M 并完成消費邏輯之后,正想通知訊息中間件“我已經消費成功了”的時候,程式就宕機了,那么對于訊息中間件來說,這個訊息并沒有成功消費過,所以它還會繼續投遞,這時候對于優惠券微服務 來說,看起來就是這個訊息明明消費成功了,但是訊息中間件還在重復投遞,這在 RockectMQ 的場景來看,就是同一個 messageId 的訊息重復投遞下來了,那這個訊息不久被重復的消費了嗎?
- 我們一般在用戶呼叫支付介面成功后才會修改訂單狀態并發送訊息給優惠券系統,但是做過微信支付的人都知道,當用戶支付成功后,微信后臺會呼叫我們專案暴露的介面,這個時候你們絕對能想到網路的波動,微信后臺由于網路波動一直調我們的介面,那這個訊息不就被重復的發送了嗎?
- 還有一種可能:采用同步訊息發送,當網路出現波動,雖然訊息已經發送到MQ了,當時MQ并沒有把成功的回應給生產者,導致訊息發送失敗,實際已經發送出去了,但是生產者為了保證訊息真的被發出去,會再發送一次訊息,那這個訊息不就被重復的發送了嗎?
總結:經過我們的分析,我們發現真正的專案開發不像我們自己做練習專案那樣,而是一定要考慮訊息的冪等性問題,特別是涉及到錢相關的問題,
2.如何解決訊息冪等性問題?
說了這么多問題可能產生的原因,那真正的如何解決呢?
在我遇到冪等性問題之后,我查詢了多方資料,有了一定的了解,這次的隨筆我不用代碼說明,只有口頭語言,也可能代表我的猜測,下次有時間我會出一個代碼demo測驗,最近有點忙哈,哈哈,
-
引入redis實作冪等性問題,但是注意這種方式不是100%成功,
-
實作思路
-
@Component @Slf4j @RocketMQMessageListener( topic = "order", consumerGroup = "order-group" ) public class OrderConsumerListener implements RocketMQListener<MessageExt> { @Autowired private StringRedisTemplate redisTemplate; @Override @Transactional public void onMessage(MessageExt messageExt) { //"key"搞一個訊息的唯一標識,根據自身業務系統來設計 String msgId = redisTemplate.boundValueOps("key").get(); if(!StringUtils.isEmpty(msgId)) { return; } else { redisTemplate.boundValueOps("key").set("1",10L, TimeUnit.MINUTES); //處理消費訊息的業務 } } } -
仔細看我們發現這是一個執行緒不安全的,不具備原子性,這個是個風險,還有就是只能防止10分鐘,如果10分鐘之內消費不成功,還是會出現冪等問題,而且還可能發生極端情況:redis宕機了怎么辦,但是不能否認這種方式可以加長key的過期時間,因為rocketmq里面訊息的重試是有次數和時間間隔的,我們可以手動配置,注意:這里我們保證的訊息冪等,不保證訊息一定要消費成功,默認是失敗了16次之后就進入死信佇列,
-
-
看了這么久是不是發現還沒有很完美的解決辦法,其實博主目前接手的專案就是用redis處理的,因為有贊這邊的服務器都比較好,頂多介面請求超時才可能發生,所以給10分鐘的時間緩沖,,,所以說這種事情確實要根據自身硬體和業務系統的需求來完成,,,,但是最近還是會有一些問題,tl給出的解決方案是在myql層結合業務處理(同一條記錄已經修改了的不能重復改,已經有記錄的不能插,就類似是這種意思),
其實最完美的辦法應該是在資料庫業務層進行處理,即資料庫樂觀鎖,
由于時間有限,我不做說明,
什么是資料庫樂觀鎖和悲觀鎖可以看下面參考的鏈接進行學習,mybatis-plus里面實作了樂觀鎖,樂觀鎖一般是基于update操作的,
https://www.cnblogs.com/kyoner/p/11318979.html
那insert操作如何處理呢?
其實insert操作我們可以把訊息的唯一主鍵當成要插入記錄的主鍵,這樣就會報主鍵例外,在資料庫層面解決了問題,但是也會在重試16次之后進入死信佇列,
最后,這次時間太趕,沒有更好的說明解決方案,也沒有給出代碼測驗,后面如果遇到類似的問題我會找時間完善這篇文章的,謝謝大家的閱讀,一入java深似海,越學越菜~~~~,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/300443.html
標籤:其他
