一:爲什麼使用MQ
中小公司系統一開始體量較小,後來隨着用戶量逐漸擴大,需要把很多業務進行拆分,因此引入消息隊列來優化問題。
其通用的使用場景可以簡單地描述爲:
當不需要立即獲得結果,但是併發量又需要進行控制的時候,就是需要消息隊列的時候。
二:消息隊列應用的幾大場景:
解耦
多應用間通過消息隊列對同一消息進行處理,避免調用接口失敗導致整個過程失敗。
具體場景:用戶註冊,系統發送郵件並驗證短信。
一般情況下,串行方式,並行方式均可。
或使用消息隊列,註冊信息寫入到消息隊列。
或電商交易系統,交易服務發送消息,要生成訂單,要扣減庫存,要物流發貨。
異步
多應用對消息隊列中同一消息進行處理,應用間併發處理消息,相比串行處理,減少處理時間。
具體場景:人臉識別系統。用戶上傳圖片,調用人臉識別,調用完成後再返回信息。該方法的缺點是:1 人臉識別失敗導致圖片上傳失敗;2 延遲高,需要系統處理完成後再返回客戶端,即使用戶不需要立即知道結果;3 圖片上傳系統和人臉識別系統互相調用,耦合度高。
客戶端上傳圖片後,圖片上傳系統將圖片信息如uid, 批次寫入mq, 直接返回成功。人臉識別系統定時從mq 中取數據,完成對新增圖片的識別。
此時,圖片上傳系統不需要關心人臉識別系統是否對這些圖片信息的處理,以及何時對這些圖片的處理。事實上,由於用戶並不需要立即知道人臉識別的結果,人臉識別系統可以選擇不同的調度策略,按忙時、閒時、正常時間對隊列中的圖片信息進行處理。
消峯
廣泛應用於秒殺或搶購活動,避免流量過大導致系統掛掉。
具體場景:秒殺活動一般瞬時訪問量過大,服務器接收過大,會導致流量暴增,相關係統無法處理請求甚至崩潰。而加入mq 後,系統可以從mq 中取數據,相當於mq 做了一次緩衝。
優點:
- 1 請求先入mq, 而不是由業務系統直接處理,做了一次緩衝,極大減少業務處理系統的壓力。
- 2 隊列長度可以做限制,事實上,秒殺時,後入隊列的用戶無法秒殺到商品,這些請求可以直接被拋棄,返回活動已結束或商品已售完。
消息驅動的系統
系統分爲消息隊列、消息生產者、消息消費者,生產者負責產生消息,多個消費者負責對消息進行處理。
具體場景:用戶新上傳一批照片,人臉識別系統需要對這個用戶的所有照片進行聚類,聚類完成後由對帳系統重新生成用戶的人臉索引加快查詢。這三個子系統間由mq 連接起來,前一個階段處理結果放入隊列中,後一個階段從隊列中獲取消息繼續處理。
優點:
- 避免了直接調用下一個系統導致當前系統失效
- 每個子系統對於消息的處理方式可以更靈活,可以選擇接收到消息 時就處理,可以選擇定時處理,也可以劃分時間段按不同處理速度處理。
三:MQ 存在問題及場景優化
3.1 重複消費問題
問題出現的場景:
- 消息生產者產生了重複的消息
- kafka 和 RocketMQ 的 offset 被回調了
- 消息消費者確認失敗
- 消息消費者確認超時
- 業務系統主動發起重試
解決方案
不管是生產者產生的重複消息,還是消費者導致的重複消息,我們都可以在消費者中解決這個問題。這就需要我們做冪等設計。
增加一張消費消息表,使用messageId 做唯一索引。在處理邏輯前,先根據messageId 查詢一下該消息有沒有處理過,如果已經處理過則直接返回,如果沒有處理過,則繼續做業務處理
3.2 數據一致性問題(異步分佈式事務問題)
問題出現的場景:
當服務是同步調用的時候,我們可以使用本地事務來控制數據一致性。但異步調用會存在數據不一致的問題。如商品訂單下單成功,但是扣減庫存失敗,就是造成超賣問題。
解決方案
數據一致性分爲:強一致性,弱一致性,最終一致性。
mq 爲了性能使用的是最終一致性,那麼必定會出現數據不一致的問題。這類問題大概率是因爲消費者讀取消息後,業務邏輯處理失敗導致的。這個時候可以增加重試機制。
重試可以分爲同步重試和異步重試。消息量比較小的業務場景,可以採用同步重試,如果消息處理失敗立即重試3-5次,如果還失敗則寫入記錄表中。如果消息量比較大,則採用異步方式。在處理失敗後立即寫入重試表,有個job 專門重定時重試。
還有一種做法,如果消費失敗,自己給同一個topic 發一條消息,在後面的某個時間點自己又會消費到那一條消息,起到了重試的效果。如果對消息順序要求不高的場景。這種做法適合對消費順序要求不高的場景。
需要事務強一致的,不用消息異步,如下單、減庫存要放在一個事務時在,加積分等業務放在mq 中異步處理。
3.3 消息丟失問題
問題出現的場景:
- 生產者產生消息時,由於網絡原因發送到mq 失敗
- mq 服務器持久化,存儲磁盤時出現異常
- kafka 和 rocketMQ 的offset 被回調時,略過了很多消息
- 消費者剛讀取消息,已經ack 確認,但是業務還沒處理完,服務被重啓了。
生產者、消費者、服務器都可能產生問題,最終的結果會導致消費者無法正確的處理消息而導致數據不一致的情況。
解決方案
增加一張消息發送表。
- 當生產者發完消息後,會往該表中寫入一條數據,狀態status 標記爲待確認
- 如果消費者讀取消息後,調用生產者api 更新該消息status 爲已確認
- 有個job 每隔一段時間檢查一次消息發送表,如果執行後還有狀態是待確認的消息,則認爲該消息丟失,重發該消息。
3.4 消息順序問題
問題出現的場景:
如商品訂單-支付-完成-退貨等狀態,如果訂單數據作爲 消息體,就會涉及順序問題。如果先支付後下單,是不可以的。
這樣的問題場景有很多,比如:
- kafka 同一個partition 中能保證順序,但是不同的partition 無法保證順序
- rabbitMQ 的同一個queue 能夠保證順序,但是如果多個消費者同一個queue 也會有順序問題
- 如果消費者使用多線程消費消息,無法保證順序
- 如果生產者發送到mq 中的路由規則跟消費者不一樣,也無法保證順序
解決方案
首先我們需要確認,消費者是否真的需要知道中間狀態,只知道最終狀態行不行。
其實很多時候我們真的需要知道的最終狀態,這樣我們可以把流程優化一下:
這種流程可以解決大部分的消息順序問題。但是如果真的有需要保證順序的需求,可以將訂單號路由到不同的partition,同一個訂單號的消息,每次發送到同一個partition
3.5 消息堆積問題
問題出現的場景:
很多時候,由於某些批處理或其他原因,導致消費速度小於生產速度,這樣會直接導致消息堆積問題,從而影響業務功能。
比如開通會員,如果消息出現堆積會導致用戶下單後,很久才變會員。這種情況肯定會引起大量用戶投訴。
解決方案
看消息是否需要保證順序。如果不需要保證順序,可以讀取消息之後用多線程處理業務邏輯。
這樣就能增加業務邏輯處理速度,解決消息堆積問題。但是線程池的核心線程數和最大線程數需要合理配置,不然可能會浪費系統資源。
如果需要保證順序,可以讀取消息後將消息按照一定的規則分發到多個隊列中,然後在隊列中用單線程處理。
rabbitmq ,activemq , rocketmq, kafka 對比
引入mq 之後的問題
本來我們需要保證系統服務可用就可以了,現在引入新的服務,我們要保證mq 的可用,系統的可用性和複雜性都會有新的要求。
文::一隻阿木木