高併發之消息隊列

消息隊列已經逐漸成爲企業IT系統內部通訊的核心手段。它具備低耦合、可靠投遞、廣播、流量控制、最終一致性等一系列功能,成爲異步RPC的主要手段之一。前端

消息被處理的過程至關於流程A被處理。咱們這裏以一個實際的模型來討論下,好比用戶下單成功時給用戶發短信,若是沒有這個消息隊列,咱們會選擇同步調用發短信的接口,數據庫

並等待短息發送成功,這時候假設短信接口實現出現問題了,或者短信調用端超時了,又或者短信發送達到上限了,咱們是選擇重試幾回仍是放棄,仍是選擇把這個放到數據庫緩存

過一段時間再看看呢,無論怎樣,實現都很複雜。服務器

咱們能夠將發短信這個請求放在消息隊列裏,消息隊列按照必定的順序挨個處理隊列裏的消息,當處理到發送短信的任務時,通知短信服務發送消息,若是出現以前出現的問題,那麼把這個消息從新放到消息隊列中。網絡

 

消息隊列的好處:併發

1.成功完成了一個異步解耦的過程。短信發送時只要保證放到消息隊列中就能夠了,接着作後面的事情就行。一個事務只關心本質的流程,須要依賴其餘事情可是不那麼重要的時候,有通知便可,無需等待結果。每一個成員沒必要受其餘成員影響,能夠更獨立自主,只經過一個簡單的容器來聯繫。負載均衡

對於咱們的訂單系統,訂單最終支付成功以後可能須要給用戶發送短信積分什麼的,但其實這已經不是咱們系統的核心流程了。若是外部系統速度偏慢(好比短信網關速度很差),那麼主流程的時間會加長不少,用戶確定不但願點擊支付過好幾分鐘纔看到結果。那麼咱們只須要通知短信系統「咱們支付成功了」,不必定非要等待它處理完成。異步

 

2.保證了最終一致性,經過在隊列中存聽任務保證它最終必定會執行。分佈式

最終一致性指的是兩個系統的狀態保持一致,要麼都成功,要麼都失敗。固然有個時間限制,理論上越快越好,但實際上在各類異常的狀況下,可能會有必定延遲達到最終一致狀態,但最後兩個系統的狀態是同樣的。
業界有一些爲「最終一致性」而生的消息隊列,如Notify(阿里)、QMQ(去哪兒)等,其設計初衷,就是爲了交易系統中的高可靠通知。
以一個銀行的轉帳過程來理解最終一致性,轉帳的需求很簡單,若是A系統扣錢成功,則B系統加錢必定成功。反之則一塊兒回滾,像什麼都沒發生同樣。
然而,這個過程當中存在不少可能的意外:高併發

  1. A扣錢成功,調用B加錢接口失敗。
  2. A扣錢成功,調用B加錢接口雖然成功,但獲取最終結果時網絡異常引發超時。
  3. A扣錢成功,B加錢失敗,A想回滾扣的錢,但A機器down機。

可見,想把這件看似簡單的事真正作成,真的不那麼容易。全部跨VM的一致性問題,從技術的角度講通用的解決方案是:

  1. 強一致性,分佈式事務,但落地太難且成本過高,後文會具體提到。
  2. 最終一致性,主要是用「記錄」和「補償」的方式。在作全部的不肯定的事情以前,先把事情記錄下來,而後去作不肯定的事情,結果多是:成功、失敗或是不肯定,「不肯定」(例如超時等)能夠等價爲失敗。成功就能夠把記錄的東西清理掉了,對於失敗和不肯定,能夠依靠定時任務等方式把全部失敗的事情從新搞一遍,直到成功爲止。
    回到剛纔的例子,系統在A扣錢成功的狀況下,把要給B「通知」這件事記錄在庫裏(爲了保證最高的可靠性能夠把通知B系統加錢和扣錢成功這兩件事維護在一個本地事務裏),通知成功則刪除這條記錄,通知失敗或不肯定則依靠定時任務補償性地通知咱們,直到咱們把狀態更新成正確的爲止。

3.廣播

消息隊列的基本功能之一是進行廣播。若是沒有消息隊列,每當一個新的業務方接入,咱們都要聯調一次新接口。有了消息隊列,咱們只須要關心消息是否送達了隊列,至於誰但願訂閱,是下游的事情,無疑極大地減小了開發和聯調的工做量。

3.提速。假設咱們還須要發送郵件,有了消息隊列就不須要同步等待,咱們能夠直接並行處理,而下單核心任務能夠更快完成。加強業務系統的異步處理能力。甚至幾乎不可能出現並發現象。

4.削峯和流控。不對於不須要實時處理的請求來講,當併發量特別大的時候,能夠先在消息隊列中做緩存,而後陸續發送給對應的服務去處理

試想上下游對於事情的處理能力是不一樣的。好比,Web前端每秒承受上千萬的請求,並非什麼神奇的事情,只須要加多一點機器,再搭建一些LVS負載均衡設備和Nginx等便可。但數據庫的處理能力卻十分有限,即便使用SSD加分庫分表,單機的處理能力仍然在萬級。因爲成本的考慮,咱們不能奢求數據庫的機器數量追上前端。
這種問題一樣存在於系統和系統之間,如短信系統可能因爲短板效應,速度卡在網關上(每秒幾百次請求),跟前端的併發量不是一個數量級。但用戶晚上個半分鐘左右收到短信,通常是不會有太大問題的。若是沒有消息隊列,兩個系統之間經過協商、滑動窗口等複雜的方案也不是說不能實現。但系統複雜性指數級增加,勢必在上游或者下游作存儲,而且要處理定時、擁塞等一系列問題。並且每當有處理能力有差距的時候,都須要單獨開發一套邏輯來維護這套邏輯。因此,利用中間系統轉儲兩個系統的通訊內容,並在下游系統有能力處理這些消息的時候,再處理這些消息,是一套相對較通用的方式。

總而言之,消息隊列不是萬能的。對於須要強事務保證並且延遲敏感的,RPC是優於消息隊列的。
對於一些無關痛癢,或者對於別人很是重要可是對於本身不是那麼關心的事情,能夠利用消息隊列去作。
支持最終一致性的消息隊列,可以用來處理延遲不那麼敏感的「分佈式事務」場景,並且相對於笨重的分佈式事務,多是更優的處理方式。
當上下游系統處理能力存在差距的時候,利用消息隊列作一個通用的「漏斗」。在下游有能力處理的時候,再進行分發。
若是下游有不少系統關心你的系統發出的通知的時候,果斷地使用消息隊列吧。

 

消息隊列的使用場景:

主要特色是異步處理,主要目的是減小請求響應時間和解耦。因此主要的使用場景就是將比較耗時並且不須要即時(同步)返回結果的操做做爲消息放入消息隊列。

使用場景的話,舉個例子:
假設用戶在你的軟件中註冊,服務端收到用戶的註冊請求後,它會作這些操做:
  1. 校驗用戶名等信息,若是沒問題會在數據庫中添加一個用戶記錄
  2. 若是是用郵箱註冊會給你發送一封註冊成功的郵件,手機註冊則會發送一條短信
  3. 分析用戶的我的信息,以便未來向他推薦一些志同道合的人,或向那些人推薦他
  4. 發送給用戶一個包含操做指南的系統通知
  5. 等等……

可是對於用戶來講,註冊功能實際只須要第一步,只要服務端將他的帳戶信息存到數據庫中他即可以登陸上去作他想作的事情了。至於其餘的事情,非要在這一次請求中所有完成麼?值得用戶浪費時間等你處理這些對他來講可有可無的事情麼?因此實際當第一步作完後,服務端就能夠把其餘的操做放入對應的消息隊列中而後立刻返回用戶結果,由消息隊列異步的進行這些操做。

或者還有一種狀況,同時有大量用戶註冊你的軟件,再高併發狀況下注冊請求開始出現一些問題,例如郵件接口承受不住,或是分析信息時的大量計算使cpu滿載,這將會出現雖然用戶數據記錄很快的添加到數據庫中了,可是卻卡在發郵件或分析信息時的狀況,致使請求的響應時間大幅增加,甚至出現超時,這就有點不划算了。面對這種狀況通常也是將這些操做放入消息隊列(生產者消費者模型),消息隊列慢慢的進行處理,同時能夠很快的完成註冊請求,不會影響用戶使用其餘功能。

 

爲何須要消息隊列?

生產和消費的速度或者穩定性不一致。

 

當今市面上有不少主流的消息中間件,如老牌的ActiveMQ、RabbitMQ,煊赫一時的Kafka,阿里巴巴自主開發的Notify、MetaQ、RocketMQ等。

Kafka的介紹

Kafka是一種高吞吐量的分佈式發佈訂閱消息系統,它能夠處理消費者規模的網站中的全部動做流數據。
Kafka 有以下特性:

  • 以時間複雜度爲O(1)的方式提供消息持久化能力,即便對TB級以上數據也能保證常數時間複雜度的訪問性能。
  • 高吞吐率。即便在很是廉價的商用機器上也能作到單機支持每秒100K條以上消息的傳輸。
  • 支持Kafka Server間的消息分區,及分佈式消費,同時保證每一個Partition內的消息順序傳輸。
  • 同時支持離線數據處理和實時數據處理。
  • Scale out:支持在線水平擴展。

kafka的術語

  • Broker:Kafka集羣包含一個或多個服務器,這種服務器被稱爲broker。
  • Topic:每條發佈到Kafka集羣的消息都有一個類別,這個類別被稱爲Topic。(物理上不一樣Topic的消息分開存儲,邏輯上一個Topic的消息雖然保存於一個或多個broker上但用戶只需指定消息的Topic便可生產或消費數據而沒必要關心數據存於何處)
  • Partition:Partition是物理上的概念,每一個Topic包含一個或多個Partition。
  • Producer:負責發佈消息到Kafka broker。
  • Consumer:消息消費者,向Kafka broker讀取消息的客戶端。
  • Consumer Group:每一個Consumer屬於一個特定的Consumer Group(可爲每一個Consumer指定group name,若不指定group name則屬於默認的group)。

 

下面來介紹RabbitMQ裏的一些基本定義,主要以下:
RabbitMQ Server:提供消息一條從Producer到Consumer的處理。
Exchange:一邊從發佈者方接收消息,一邊把消息推送到隊列。
producer只能將消息發送給exchange。而exchange負責將消息發送到queues。Procuder Publish的Message進入了exchange,exchange會根據routingKey處理接收到的消息,判斷消息是應該推送到指定的隊列仍是是多個隊列,或者是直接忽略消息。這些規則是經過交換機類型(exchange type)來定義的主要的type有direct,topic,headers,fanout。具體針對不一樣的場景使用不一樣的type。
queue也是經過這個routing keys來作的綁定。交換機將會對綁定鍵(binding key)和路由鍵(routing key)進行精確匹配,從而肯定消息該分發到哪一個隊列。
Queue:消息隊列。接收來自exchange的消息,而後再由consumer取出。exchange和queue能夠一對一,也能夠一對多,它們的關係經過routingKey來綁定。
Producer:Client A & B,生產者,消息的來源,消息必須發送給exchange。而不是直接給queue
Consumer:Client 1,2,3消費者,直接從queue中獲取消息進行消費,而不是從exchange中獲取消息進行消費。
相關文章
相關標籤/搜索