可重入鎖思想,設計MQ遷移方案

如果你的MQ消息要從Kafka切換到RocketMQ且不停機,怎么做?在讓這個MQ消息調用第三方發獎接口,但無冪等字段又怎么處理?今天小傅哥就給大家分享一個關于MQ消息在這樣的場景中的處理手段。
這是一種比較特例的場景,需要保證切換的MQ消息不被兩端同時消費,并且還需要在一段消費失敗后的MQ還可以繼續重試。并且這一端消費的MQ消息,也要保證自身的冪等。
我們知道一般通用場景下,MQ消息都會有一個業務唯一ID值,用于接收方做仿重處理。但除此之外還應該有一個MQ消息本身的ID,這個ID也要全局唯一,每一條消息都要有一個ID,這是因為MQ是可能重復發送的(發送MQ成功,但獲取MQ發送結果響應超時或更新庫表消息狀態失敗,則重復發送),如果沒有消息的唯一ID也就沒法確保是哪一條消息了。
這個ID可以用于;唯一標識、去重、鏈路追蹤、冪等性、事務以及安裝性等,但可能有些伙伴在做MQ消息發送的時候,是容易忽略而沒有在MQ中添加這個ID,或者隨意用時間戳來當ID用,這樣都是不合理的。會影響一些場景的代碼健壯性設計。
需求背景描述好了,接下來,我們看看這樣的場景怎么設計。
1. 場景問題
將原本使用 Kafka 的MQ方式,遷移到 RocketMQ,同時部分場景的 MQ 消息調用三方接口是沒有冪等字段的,需要做好程序兼容處理。
2. 場景思考
首先我們要知道在分布式架構下,我們每做的技術方案都要考慮順序性和臨界狀態。像是MQ的生產和消費都是多套應用實例部署的,那么生產端發送出來的MQ消息到不同的隊列中也是有延遲和存放順序以及拉取消費不同的情況。如;生產端發送MQ為A、B、C、D,但到Kafka/RocketMQ以及不同的消費端拉取時,不一定是A、B、C、D的順序,那么直接做切量開關,是可能導致一個A消息在Kafka隊列中消費完,點擊切換開關(一種切量哈希計算手段,如消息{A}哈希值最后兩位當做百分比用),正好RocketMQ也會把A消費掉。這樣同一個消息就被重復消費了。
3. 方案設計
在整個方案設計中,我們要考慮幾個非常重要的點。如圖:

-
一個是切換的兩端MQ消費是搶占式加鎖,避免重復消費。這是因為切量開關,切換過程中,兩個消息隊列中的MQ并不是順序可靠的,可能存在重復消費,所以要加分布式鎖。
-
一段MQ消費失敗要進行重試,但這個時候不能在消費失敗后刪分布式鎖,因為MQ消費都是很快的,可能導致刪鎖后另外一端MQ進行了相同的消費。那可能有些伙伴會說,那也沒關系呀,反正失敗的這段沒有消費成功。當往往失敗并不一定是直接的結果失敗,可能是網絡失敗,可能是超時失敗等。也就是實際成功了,但超時反饋了。所以不能被其他端重復消費,并且要保證自己這一端消費失敗后可重試。所以這塊要設計可重入鎖,也就是 setnx 加鎖的值,為自身一段的 mq 類型,這樣自己在接收mq消息以后,檢查鎖為自身加鎖值可重試。這樣也就保證了一端消費重試,不會讓另外一端把MQ也跟著消費掉,因為setnx存在,并且有加鎖值判斷,所以不能進入。
-
另外MQ消息還可能存在同一個MQ發送多次的場景,這個是非常正常的。比如,你再發送MQ的時候,超時網絡抖動失敗(1萬次會有1次),那么就會補償重發。但這個MQ已經發送過了,所以會接收2條MQ消息。那么在消費的時候,不能讓2個MQ消息都進入消費中,因為多臺實例消費,可能都去調用發獎了。那么這里還需要給MQ的ID進行冪等加鎖。確保一個MQ消息,失敗后,順序輪訓重試。也就保證了,發獎的過程中不會出現超發獎品。大部分三方接口還是有冪等字段的,有的話會更好。
-
另外還有2個開關,一個是
消費開關,一個是切量開關。消費開關要在整個新的MQ改造工程工程全部上線后開啟,但還要被切量開關限定消費。開啟后,切量開關才會生效。切量是一種哈希值的百分比比對,比如一個哈希值最后兩位是10,那么切量配置小于等于10%則這個MQ則可以被切量后消費,另外一段則不消費這個MQ。 -
另外,為了方便測試線上功能,還會加入白名單。不過大部分時候這類東西會用通用組件能力解決。
這樣的場景方案設計,是非常值得積累的,同類的思想也可以幫我們解決很多共性問題。

浙公網安備 33010602011771號