1. 程式人生 > >視訊第13章(高併發之訊息佇列思路)

視訊第13章(高併發之訊息佇列思路)

1、訊息佇列

訊息佇列已經逐漸成為企業IT系統內部通訊的核心手段。它具有低耦合、可靠投遞、廣播、流量控制、最終一致性等一系列功能,成為非同步RPC的主要手段之一。 在這裡插入圖片描述 訊息被處理的過程相當於流程A被處理。我們這裡以一個實際的模型來討論下,比如使用者下單成功時給使用者發簡訊,如果沒有這個訊息佇列,我們會選擇同步呼叫發簡訊的介面,

並等待短息傳送成功,這時候假設簡訊介面實現出現問題了,或者簡訊呼叫端超時了,又或者簡訊傳送達到上限了,我們是選擇重試幾次還是放棄,還是選擇把這個放到資料庫

過一段時間再看看呢,不管怎樣,實現都很複雜。

我們可以將發簡訊這個請求放在訊息佇列裡,訊息佇列按照一定的順序挨個處理佇列裡的訊息,當處理到傳送簡訊的任務時,通知簡訊服務傳送訊息,如果出現之前出現的問題,那麼把這個訊息重新放到訊息佇列中。

1.1訊息佇列的特性

  • 業務無關,一個具有普適性質的訊息佇列元件不需要考慮上層的業務模型,只做好訊息的分發就可以了,上層業務的不同模組反而需要依賴訊息佇列所定義的規範進行通訊。

  • FIFO,先投遞先到達的保證是一個訊息佇列和一個buffer的本質區別。

  • 容災,對於普適的訊息佇列元件來說,節點的動態增刪和訊息的持久化,都是支援其容災能力的重要基本特性。當然,這個特性對於遊戲伺服器中大部分應用中的訊息佇列來說不是必須的,這個也是跟應用情景有關的,很多時候沒有這種持久化的需求。

  • 效能,這個不必多說了,訊息佇列的吞吐量上去了,整個系統的內部通訊效率也會有提高。 為什麼需要訊息佇列

    當系統中出現“生產“和“消費“的速度或穩定性等因素不一致的時候,就需要訊息佇列,作為抽象層,彌合雙方的差異。“ 訊息 ”是在兩臺計算機間傳送的資料單位。訊息可以非常簡單,例如只包含文字字串;也可以更復雜,可能包含嵌入物件。訊息被髮送到佇列中,“ 訊息佇列 ”是在訊息的傳輸過程中儲存訊息的容器 。

舉幾個例子 1)業務系統觸發簡訊傳送申請,但簡訊傳送模組速度跟不上,需要將來不及處理的訊息暫存一下,緩衝壓力。就可以把簡訊傳送申請丟到訊息佇列,直接返回使用者成功,簡訊傳送模組再可以慢慢去訊息佇列中取訊息進行處理。 2)調遠端系統下訂單成本較高,且因為網路等因素,不穩定,攢一批一起傳送。 3)任務處理類的系統,先把使用者發起的任務請求接收過來存到訊息佇列中,然後後端開啟多個應用程式從佇列中取任務進行處理。

1.2訊息佇列的好處

1.成功完成了一個非同步解耦的過程。簡訊傳送時只要保證放到訊息佇列中就可以了,接著做後面的事情就行。一個事務只關心本質的流程,需要依賴其他事情但是不那麼重要的時候,有通知即可,無需等待結果。每個成員不必受其他成員影響,可以更獨立自主,只通過一個簡單的容器來聯絡。

對於我們的訂單系統,訂單最終支付成功之後可能需要給使用者傳送簡訊積分什麼的,但其實這已經不是我們系統的核心流程了。如果外部系統速度偏慢(比如簡訊閘道器速度不好),那麼主流程的時間會加長很多,使用者肯定不希望點選支付過好幾分鐘才看到結果。那麼我們只需要通知簡訊系統“我們支付成功了”,不一定非要等待它處理完成。

2.保證了最終一致性,通過在佇列中存放任務保證它最終一定會執行。

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

A扣錢成功,呼叫B加錢介面失敗。 A扣錢成功,呼叫B加錢介面雖然成功,但獲取最終結果時網路異常引起超時。 A扣錢成功,B加錢失敗,A想回滾扣的錢,但A機器down機。

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

強一致性,分散式事務,但落地太難且成本太高,後文會具體提到。 最終一致性,主要是用“記錄”和“補償”的方式。在做所有的不確定的事情之前,先把事情記錄下來,然後去做不確定的事情,結果可能是:成功、失敗或是不確定,“不確定”(例如超時等)可以等價為失敗。成功就可以把記錄的東西清理掉了,對於失敗和不確定,可以依靠定時任務等方式把所有失敗的事情重新搞一遍,直到成功為止。 回到剛才的例子,系統在A扣錢成功的情況下,把要給B“通知”這件事記錄在庫裡(為了保證最高的可靠性可以把通知B系統加錢和扣錢成功這兩件事維護在一個本地事務裡),通知成功則刪除這條記錄,通知失敗或不確定則依靠定時任務補償性地通知我們,直到我們把狀態更新成正確的為止。

3.廣播

訊息佇列的基本功能之一是進行廣播。如果沒有訊息佇列,每當一個新的業務方接入,我們都要聯調一次新介面。有了訊息佇列,我們只需要關心訊息是否送達了佇列,至於誰希望訂閱,是下游的事情,無疑極大地減少了開發和聯調的工作量。

4.削峰和流控。 不對於不需要實時處理的請求來說,當併發量特別大的時候,可以先在訊息佇列中作快取,然後陸續傳送給對應的服務去處理

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

總而言之,訊息佇列不是萬能的。對於需要強事務保證而且延遲敏感的,RPC是優於訊息佇列的。 對於一些無關痛癢,或者對於別人非常重要但是對於自己不是那麼關心的事情,可以利用訊息佇列去做。 支援最終一致性的訊息佇列,能夠用來處理延遲不那麼敏感的“分散式事務”場景,而且相對於笨重的分散式事務,可能是更優的處理方式。 當上下游系統處理能力存在差距的時候,利用訊息佇列做一個通用的“漏斗”。在下游有能力處理的時候,再進行分發。 如果下游有很多系統關心你的系統發出的通知的時候,果斷地使用訊息佇列吧。

2、初識Kafka

Kafka是Apache基金會下的一個開源專案。 是一個高效能跨語言的分散式釋出訂閱(PUB/SUB)的訊息佇列系統。 其處理流程圖為;

在這裡插入圖片描述

2.1Kafka特性

可快速持久化。通過O(1)的磁碟資料結構提供訊息的持久化,這種結構對於即使數以TB的訊息儲存也能夠保持長時間的穩定效能。
高吞吐量。即使是非常普通的硬體Kafka也可以支援每秒數百萬的訊息。
完全的分散式系統。它的Broker、Producer、Consumer都原生地支援分散式,自動支援負載均衡。
支援Hadoop的資料並行載入。可通過hadoop的並行載入機制統一處理線上、離線的訊息。
支援通過Kafka伺服器和消費機叢集來分割槽訊息。

2.2Kafka相關術語

對應於上圖:

  • 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)。

3、初識RabbitMQ

相對於其他訊息佇列,RabbitMQ有自己的伺服器的管理介面。 處理流程圖如下:

在這裡插入圖片描述

其中的概念說明:

  • Broker:簡單來說就是訊息佇列伺服器實體。
  • Exchange:訊息交換機,它指定訊息按什麼規則,路由到哪個佇列。
  • Queue:訊息佇列載體,每個訊息都會被投入到一個或多個佇列。 Binding:繫結,它的作用就是把exchange和queue按照路由規則繫結起來。 Routing Key:路由關鍵字,exchange根據這個關鍵字進行訊息投遞。
  • vhost:虛擬主機,一個broker裡可以開設多個vhost,用作不同使用者的許可權分離。
  • producer:訊息生產者,就是投遞訊息的程式。
  • consumer:訊息消費者,就是接受訊息的程式。
  • channel:訊息通道,在客戶端的每個連線裡,可建立多個channel,每個channel代表一個會話任務。
  • ExchangeType(關鍵):交換策略。指定exchange分發訊息到哪一個或多個佇列中。其有四種類型:Direct , Fanout , Topic , Handers。

其處理流程為

  1. 客戶端連線到訊息佇列伺服器,開啟一個channel。

  2. 客戶端宣告一個exchange,並設定相關屬性。

  3. 客戶端宣告一個queue,並設定相關屬性。

  4. 客戶端使用RoutingKey,在exchange和queue之間建立好繫結關係。

  5. 客戶端投遞訊息到exchange。

  6. exchange接收到訊息後,就根據訊息的key和已經設定的binding,進行訊息路由,將訊息投遞到一個或多個佇列裡。

  7. 客戶端只需要負責處理訊息。