消息隊列是最古老的中間件之一,從系統之間有通訊需求開始,就天然產生了消息隊列。可是給消息隊列下一個準確的定義卻不太容易。咱們知道,消息隊列的主要功能就是收發消息,可是它的做用不只僅只是解決應用之間的通訊問題這麼簡單。程序員
咱們舉個例子說明一下消息隊列的做用。話說小袁是一家巧克力做坊的老闆,生產出美味的巧克力須要三道工序:首先將可可豆磨成可可粉,而後將可可粉加熱並加入糖變成巧克力漿,最後將巧克力漿灌入模具,撒上堅果碎,冷卻後就是成品巧克力了。面試
最開始的時候,每次研磨出一桶可可粉後,工人就會把這桶可可粉送到加工巧克力漿的工人手上,而後再回來加工下一桶可可粉。小袁很快就發現,其實工人能夠不用本身運送半成品,因而他在每道工序之間都增長了一組傳送帶,研磨工人只要把研磨好的可可粉放到傳送帶上,就能夠去加工下一桶可可粉了。 傳送帶解決了上下游工序之間的「通訊」問題。後端
傳送帶上線後確實提升了生產效率,但也帶來了新的問題:每道工序的生產速度並不相同。在巧克力漿車間,一桶可可粉傳送過來時,工人可能正在加工上一批可可粉,沒有時間接收。不一樣工序的工人們必須協調好什麼時間往傳送帶上放置半成品,若是出現上下游工序加工速度不一致的狀況,上下游工人之間必須互相等待,確保不會出現傳送帶上的半成品無人接收的狀況。緩存
爲了解決這個問題,小袁在每組傳送的下游帶配備了一個暫存半成品的倉庫,這樣上游工人就不用等待下游工人有空,任什麼時候間均可以把加工完成的半成品丟到傳送帶上,沒法接收的貨物被暫存在倉庫中,下游工人能夠隨時來取。傳送帶配備的倉庫實際上起到了「通訊」過程當中「緩存」的做用。服務器
傳送帶解決了半成品運輸問題,倉庫能夠暫存一些半成品,解決了上下游生產速度不一致的問題,小袁在不知不覺中實現了一個巧克力工廠版的消息隊列。架構
接下來咱們說一下平常開發中,哪些問題適合使用消息隊列解決。併發
大多數程序員在面試中,應該都問過或被問過一個經典卻沒有標準答案的問題:如何設計一個秒殺系統?這個問題能夠有一百個版本的合理答案,但大多數答案中都離不開消息隊列。運維
秒殺系統須要解決的核心問題是,如何利用有限的服務器資源,儘量多地處理短期內的海量請求。咱們知道,處理一個秒殺請求包含了不少步驟,例如:異步
若是沒有任何優化,正常的處理流程是:App 將請求發送給網關,依次調用上述 5 個流程,而後將結果返回給 APP。分佈式
對於對於這 5 個步驟來講,可否決定秒殺成功,實際上只有風險控制和庫存鎖定這 2 個步驟。只要用戶的秒殺請求經過風險控制,並在服務端完成庫存鎖定,就能夠給用戶返回秒殺結果了,對於後續的生成訂單、短信通知和更新統計數據等步驟,並不必定要在秒殺請求中處理完成。
因此當服務端完成前面 2 個步驟,肯定本次請求的秒殺結果後,就能夠立刻給用戶返回響應,而後把請求的數據放入消息隊列中,由消息隊列異步地進行後續的操做。
處理一個秒殺請求,從 5 個步驟減小爲 2 個步驟,這樣不只響應速度更快,而且在秒殺期間,咱們能夠把大量的服務器資源用來處理秒殺請求。秒殺結束後再把資源用於處理後面的步驟,充分利用有限的服務器資源處理更多的秒殺請求。
能夠看到,在這個場景中,消息隊列被用於實現服務的異步處理。這樣作的好處是:
繼續說咱們的秒殺系統,咱們已經使用消息隊列實現了部分工做的異步處理,但咱們還面臨一個問題:如何避免過多的請求壓垮咱們的秒殺系統?
一個設計健壯的程序有自我保護的能力,也就是說,它應該能夠在海量的請求下,還能在自身能力範圍內儘量多地處理請求,拒絕處理不了的請求而且保證自身運行正常。不幸的是,現實中不少程序並無那麼「健壯」,而直接拒絕請求返回錯誤對於用戶來講也是不怎麼好的體驗。
所以,咱們須要設計一套足夠健壯的架構來將後端的服務保護起來。咱們的設計思路是,使用消息隊列隔離網關和後端服務,以達到流量控制和保護後端服務的目的。
加入消息隊列後,整個秒殺流程變爲:
秒殺開始後,當短期內大量的秒殺請求到達網關時,不會直接衝擊到後端的秒殺服務,而是先堆積在消息隊列中,後端服務按照本身的最大處理能力,從消息隊列中消費請求進行處理。
對於超時的請求能夠直接丟棄,APP 將超時無響應的請求處理爲秒殺失敗便可。運維人員還能夠隨時增長秒殺服務的實例數量進行水平擴容,而不用對系統的其餘部分作任何更改。
這種設計的優勢是:能根據下游的處理能力自動調節流量,達到「削峯填谷」的做用。但這樣作一樣是有代價的:
那還有沒有更簡單一點兒的流量控制方法呢?若是咱們能預估出秒殺服務的處理能力,就能夠用消息隊列實現一個令牌桶,更簡單地進行流量控制。
令牌桶控制流量的原理是:單位時間內只發放固定數量的令牌到令牌桶中,規定服務在處理請求以前必須先從令牌桶中拿出一個令牌,若是令牌桶中沒有令牌,則拒絕請求。這樣就保證單位時間內,能處理的請求不超過發放令牌的數量,起到了流量控制的做用。
實現的方式也很簡單,不須要破壞原有的調用鏈,只要網關在處理 APP 請求時增長一個獲取令牌的邏輯。
令牌桶能夠簡單地用一個有固定容量的消息隊列加一個「令牌發生器」來實現:令牌發生器按照預估的處理能力,勻速生產令牌並放入令牌隊列(若是隊列滿了則丟棄令牌),網關在收到請求時去令牌隊列消費一個令牌,獲取到令牌則繼續調用後端秒殺服務,若是獲取不到令牌則直接返回秒殺失敗。
以上是經常使用的使用消息隊列兩種進行流量控制的設計方法,你能夠根據各自的優缺點和不一樣的適用場景進行合理選擇。
消息隊列的另一個做用,就是實現系統應用之間的解耦。再舉一個電商的例子來講明解耦的做用和必要性。
咱們知道訂單是電商系統中比較核心的數據,當一個新訂單建立時:
這些訂單下游的系統都須要實時得到訂單數據。隨着業務不斷髮展,這些訂單下游系統不斷的增長,不斷變化,而且每一個系統可能只須要訂單數據的一個子集,負責訂單服務的開發團隊不得不花費很大的精力,應對不斷增長變化的下游系統,不停地修改調試訂單系統與這些下游系統的接口。任何一個下游系統接口變動,都須要訂單模塊從新進行一次上線,對於一個電商的核心服務來講,這幾乎是不可接受的。
全部的電商都選擇用消息隊列來解決相似的系統耦合過於緊密的問題。引入消息隊列後,訂單服務在訂單變化時發送一條消息到消息隊列的一個主題 Order 中,全部下游系統都訂閱主題 Order,這樣每一個下游系統均可以得到一份實時完整的訂單數據。
不管增長、減小下游系統或是下游系統需求如何變化,訂單服務都無需作任何更改,實現了訂單服務與下游服務的解耦。
以上就是消息隊列最常被使用的三種場景:異步處理、流量控制和服務解耦。固然,消息隊列的適用範圍不只僅侷限於這些場景,還有包括:
簡單的說,咱們在單體應用裏面須要用隊列解決的問題,在分佈式系統中大多均可以用消息隊列來解決。
同時咱們也要認識到,消息隊列也有它自身的一些問題和侷限性,包括:
因此咱們說沒有最好的架構,只有最適合的架構,根據目標業務的特色和自身條件選擇合適的架構,纔是體現一個架構師功力的地方。
在你工做或學習涉及到的系統中,哪些問題能夠經過引入消息隊列來解決?對於系統中已經使用消息隊列,能夠對應到這一講中提到的哪一個場景?若是沒有能夠對應的場景,那這個消息隊列在系統中起到的是什麼做用?解決了什麼問題?是否又帶來了什麼新的問題?歡迎在留言區寫下你的想法。