消息隊列是系統設計中存在時間最長的中間件之一,從系統有通信需求開始,就產生了消息隊列。
消息隊列的使用場景
在日常系統設計與實現的過程中,下面3種場景會涉及到消息隊列:
- 異步處理
- 流量控制
- 服務解耦
異步處理
典型的應用場景是秒殺系統,它要解決的核心問題是如何利用有限的服務器資源,儘可能多的處理短時間內的海量請求。
一般秒殺系統會包含下面的步驟:
- 風險控制
- 庫存鎖定
- 生成訂單
- 短信通知
- 更新統計數據
在沒有引入消息隊列時,上述5個步驟會依次執行,在引入消息隊列後,只要風險控制和庫存鎖定完成,那麼就可以返回用戶秒殺成功,後面的步驟和秒殺沒有直接關聯,我們可以在庫存鎖定完成後,向消息隊列中發送用戶和商品信息,之後的子系統會讀取消息隊列的消息,進行後續處理。
這秒殺場景中引入消息隊列,帶來兩個好處:
- 可以更快的返回結果。
- 減少等待,實現步驟之間並行處理,提升性能。
流量控制
一個設計健壯的程序有自我保護能力,在海量請求下可以在自身能力範圍內儘可能多的處理請求,拒絕處理不了的請求並且保證自身運行正常。
還是以秒殺場景爲例,我們可以進一步調整設計思路,使用消息隊列隔離網關和後端服務:
- 網關收到請求後,將請求放入到請求消息隊列。
- 後端服務從請求消息隊列中獲取APP請求,完成後續秒殺處理過程,然後返回結果。
這樣當海量請求到來時,不會直接衝擊到後端的秒殺服務,而是堆積在消息隊列中,後端服務按照自己的最大處理能力,從消息隊列中消費請求進行處理。對於超時的請求可以直接丟棄,APP可以將其視爲秒殺失敗。
這其實就是流量控制中的“漏桶流量控制”,網關在這裏充當了”漏桶“的角色。
這樣的設計可以根據下游的處理能力自動調節流量,達到”削峯填谷“的目的,但是有兩個問題:
- 增加了系統調用鏈環節,導致總體響應時間變長。
- 上下游系統都要將同步調用改爲異步調用,增加了系統複雜度。
我們還可以使用“令牌桶”來控制流量。它的原理是:單位時間內只發放固定數量的令牌到令牌桶中,規定服務在處理請求前必須先從令牌桶中拿出一個令牌,如果令牌桶中沒有令牌,則拒絕請求。這樣就保證在單位時間內,能處理的請求不超過發放令牌的數量。
令牌桶可以簡單地用一個有固定容量的消息隊列+一個“令牌發生器”來實現:令牌發生器按照預估的處理能力,勻速生產令牌並放入令牌隊列(如果隊列滿了就丟棄令牌),網關在收到請求時到令牌隊列消費一個令牌,獲取到令牌則繼續調用後端服務,如果獲取不到則等待或者返回失敗。
服務解耦
消息隊列還可以起到服務解耦的作用,例如訂單是電商系統中比較核心的數據,當創建一個新訂單時:
- 支付系統需要發起支付流程
- 風控系統需要審覈訂單的合法性
- 客服系統需要給用戶發送短信
- 經營分析系統需要更新統計數據
- 。。。。。
這些訂單下游的系統都需要實時獲取訂單數據,隨着業務發展,這些訂單下游系統會不斷增加,不斷變化,並且每個系統可能只需要訂單數據的一個子集,負責訂單服務的開發團隊不得不花費很大精力,應付不斷增加變化的下游系統,不停修改調試訂單系統與這些下游系統的接口。任何一個下游系統接口變更,都需要訂單模塊重新上線,這是不可接受的。
問題的解決辦法是所有的下游系統可以通過消息隊列來和訂單系統通信,引入消息隊列後,訂單服務在訂單變化時發送一條消息到消息隊列,所有訂閱相關主題的下游系統都可以實時獲得一份完整的訂單數據。
這樣無論下游系統如何變化,訂單服務都無需做任何更改,實現了訂單服務和下游服務的解耦。
除了上述三種典型場景,消息隊列還可以應用到:
- 作爲發佈/訂閱系統實現一個微服務級系統間的觀察者模式
- 連接流計算任務和數據
- 用於將消息廣播給大量接收者
消息隊列並不是“銀彈”,下面的場景就不適合用消息隊列:
- 實時響應要求高
- 數據強一致性要求高
- 不能容忍數據或者消息丟失
在引入消息隊列後,也可能帶來一些新的問題,包括:
- 異步通信帶來的延遲問題
- 增加了系統設計的複雜度
- 可能會產生數據不一致的問題