RabbitMQ系列一 《消息中間件和RabbitMQ》

什麼是消息中間件?

消息中間件(MQ)的定義

        其實並沒有標準定義。一般認爲,消息中間件屬於分佈式系統中一個子系統,關注於數據的發送和接收,利用高效可靠的異步消息傳遞機制對分佈式系統中的其餘各個子系統進行集成。

        高效:對於消息的處理處理速度快。

        可靠:一般消息中間件都會有消息持久化機制和其他的機制確保消息不丟失。

        異步:指發送完一個請求,不需要等待返回,隨時可以再發送下一個請求,既不需要等待。

        一句話總結,我們消息中間件不生產消息,只是消息的搬運工。

 

 

爲什麼要用消息中間件?

        假設一個電商交易的場景,用戶下單之後調用庫存系統減庫存,然後需要調用物流系統進行發貨,如果交易、庫存、物流是屬於一個系統的,那麼就是接口調用。但是隨着系統的發展,各個模塊越來越龐大、業務邏輯越來越複雜,必然是要做服務化和業務拆分的。這個時候就需要考慮這些系統之間如何交互,一般的處理方式就是 RPC(Remote Procedure Call)(具體實現 dubbo,SpringCloud)。系統繼續發展,可能一筆交易後續需要調用幾十個接口來執行業務,比如還有風控系統、短信服務等等。這個時候就需要消息中間件登場來解決問題了。

        所以消息中間件主要解決分佈式系統之間消息的傳遞,同時爲分佈式系統中其他子系統提供了松耦合的架構,同時還有以下好處:

低耦合

      低耦合,不管是程序還是模塊之間,使用消息中間件進行間接通信。

異步通信能力

      異步通信能力,使得子系統之間得以充分執行自己的邏輯而無需等待。

緩衝能力

      緩衝能力,消息中間件像是一個巨大的蓄水池,將高峯期大量的請求存儲下來慢慢交給後臺進行處理,對於秒殺業務來說尤爲重要。

伸縮性

        伸縮性,是指通過不斷向集羣中加入服務器的手段來緩解不斷上升的用戶併發訪問壓力和不斷增長的數據存儲需求。就像彈簧一樣掛東西一樣,用 戶多,伸一點,用戶少,淺一點,啊,不對,縮一點。是伸縮,不是深淺。衡量架構是否高伸縮性的主要標準就是是否可用多臺服務器構建集羣,是否容易向集羣中添加新的服務器。加入新的服務器後是否可以提供和原來服務器無差別的服務。集羣中可容納的總的服務器數量是否有限制。

擴展性

        擴展性,主要標準就是在網站增加新的業務產品時,是否可以實現對現有產品透明無影響,不需要任何改動或者很少改動既有業務功能就可以上線                                新產品。比如用戶購買電影票的應用,現在我們要增加一個功能,用戶買了鐵血戰士的票後,隨機抽取用戶送異形的限量周邊。怎麼做到不改動用戶購                                 票功能的基礎上增加這個功能。熟悉設計模式的同學,應該很眼熟,這是設計模式中的開閉原則(對擴展開放,對修改關閉)在架構層面的一個原則。

 

RPC 有何區別?

        RPC 和消息中間件的場景的差異很大程度上在於就是“依賴性”和“同步性”。

依賴性:

        比如短信通知服務並不是事交易環節必須的,並不影響下單流程,不是強依賴,所以交易系統不應該依賴短信服務。如果是 RPC 調用,短信通知服務掛了,整個業務就掛了,這個就是依賴性導致的,而消息中間件則沒有這個依賴性。

        消息中間件出現以後對於交易場景可能是調用庫存中心等強依賴系統執行業務,之後發佈一條消息(這條消息存儲於消息中間件中)。像是短信通知服務、數據統計服務等等都是依賴於消息中間件去消費這條消息來完成自己的業務邏輯。

同步性:

        RPC 方式是典型的同步方式,讓遠程調用像本地調用。消息中間件方式屬於異步方式。

相同點:都是分佈式下面的通信方式。

 

消息中間件有些什麼使用場景?

異步處理

場景說明:用戶註冊後,需要發註冊郵件和註冊短信。傳統的做法有兩種   1.串行的方式;2.並行方式。

(1)串行方式:將註冊信息寫入數據庫成功後,發送註冊郵件,再發送註冊短信。以上三個任務全部完成後,返回給客戶端。

(2)並行方式:將註冊信息寫入數據庫成功後,發送註冊郵件的同時,發送註冊短信。以上三個任務完成後,返回給客戶端。與串行的差別是,並行的方式可以提高處理的時間。

假設三個業務節點每個使用 50 毫秒鐘,不考慮網絡等其他開銷,則串行方式的時間是 150 毫秒,並行的時間可能是 100 毫秒。

小結:如以上案例描述,傳統的方式系統的性能(併發量,吞吐量,響應時間)會有瓶頸。如何解決這個問題呢?

引入消息隊列,將不是必須的業務邏輯,進行異步處理。

 

        按照以上約定,用戶的響應時間相當於是註冊信息寫入數據庫的時間,也就是 50 毫秒。註冊郵件,發送短信寫入消息隊列後,直接返回,因此寫入消息隊列的速度很快,基本可以忽略,因此用戶的響應時間可能是 50 毫秒。因此架構改變後,系統的吞吐量提高到每秒 20 QPS。比串行提高了 3 倍, 比並行提高了兩倍。

 

應用解耦

場景說明:用戶下單後,訂單系統需要通知庫存系統。傳統的做法是,訂單系統調用庫存系統的接口。

傳統模式的缺點:

  1. 假如庫存系統無法訪問,則訂單減庫存將失敗,從而導致訂單失敗;
  2. 訂單系統與庫存系統耦合;

    

如何解決以上問題呢?引入應用消息隊列後的方案:

訂單系統:用戶下單後,訂單系統完成持久化處理,將消息寫入消息隊列,返回用戶訂單下單成功。

庫存系統:訂閱下單的消息,採用拉/推的方式,獲取下單信息,庫存系統根據下單信息,進行庫存操作。

    

假如:在下單時庫存系統不能正常使用。也不影響正常下單,因爲下單後,訂單系統寫入消息隊列就不再關心其他的後續操作了。實現訂單系統與庫存系統的應用解耦。

 

流量削峯

        流量削峯也是消息隊列中的常用場景,一般在秒殺或團搶活動中使用廣泛。

        應用場景:秒殺活動,一般會因爲流量過大,導致流量暴增,應用掛掉。爲解決這個問題,一般需要在應用前端加入消息隊列:可以控制活動的人數;可以緩解短時間內高流量壓垮應用。

        

 

日誌處理

        日誌處理是指將消息隊列用在日誌處理中,比如 Kafka 的應用,解決大量日誌傳輸的問題。架構簡化如下:

        

        日誌採集客戶端,負責日誌數據採集,定時寫入 Kafka 隊列:Kafka 消息隊列,負責日誌數據的接收,存儲和轉發;日誌處理應用:訂閱並消費 kafka隊列中的日誌數據;

 

消息通訊

消息通訊是指,消息隊列一般都內置了高效的通信機制,因此可以用在純消息通訊。比如實現點對點消息隊列、聊天室等。    

點對點通訊:客戶端 A 和客戶端 B 使用同一隊列,進行消息通訊。

聊天室通訊:客戶端 A,客戶端 B,客戶端 N 訂閱同一主題,進行消息發佈和接收。實現類似聊天室效果。

 

 

消息中間件的編年史

        卡夫卡與法國作家馬塞爾·普魯斯特,愛爾蘭作家詹姆斯·喬伊斯並稱爲西方現代主義文學的先驅和大師。《變形記》是卡夫卡的短篇代表作,是     卡夫卡的藝術成就中的一座高峯,被認爲是 20 世紀最偉大的小說作品之一。


常見的消息中間件比較

如果一般的業務系統要引入 MQ,怎麼選型:

        用戶訪問量在 ActiveMQ 的可承受範圍內,而且確實主要是基於解耦和異步來用的,可以考慮 ActiveMQ,也比較貼近 Java 工程師的使用習慣,但是 ActiveMQ 現在停止維護了,同時 ActiveMQ 併發不高,所以業務量一定的情況下可以考慮使用。

        RabbitMQ 作爲一個純正血統的消息中間件,有着高級消息協議 AMQP 的完美結合,在消息中間件中地位無可取代,但是 erlang 語言阻止了我們去深入研究和掌控,對公司而言,底層技術無法控制,但是確實是開源的,有比較穩定的支持,活躍度也高。

        對自己公司技術實力有絕對自信的,可以用 RocketMQ,但是 RocketMQ 誕生比較晚,並且更新迭代很快,這個意味着在使用過程中有可能會遇到很多坑,所以如果你們公司 Java 技術不是很強,不推薦使用 。

        所以中小型公司,技術實力較爲一般,技術挑戰不是特別高,用ActiveMQ、RabbitMQ          是不錯的選擇;大型公司,基礎架構研發實力較強,用RocketMQ 是很好的選擇

        如果是大數據領域的實時計算、日誌採集等場景,用 Kafka 是業內標準的,絕對沒問題,社區活躍度很高,幾乎是全世界這個領域的事實性規範。

        從性能上來看,使用文件系統的消息中間件(kafka、rokcetMq)性能是最好的,所以基於文件系統存儲的消息中間件是發展趨勢。(從存儲方式和效率來看 文件系統>KV 存儲>關係型數據庫)

 

AMQP 概論

AMQP

        是應用層協議的一個開放標準,爲面向消息的中間件設計。基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不同產品,不同的開發語言等條件的限制。目標是實現一種在全行業廣泛使用的標準消息中間件技術,以便降低企業和系統集成的開銷,並且向大衆提供工業級的集成服務。     主要實現有 RabbitMQ。

客戶端與RabbitMQ 的通訊

連接

        首先作爲客戶端(無論是生產者還是消費者),你如果要與 RabbitMQ 通訊的話,你們之間必須創建一條 TCP 連接,當然同時建立連接後,客戶端還必鬚髮送一條“問候語”讓彼此知道我們都是符合 AMQP 的語言的,比如你跟別人打招呼一般會說“你好!”,你跟國外的美女一般會說“hello!” 一樣。你們確認好“語言”之後,就相當於客戶端和 RabbitMQ 通過“認證”了。你們之間可以創建一條 AMQP 的信道。

        連接在 RabbitMQ 原生客戶端(5.0.0)版本中默認使用 java 的原生 socket,但是也支持 NIO,需要手動設置修改。

信道

        概念:信道是生產者/消費者與 RabbitMQ 通信的渠道。信道是建立在 TCP 連接上的虛擬連接,什麼意思呢?就是說 rabbitmq 在一條 TCP 上建立成百上千個信道來達到多個線程處理,這個 TCP 被多個線程共享,每個線程對應一個信道,信道在 RabbitMQ 都有唯一的 ID ,保證了信道私有性,對應上唯一的線程使用。

        疑問:爲什麼不建立多個 TCP 連接呢?原因是 rabbit 保證性能,系統爲每個線程開闢一個 TCP 是非常消耗性能,每秒成百上千的建立銷燬 TCP會嚴重消耗系統。所以 rabbitmq 選擇建立多個信道(建立在 tcp 的虛擬連接)連接到 rabbit 上。

        從技術上講,這被稱之爲“多路複用”,對於執行多個任務的多線程或者異步應用程序來說,它非常有用。

 

RabbitMQ 中使用 AMQP

包括的要素

生產者、消費者、消息

        生產者:消息的創建者,發送到 rabbitmq;

        消費者:連接到 rabbitmq,訂閱到隊列上,消費消息,持續訂閱(basicConsumer)和單條訂閱(basicGet).

        消息:包含有效載荷和標籤,有效載荷指要傳輸的數據,,標籤描述了有效載荷,並且 rabbitmq 用它來決定誰獲得消息,消費者只能拿到有效載荷, 並不知道生產者是誰。

交換器、隊列、綁定、路由鍵

        隊列通過路由鍵(routing key,某種確定的規則)綁定到交換器,生產者將消息發佈到交換器,交換器根據綁定的路由鍵將消息路由到特定隊列, 然後由訂閱這個隊列的消費者進行接收。

       (routing_key 和 綁定鍵 binding_key  的最大長度是 255 個字節)

 

消息的確認

        消費者收到的每一條消息都必須進行確認(自動確認和自行確認)。

        消費者在聲明隊列時,可以指定 autoAck 參數,當 autoAck=false 時,RabbitMQ 會等待消費者顯式發回 ack 信號後才從內存(和磁盤,如果是持久化消息的話)中移去消息。否則,RabbitMQ 會在隊列中消息被消費後立即刪除它。

        採用消息確認機制後,只要令 autoAck=false,消費者就有足夠的時間處理消息(任務),不用擔心處理消息過程中消費者進程掛掉後消息丟失的問題, 因爲 RabbitMQ 會一直持有消息直到消費者顯式調用 basicAck 爲止。

        當 autoAck=false 時,對於 RabbitMQ 服務器端而言,隊列中的消息分成了兩部分:一部分是等待投遞給消費者的消息;一部分是已經投遞給消費者,但是還沒有收到消費者 ack 信號的消息。如果服務器端一直沒有收到消費者的 ack 信號,並且消費此消息的消費者已經斷開連接,則服務器端會安排該消息重新進入隊列,等待投遞給下一個消費者(也可能還是原來的那個消費者)。

        RabbitMQ 不會爲未 ack 的消息設置超時時間,它判斷此消息是否需要重新投遞給消費者的唯一依據是消費該消息的消費者連接是否已經斷開。這麼設計的原因是 RabbitMQ 允許消費者消費一條消息的時間可以很久很久。

常見問題

        如果消息達到無人訂閱的隊列會怎麼辦?消息會一直在隊列中等待,RabbitMq 默認隊列是無限長度的。

        多個消費者訂閱到同一隊列怎麼辦?消息以循環的方式發送給消費者,每個消息只會發送給一個消費者。

        消息路由到了不存在的隊列怎麼辦?一般情況下,涼拌,RabbitMq 會忽略,當這個消息不存在,也就是這消息丟了。

虛擬主機

        虛擬消息服務器,vhost,本質上就是一個 mini 版的 mq 服務器,有自己的隊列、交換器和綁定,最重要的,自己的權限機制。Vhost 提供了邏輯上的分離,可以將衆多客戶端進行區分,又可以避免隊列和交換器的命名衝突。Vhost 必須在連接時指定,rabbitmq 包含缺省 vhost:“/”,通過缺省用戶和口令 guest 進行訪問。

        rabbitmq 裏創建用戶,必須要被指派給至少一個 vhost,並且只能訪問被指派內的隊列、交換器和綁定。Vhost 必須通過 rabbitmq 的管理控制工具創建。

 

交換器類型

        共有四種 direct,fanout,topic,headers,其種 headers(幾乎和 direct 一樣)不實用,可以忽略。

Direct

        路由鍵完全匹配,消息被投遞到對應的隊列, direct 交換器是默認交換器。聲明一個隊列時,會自動綁定到默認交換器,並且以隊列名稱作爲路由鍵:channel -> basic_public($msg,’’,’queue-name’)

 

Fanout

        消息廣播到綁定的隊列,不管隊列綁定了什麼路由鍵,消息經過交換器,每個隊列都有一份。

 

Topic

        通過使用“*”和“#”通配符進行處理,使來自不同源頭的消息到達同一個隊列,”.”將路由鍵分爲了幾個標識符,“*”匹配 1 個,“#”匹配一個或多個。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章