冪等性
概念
在應用程序中,冪等性就是指對一個系統進行重復調用(相同參數),不論請求多少次,這些請求對系統的影響都是相同的效果.
比如數據庫的select操作.不同時間兩次查詢的結果可能不同,但是這個操作是符合冪等性的.冪等性指的是對資源的影響,而不是返回結果,查詢操作對數據資源本身不會產生影響,之所以結果不同,可能是因為兩次查詢之間有其他操作對資源進行了修改.
比如i++這個操作,就是非冪等性的,如果調用方沒有控制好邏輯,一次流程重復調用好幾次,結果
就會不同。
MQ的冪等性介紹
對于MQ而言,冪等性是指同一條消息,多次消費,對系統的影響是相同的
一般消息中間件的消息傳輸保障分為三個層級。
1.At most once:最多一次.消息可能會丟失,但絕不會重復傳輸.
2.Atleastonce:最少一次.消息絕不會丟失,但可能會重復傳輸。
3.Exactlyonce:恰好一次.每條消息肯定會被傳輸一次且僅傳輸一次.
RabbitMQ支持"最多一次"和"最少一次".
對于"恰好一次",目前RabbitMQ還做不到,不僅是RabbitMQ,目前市面上主流的消息中間件,都做不到這一點
在業務使用中,對于可靠性要求比較高的場景,建議使用”最少一次",以防止消息丟失,“最多一次"會因為消息發送過程中,網絡問題,消費出現異常等種種原因,導致消息丟失
以下場景可能會導致消息發送重復(包含但不限于)
發送時消息重復:當一條消息已被成功發送到服務端并完成持久化,此時出現了網絡閃斷或者客戶
端宕機,導致服務端對客戶端應答失敗,如果此時Producer意識到消息發送失敗并嘗試再次發送消
息,Consumer后續會收到兩條內容相同并且MessageID也相同的消息.
投遞時消息重復:消息消費的場景下,消息已投遞到consumer并完成業務處理,當客戶端給服務端反饋應答的時候網絡閃斷,為了保證消息至少被消費一次,云消息隊列RabbitMQ版的服務端將在網絡恢復后再次嘗試投遞之前已被處理過的消息,Consumer后續會收到兩條內容相同并且Message ID也相同的消息.
解決方案
為了避免同一消息重復消費的話,我們可以通過以下方案來解決:
全局唯一 ID 設置:
每一個消息都有唯一的 ID 進行標識,我們可以使用 redis 緩存過已經消費過的消息ID,當消費的時候先查閱 redis 是否存在這個 ID ,如果不存在則進行消費,消費成功后將消息ID 存儲到 Reids 中,如果存在則不消費這條消息
業務邏輯判斷:
例如:通過檢查數據庫中是否已存在相關數據記錄,或者使用樂觀鎖機制來避免更新已被其他事務更改的數據,再或者在處理消息之前,先檢查相關業務的狀態,確保消息對應的操作尚未執行,然后才進行處理,具體根據業務場景來處理
順序性保障
消息的順序性是指消費者消費的消息和生產者發送消息的順序是一致的。
比如生產者發送的消息分別是msgl,msg2,msg3,那么消費者也是按照msgl,msg2,msg3的順序進行消費的.
很多業務場景下,消息的消費是不用保證順序的,比如使用MQ實現訂單超時的處理,但有些業務場景,可能存在多個消息順序處理的情況,比如用戶信息修改,對同一個用戶的同一個資料進行修改,需要保證消息的順序。
一些資料顯示RabbitMQ的消息能夠保障順序性,這是不嚴謹的,在不考慮消息丟失,網絡故障等異常的
情況下,如果只有一個消費者,最好也只有一個生產者的情況下,是可以保證消息的順序性,如果有多個
生產者同時發送消息,無法確定消息到達RabbitMQBroker的前后順序,也就無法驗證消息的順序性.
哪些情況可能會打破RabbitMQ的順序性呢?下面介紹幾種常見的場景:
1.多個消費者:當隊列配置了多個消費者時,消息可能會被不同的消費者并行處理,從而導致消息處理
的順序性無法保證
2.網絡波動或異常:在消息傳遞過程中,如果出現網絡波動或異常,可能會導致消息確認(ACK)丟失,從
而使得消息被重新入隊和重新消費,造成順序性問題。
3.消息重試:如果消費者在處理消息后未能及時發送確認,或者確認消息在傳輸過程中丟失,那么MQ
可能會認為消息未被成功消費而進行重試,這也可能導致消息處理的順序性問題
4.消息路由問題:在復雜的路由場景中,消息可能會根據路由鍵被發送到不同的隊列,從而無法保證全
局的順序性。
5.死信隊列:消息因為某些原因(如消費端拒絕消息)被放入死信隊列,死信隊列被消費時,無法保證消息
的順序和生產者發送消息的順序一致
包括但不僅限于以上幾種情形會使RabbitMQ消息錯序,如果要保證消息的順序性,需要業務方使用
RabbitMQ之后做進一步的處理
實現方案
消息順序性保障分為:局部順序性保證和全局順序性保證
局部順序性通常指的是在單個隊列內部保證消息的順序,全局順序性是指在多個隊列或多個消費者之間保證消息的順序。
在實際應用中,全局順序性很難實現,可以考慮使用業務邏輯來保證順序性,比如在消息中嵌入序列號,
并在消費端進行排序處理,相對而言,局部順序性更常見,也更容易實現
RabbitMQ作為一個分布式消息隊列,主要優化的是吞吐量和可用性,而不是嚴格的順序性保證,如果業
務場景確實需要嚴格的消息順序,可能需要在應用層面進行額外的設計和實現
實現策略:
1.單隊列單消費者
最簡單的方法是使用單個隊列,并由單個消費者進行處理,同一個隊列中的消息是先進先出的,這是
RabbitMQ來幫助我們保證的.
2.分區消費
單個消費者的吞吐太低了,當需要多個消費者以提高處理速度時,可以使用分區消費,把一個隊列分割成
多個分區,每個分區由一個消費者處理,以此來保持每個分區內消息的順序性
RabbitMQ本身并不支持分區消費,需要業務邏輯去實現,或者借助spring-cloud-stream來實現,官方文檔:https://docs.spring.io/spring-cloud-stream/reference/rabbit/rabbit_partitions.html
3.業務邏輯實現
消息積壓
消息積壓是指在消息隊列(如RabbitMQ)中,待處理的消息數量超過了消費者處理能力,導致消息在隊列
中不斷堆積的現象.
通常有以下幾種原因:
1.消息生產過快:在高流量或者高負載的情況下,生產者以極高的速率發送消息,超過了消費者的處理能力。
2.消費者處理能力不足:消費者處理處理消息的速度跟不上消息生產的速度,也會導致消息在隊列中積壓.
可能原因有:
1)消費端業務邏輯復雜,耗時長
2)消費端代碼性能低
3)系統資源限制,如CPU、內存、磁盤I/O等也會限制消費者處理消息的效率。
4)異常處理處理不當,消費者在處理消息時出現異常,導致消息無法被正確處理和確認
3.網絡問題:因為網絡延遲或不穩定,消費者無法及時接收或確認消息,最終導致消息積壓
4.RabbitMQ服務器配置偏低
消息積壓可能會導致系統性能下降,影響用戶體驗,甚至導致系統崩潰,因此,及時發現消息積壓并解決
對于維護系統穩定性至關重要。
解決方案
遇到消息積壓時,首先要分析消息積壓造成的原因,根據原因來調整策略。
主要從以下幾個方面來解決:
-
提高消費者效率
a. 增加消費者實例數量,比如新增機器
b. 優化業務邏輯,比如使用多線程來處理業務
c. 設置prefetchCount,當一個消費者阻塞時,消息轉發到其他未阻塞的消費者.
d. 消息發生異常時,設置合適的重試策略,或者轉入到死信隊列 -
限制生產者速率,比如流量控制,限流算法等。
a. 流量控制:在消息生產者中實現流量控制邏輯,根據消費者處理能力動態調整發送速率
b. 限流:使用限流工具,為消息發送速率設置一個上限
c. 設置過期時間,如果消息過期未消費,可以配置死信隊列,以避免消息丟失,并減少對主隊列的壓力 -
資源與配置優化.比如升級RabbitMQ服務器的硬件,調整RabbitMQ的配置參數等