1.消息隊列介紹 消息隊列本質上來說是一個符合先進先出原則的單向隊列:一方發送消息並存入消息隊列尾部(生產者投遞消息),一方從消息隊列的頭部取出消息(消費者消費消息)。但對於一個成熟可靠的消息隊列來說,所需要解決的主要問題還包括:高效可靠的消息投遞、存儲;能承受高併發的流量衝擊,可通過集群部署來解決 ...
1.消息隊列介紹
消息隊列本質上來說是一個符合先進先出原則的單向隊列:一方發送消息並存入消息隊列尾部(生產者投遞消息),一方從消息隊列的頭部取出消息(消費者消費消息)。但對於一個成熟可靠的消息隊列來說,所需要解決的主要問題還包括:高效可靠的消息投遞、存儲;能承受高併發的流量衝擊,可通過集群部署來解決單點故障等等。
由於消息隊列具備了以上特點,因此在如今的微服務架構中能夠作為一種中間件,提供許多重要的功能以解決微服務架構中的諸多痛點:
1.應用解耦
微服務架構中,存在著眾多子系統,共同完成對外部用戶的服務。
舉個例子:當用戶在訂單系統下單時,訂單子系統除了需要執行自己系統的業務邏輯之外,可能還需要調用庫存子系統去扣減庫存;調用會員子系統去增加用戶的積分;調用數據分析子系統去插入用戶下單的分析數據等等。用戶的一個下單行為橫跨了N個業務子系統,如果按照傳統的同步串列方式一個接一個的調用,用戶的下單操作將會執行較長的時間,對用戶不友好。同時,由於是同步調用,一旦某一個子系統出現了宕機,訪問超時等問題,整個下單業務都將陷入癱瘓。
消息隊列可以將同步的系統調用轉為非同步的消息投遞,一定程度上解除業務子系統間的耦合。當訂單子系統執行完本地邏輯後,只需發送一個標識下單成功的消息,讓下游依賴的子系統訂閱此消息,消費處理消息來完成對應的業務。這樣,用戶的下單操作將很快完成,也不必擔心下游子系統的故障會波及到訂單系統。
雖然消息隊列解除了業務子系統間的耦合,但同時也讓業務子系統對消息隊列系統有了很強的依賴關係,如果消息隊列出現了故障,業務系統將會出現嚴重故障。
但由於消息隊列在設計之初的目的十分簡單明確:就是為了可靠的收發消息。因此其可用性,穩定性比絕大多數業務系統要高的多。天下沒有免費的午餐,在微服務系統中引入消息隊列依然是利大於弊的。
2.流量削峰
大多數系統的訪問流量並不是一天24小時均勻穩定的,而是存在著一定的突發性。例如電商的秒殺活動,系統配置在平時能承受住500qps,可在進行秒殺活動時,瞬時的qps可能達到了5000,為平常的10倍,如果不進行處理防護,將會導致服務癱瘓。
可以選擇擴容伺服器來應對可能的高峰流量,但擴容的伺服器在秒殺活動過去之後多數會被閑置,從而造成很大的浪費;也可以設定併發的閾值,在訪問併發數達到一定程度時就進行熔斷限流,拒絕手慢的秒殺用戶下單,可這樣會讓用戶體驗很差。
這時,消息隊列就能派上用場了。我們可以在系統中使用消息隊列作為緩衝,將每一個用戶下單請求都作為一條消息存入消息隊列,消息隊列會根據消費者的消費速度以一種穩定的方式將流量傳遞給下游消費者系統,在消費者系統處理完下單操作後非同步的通知用戶下單結果。雖然用戶可能會延遲一段時間才能得到反饋,但無論如何也比無法下單要好。
消息隊列就像一個漏桶,可以將瞬時的尖峰流量緩存起來,並以一種穩定的速度傳遞給下游消費者,從而達到流量削峰的目的。
3.消息分發
沿用之前的例子,訂單子系統的下單成功操作在業務上可能有許多其它系統需要對其做出響應(扣庫存,加積分,核銷優惠券等等)。
按照傳統的方式,需要訂單系統挨個調用其它子系統的介面。隨著業務的變化,每當有新的子系統需要對下單成功操作做出響應時,就需要改動訂單系統的代碼邏輯去適應新的需求。
而如果引入了消息隊列,則可以在下單成功之後由訂單系統發送一條消息,讓感興趣的其它子系統去訂閱下單成功消息。如果新的系統也出現了依賴下單成功動作的需求,自行訂閱對應消息即可,並不需要訂單系統做出任何的改變。
可以利用消息分發機制可以實現代碼邏輯的解耦。
2.Rocketmq介紹
rocketmq是阿裡巴巴團隊使用java語言開發的一款分散式消息中間件,是一款低延遲,高可用,擁有海量消息堆積能力和靈活拓展性的消息隊列。
2.1 rocketmq組成部分
rocketmq由四大核心模塊組成:producer、consumer、brokerServer、nameServer。其中brokerServer和nameServer是rocketmq的服務端,兩者一起獨立的對外提供服務;而producer和consumer可看做是rocketmq的客戶端,一般依附於業務應用程式。
1. Producer
producer負責發送消息。使用producer將消息發送到brokerServer,由brokerServer統一進行消息的分發。
rocketmq支持多種消息發送方式,如同步消息發送、非同步回調消息發送、順序消息發送以及單向消息發送(非同步無回調)。除了單向消息發送,其餘的發送方式均需要brokerServer返回發送結果的確認消息。
特別的,rocketmq的一大特色是支持發送事務消息(半消息),能一定程度上解決分散式事務的問題。
2. Consumer
consumer 負責消費producer發送的消息。consumer會從brokerServer獲取消息,並傳遞給應用程式。
rocketMQ使用的消息原語是At Least Once(至少一次成功消費),如果一定時間內沒有接收到consumer消息確認消費的響應結果,會將同一條消息再次投遞給consumer。rocketmq採用ack機制保證消息的消費成功,所以consumer可能會多次收到同一條消息,需要consumer的業務方做好冪等防護。
從使用者的角度來看,consumer分為兩種方式來獲取信息。一種是推模式(push consume),推模式看起來像是brokerServer將消息推給了consumer;另一種是拉模式(pull consume),拉模式看起來像是consumer主動的去brokerServer拉取消息(實際上,推模式是基於拉模式實現的)。
3. BrokerServer
brokerServer負責消息的接收,存儲和分發,是rocketmq最核心,最重量級的組成部分。
為實現高可用和高吞吐,brokerServer通常採用集群部署,共同對外提供服務。
4. NameServer
nameServer負責提供路由元數據。例如,brokerServer通常是集群部署的,其拓撲結構會經常的發生變化。如果每次集群中broker機器的上下線都需要通知所有的消費者、生產者,效率太低。
因此,rocketmq引入了nameServer作為brokerServer路由信息的維護者,broker的每次上下線都和nameServer通信,由nameServer來維護broker的路由信息,而producer和consumer通過訪問nameServer獲得對應broker的訪問地址後,再向對應的broker發起請求。nameServer解除了broker和客戶端的耦合依賴關係,大大提高了效率。
在其它主流消息隊列中也存在著類似的維護元信息功能的組件,如zookeeper等。rocketmq的設計者認為zk的功能過於強大,殺雞焉用牛刀,通過一個精簡版的元數據服務nameServer,以減少對外部系統的耦合依賴,得以提供更可靠的服務。
nameServer同樣能以集群形式對外提供服務。但和zk集群不同的是,集群內的nameServer伺服器並不會互相通信,而是保持相互獨立。
2.2 rocketmq基本概念模型
介紹完rocketmq的組成部分之後,還需要再引入一些相關概念才能更好的理解rocketmq:
1.topic 主題
topic主題,代表一系列消息的集合,任何消息只能屬於一個topic主題,主題是rocketmq進行消息發佈訂閱的最小單位。業務方可以通過創建並訂閱各式各樣的主題來滿足自身的業務要求。不同主題之間的消息在邏輯上沒有關聯。
2.tag 標簽
tag標簽,tag從屬於topic主題,主要用於對同一主題下的消息進行進一步區分。標簽可以簡單的認為是二級主題,通過tag標簽功能,業務方可以方便的實現對各種二級主題的消費需求。
3.group 組
group組,代表著同一類客戶端的集合。具體可分為消費者組(consumer group)和生產者組(producer group)兩種。消費者組和生產者組之間沒有任何關聯(即使組名一樣)。
消費者組:
消費者組代表著同一類型的消費者集群。同一消費者組內的消費者通常消費同樣的消息且消息消費邏輯一致。消費者組的概念使得consumer集群在消費消息時,rocketmq可以通過負載均衡來做到消費消息時的高可用和容錯。消費者組的更多作用將會在後面的集群/廣播消費模式中繼續講解。
生產者組:
生產者組代表著同一類型的生產者集群。一般來說,消息的生產者在發出了消息得到確認之後便完成了任務,似乎沒有必要為此抽象出生產者集群的概念。
前面說到,rocketmq具有發送事務消息的特性,發送事務消息簡單來說就是生產者先發送出一個半消息(預消息),然後執行本地的事務,在事務完成提交之後再跟著發送一個事務確認消息。半消息和普通消息的最大區別在於,半消息在投遞給broker之後,broker不會馬上讓消費者進行消費,而是等待。只有當接收到生產者後續對應的的事務確認消息後,預消息和確認消息合二為一,才將對應的事務消息交給消費者去消費;而如果最終沒有接收到事務確認消息,則會將消息直接刪除不投遞給消費者,以達到類似事務回滾的效果。事務消息對消費者來說是透明無感知的。
可如果生產者在發送了預消息之後掛了怎麼辦?為解決這個問題,broker會在一定時間沒有收到確認消息後,定時的回查生產者當前事務消息的狀態,回查的範圍是整個生產者組中的某一個線上節點。這種情況下,生產者和消費者一樣,也構成了一個集群監聽來自broker的回查。這樣,即使發送消息的生產者發生了故障,在一定條件下整個生產者集群的事務消息發送功能依然可以正常運轉。
通過生產者組的概念,rocketmq實現了事務消息投遞的高可用。
4.message 消息
message消息是rocketmq中傳遞消息的主體,消息具有全局唯一的messageID屬性,用戶可以根據messageID查詢進行消息的精確查詢。
消息的內容可以是不超過rocketmq限制的、二進位的任意數據,rocketmq不會對消息承載的數據內容做任何干預。
5. 集群(Clustering)/廣播(Broadcasting)消費
集群消費:
對於任意一條被訂閱的消息,同一消費者組下的節點只有一個節點對其進行消費;一個消費者組中的全部節點分攤所有消息。
廣播消費:
對於任意一條被訂閱的消息,同一消費者組下的所有節點都會對其進行消費;一個消費者組中的全部節點都能接收到全量的消息。
混合模式消費:
實質上是前兩者的綜合。同一應用集群構成一個消費者組,不同應用集群之間構成多個不同的消費者組,但卻可以訂閱同一個topic/tag下的消息。
對於任意一條被訂閱的消息,同一消費者組之間只會有一個節點對其進行消費,不同消費者組都會進行全量消息的消費。
3.Rocketmq下載與安裝
介紹了rocketmq的一些基本概念之後,下麵進行rocketmq的下載和安裝,併進行基本的功能測試。簡單起見,nameServer,broker都以單機模式啟動。
註意:示例中新版本的rocketmq要求jvm的最低版本是1.8。
3.1 從官網下載資源
首先在rocketmq的官網可以找到下載資源,其中有已經編譯完成的二進位資源(binary)和需要用戶自己編譯的源代碼資源(source)兩種。在這裡選擇下載已經編譯完成的,更容易上手的二進位資源進行安裝。
3.2 配置rocketmq環境變數
將下載好的資源解壓縮到任意目錄,可以看到如下文件夾和文件,其中命令行的腳本文件都集中放在bin文件夾下。(這裡是windows環境下的操作,資源包中也包含了linux下同樣功能的shell腳本文件,操作並沒有明顯差異)
由於腳本文件依賴一個叫做ROCKETMQ_HOME的環境變數,代指rocketmq安裝的主目錄,因此我們需要配置ROCKETMQ_HOME環境變數。
3.3 啟動nameServer
開啟一個新的命令行視窗用於啟動nameServer,將命令行路徑指向bin文件目錄,後續新開啟的命令行視窗需要做同樣的操作(也可以選擇配置path路徑,一勞永逸)。
執行"mqnamesrv.cmd",看到如下圖的日誌信息代表著nameServer已經啟動成功。保持nameServer視窗開啟,不要關閉。nameServer預設的啟動埠是9876。
rocketmq 4.4.0版本的預設配置文件記憶體設置的比較大,如果啟動時出現了jvm記憶體不足之類的錯誤,可以打開runserver.cmd對其進行編輯,將預設的jvm記憶體分配參數設置的小一點。
3.4 啟動broker
開啟一個新的命令行視窗,執行"mqbroker.cmd -n localhost:9876",用於啟動broker。前面提到,nameServer作為維護路由元數據的中心,broker會在啟動時會先在nameServer進行註冊,使得生產者和消費者能夠及時獲得broker相關的信息。命令後面的-n localhost:9876參數就是用於指定對應nameServer的地址。
當看到如下圖所示日誌信息時,說明broker已經啟動完成。保持broker視窗開啟,不要關閉。
和nameServer啟動一樣,如果出現了記憶體不足的問題,可以修改runbroker.cmd中的jvm啟動參數以符合要求。
當nameServer和broker都啟動完成後,rocketmq的服務端就已經可以對外提供服務了。
3.5 啟動consumer和producer測試消息收發功能
rocketmq的開發人員在rocketmq中添加了簡單的demo消息收發測試程式,我們可以通過tools.cmd調用來進行測試(和前面一樣,其啟動時的jvm參數直接在tools.cmd中修改即可)。
首先開啟一個新的命令行視窗用於啟動consumer,先執行"set NAMESRV_ADDR=localhost:9876"設定命令行視窗級別的環境變數,然後執行"tools.cmd org.apache.rocketmq.example.quickstart.Consumer",看到如下圖所示提示信息時,代表consumer已經啟動成功。保持視窗開啟狀態,此時consumer正在監聽對應的消息,等待消費。
再開啟一個新的命令行視窗用於啟動producer,依然先執行"set NAMESRV_ADDR=localhost:9876",設定命令行視窗級別的環境變數,為生產者指定nameServer的地址。
接著執行"tools.cmd org.apache.rocketmq.example.quickstart.Producer",如無意外,會看到發送消息的刷屏日誌,producer在一瞬間就發送了N條普通消息(1000條);
與此同時,consumer也接收到消息,併在控制臺中列印出了消息消費日誌。
至此,rocketmq的安裝與基本功能的簡單測試宣告完成。
總結
這是"rocketmq學習"系列的第一篇博客,所以先以rockemq概念的介紹和安裝入手。後續的博客內容將會有諸如rocketmq集群部署、圖形化控制台安裝等,並結合rocketmq的源碼進一步理解rocketmq的工作原理。
寫"rocketmq學習"系列博客的主要目的還是為了鞏固並加深自己對rocketmq的理解。因為對於一些知識點只有在寫作的過程中才會發現對其瞭解的並不透徹,通過寫博客可以很好的查漏補缺。
如有理解不到位的地方,歡迎指正。