前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點擊跳轉到教程。
消息中間件的作用
1. 應用解耦
2. 異步處理
比如用戶注冊場景,注冊主流程完成以后,需要調用郵件系統發送郵件通知用戶注冊成功,可能還需要調用其他系統。這是串行的,如果一個系統依賴很多系統,那么這個主流程會比較長,耦合度高,整個系統維護成本也會越來越高。那么我們就可以使用消息中間件來進行解耦,通過發布訂閱模式,完成用戶注冊之后,向中間件發送消息,這樣就可以馬上給用戶返回,至于后續工作其他系統向中間件訂閱這個消息并完成后續工作就好。這也就是一個解耦和異步處理過程。
?
中間件有下面兩種模型
點對點模型
發布訂閱模型
?
消息中間件的解耦和異步是兩個最重要的需求點,除此之外還應該做一些其他事情比如:
-
保證一致性,產生消息和發送消息是一致的,也就是如果操作成功,那么消息一定發送成功;如果業務操作沒有成功那么就不能發送消息
-
具備一定消息堆積能力,可以為后端擋住一些數據流保證后端不會被壓垮
-
具備消息實時性,保證消息的低延遲
-
具備消息的可靠性,主要是可靠地存儲和投遞
?
消息系統里面應該有這樣一個假設:消息一定會堆積。下游系統通常有很多,里面有重要的也不重要的,面對突發流量高峰,一定會有后端系統處理不過來的情況,從而造成消息堆積;當然還有一種情況是后端系統出現問題導致暫時無法消費消息從而造成消息中間件的消息堆積。所以中間件要起到蓄水池的作用。
?
數據一致性,這個很容易理解,因為是分布式異步的,但是又不能容忍數據出錯,所以在性能和數據一致性方面就需要有所妥協,通常在互聯網行業中采取最終一致性。需要注意的是最終一致性和弱一致性不同,弱一致性表示允許在異常情況下數據可能不一致,而最終一致性則是在某段時間內允許不一致但是最終會一致。
?
RocketMQ介紹
基于發布訂閱的隊列模型消息中間件,它只有發布和訂閱的消息方式,消息類型只支持Message,消息可以持久化。服務端使用JAVA編寫,客戶端支持JAVA、C++。阿里2012年開源,之后作為Apache基金會的一個項目進行維護。是一款低延遲、高可靠、可伸縮、易于使用的中間件。在Github上有相關介紹。
?
特性
消息可靠性:
-
生產者的可靠性保證:生產者發送消息后返回SendResult,如果isSuccess返回true,則表示消息已經確認發送到服務器并被服務器接收保存。整個發送過程是一個同步過程。
-
服務器的可靠性:消息生產者發送的消息,RocketMQ服務收到后在做必要的校驗和檢查之后馬上保存到磁盤,寫入成功后返回給生產者。因此可以確認每條發送結果為成功的消息都會被消息服務器寫入磁盤。
-
消費者的可靠性:消費者是一條一條順序消費的,之后在成功消費一條后才會消費嚇一跳。如果在消費某一條消息時失敗則會重試消費這條消息,默認為5次,如果超過最大次數仍然無法消費,則將消息保存到本地,后臺線程繼續重試消費,主線程則會繼續往后走,消費隊列后面的消息。
?
消息持久性:RocketMQ收到消息后,會將消息持久化到文件,并利用Linux文件系統內存來提高性能
消息實時性:RocketMQ采取長輪詢+PULL模式保證消息的持久性
消息重復:對于消費者來說,通過拉取方式將消息保存到本地,消費完再向服務器返回,在網絡異常的情況下可能會出現重復。
消息過濾:
-
服務器端過濾:減少不必要消息傳輸,但是會增加服務器負擔
-
客戶端過濾:根據客戶端需求來定制消息,缺點是客戶端會收到對它來說沒用的消息,如果客戶端無法承載這么多消息就會導致故障
消息堆積:支持10億級別的消息堆積,不會因為消息堆積影響性能
?
術語說明
?
角色 | 說明 |
Producer | Producer: 生產者,用于將消息發送到RocketMQ,生產者本身既可以是生成消息,也可以對外提供接口,由外部來調用接口,再由生產者將受到的消息發送給MQ。 |
Consumer | Consumer: 消費者,從Broker拉取消息進行消費。從應用角度來說有兩類消費者:
|
Broker | Broker: RocketMQ服務器,也是整個服務的核心,它實現了消息的存儲、拉取功能。它通常以集群方式啟動,并可配置主從,每個broker上提供對指定topic的服務。理解了broker的原理以及它和其他服務交互的過程,也就命令消息中間件的原理,其實都大同小異。它具有2中角色
|
Topic | Topic: 消息的主題,由用于定義并在服務端配置,消費者可以按照主題進行訂閱,也就是消息分類,通常一個應用一個Topic |
Message | Message: 在生產者、消費者、服務器之間傳遞的消息,一個message必須屬于一個Topic |
Namesrv | Namesrv: 一個無狀態的名稱服務,可以集群部署,每一個broker啟動的時候都會向名稱服務器注冊,主要是接收broker的注冊(broker每十秒就會向所有名稱服務器發送心跳請求,同時注冊topic信息到名稱服務器),接收客戶端的路由請求并返回路由信息,你可以理解為服務自動發現,就是相當于zookeeper在dubbo框架中的作用。
|
Group | Group: 組名,一類消費者或者生產者的集合名稱。
|
Offset | Offset: 偏移量,消費者拉取消息時需要知道上一次消費到了什么位置,這一次從哪里開始。 |
Partition | Partition: 分區,Topic物理上的分組,一個Topic可以分為多個分區,每個分區是一個有序的隊列。分區中的每條消息都會給分配一個有序的ID,也就是偏移量。 分區的目的:
Topic是消息的邏輯隊列,分區是物理隊列。可以通過配置文件來設置topic的默認分區數量,也可以在新建立topic的時候指定。建議分區數量和消費者數量一致,因為消費者數量多,多出來的不會去消費消息的,因為一個隊列只能被一個消費者消費。如果消費者數量少則消費者就會比較繁忙。 |
Tag | Tag: 用于對消息進行過濾,理解文件message的子主題,同一業務不同目的的message可以用相同的topic但是可以用不同的tag來區分,在隊列中tag在消息的數據結構中被 轉換為一個8byte的hashcode,這樣節省空間。過濾分兩步:
|
key | key: 消息的KEY字段是為了唯一表示消息的,方便查問題,不是說必須設置,只是說設置為了方便開發和運維定位問題,這個KEY可以是訂單ID等。 |
?
原理
消費者:
-
Push Consumer,應用向Consumer對象注冊一個Listener接口,一但收到消息,Consumer對象立刻回調Listener接口方法
-
Pull Consumer,應用主動調用Consumer的拉取消息方法,從Broker拉消息
消費模式:
-
廣播模式:一條消息被多個消費者消費,即使它們屬于同一個消費者組,消息會被組中的每個成員消費一次。
-
集群模式:消息會被平均分配到消費者組中進行消費。
消息模式:
-
順序消息:消息的消費順序要和發送的順序一致,一類消息為滿足順序性,生產者必須單線程順序發送且發送到同一個隊列,這樣消費者就可以按照生產者發送的順序去消費。
-
普通順序消息:正常情況下可以保證完全順序消費,但是一旦發生異常,比如broker重啟,由于隊列總數發生變化,會產生短暫的消息順序不一致。如果業務可以容忍這種異常情況則可以使用。
-
嚴格順序消息:無論任何情況下都必須保證消息的順序,但是這就犧牲分布式的高可用功能,也就是Broker集群中只要有一臺不可用,那么整個集群就不可用。如果集群部署模式為同步雙寫模式,那么可以通過備機自動切換來避免,不過仍然存在短暫間隙的服務不可用。
消息的存儲
生產者上產消息,根據Topic選擇其對應的某一個分區,然后發送到這個分區所在的Brocker上,消費者根據訂閱的Topic選擇去Topic的某一個分區拉取消息。
RocketMQ收到消息后會把消息保存在本地文件中,每個文件最大上線1G,如果寫入消息時超過當前文件大小,會建立一個新文件,文件名為起始字節大小。消息寫入是順序的,讀取是隨機的,因為數據持久化當前寫入文件只有一個,所以可以是順序寫入,但是讀取的時候因為有多個邏輯隊列,每個邏輯隊列由多個分區所以就出現多個邏輯讀隊列,這樣讀取的時候就是隨機的。如何提高讀取性能呢?就是盡可能讓讀命中系統pageCache,減少磁盤IO次數。RcoketMQ的持久化是先寫入pageCache頁面高速緩存,然后刷盤,這樣保證內存與磁盤都有一份相同的數據,訪問時直接從內存讀取。另外一方面RocketMQ在文件讀寫方面做了優化,采用內存映射方式完成,也就是把磁盤文件映射到內存地址空間,避免了內核空間到用戶空間的復制。
支持的部署架構
?
集群方式 | 消息可靠性(Master宕機) | 服務可用性 | 特點 | 其他說明 |
一組主主 | 同步刷盤消息一條都不會丟失 | 整體可用,未被消費的消息無法取得,影響實時性 | 結構簡單、擴容方便、性能最高 | 適合消息可靠性高,實時性低的需求 |
一組主從 | 異步有毫秒級丟失,同步雙寫不丟失 | 主備不能切換,且備機只能讀不能寫,會造成服務整體不可用 | ? | 不推薦使用 |
多組主從 (異步復制) | 故障是會丟失消息 | 整體可用,實時性影響是毫秒級別,該組服務只能讀不能寫 | 結構復雜、擴容方便,性能很高。 | 適合消息可靠性中等,實時性要求中等的場景 |
多組主從(同步雙寫) | 不丟消息 | 整體可用,不影響實時性。該組服務只能讀不能寫。不能自動切換。 | 結構復雜,擴容方便,性能比異步低一點,所以實時性也并不比異步方式高太多。 | 適合消息可靠性高,實時性中等,性能要求不高的場景。 |
推薦的架構如下:
高要求則使用多組主從同步雙寫,低要求使用主主方案。
應用場景
-
RocketMQ應用到Cache,可以用在大量機器同步信息的場景
-
業務削峰,在大量交易涌入時,后端可能無法及時處理,所以MQ的大量消息堆積功能就可以發揮作用。
-
日志收集,RocketMQ的設計模型從Kafka衍生而來,kafka在日志收集系統中充當緩沖功能,隨意RocketMQ也適用此場景
-
對可靠性要求很高的場景,尤其是電商里面的訂單扣款,因為扣款要涉及到很多第三方支付。
優缺點
優點
-
順序性,它支持順序性,可以做到局部有序,在單線程內使用該生產者發送的消息按照發送的順序到達服務器并存儲,并按照相同順序被消費,但前提是這些消息發往同一服務器的同一個分區
-
實時性:采取長輪詢+PULL消費消息,你可以自己決定如何在響應性和吞吐量之間做平衡,配合合理的參數設置來獲得更高的響應時間,實時性不低于PUSH方式
-
提供了豐富的拉取模式
-
支持10億級別的消息堆積,不會因為堆積導致性能下降
-
高效的訂閱者水平擴展機制
缺點
-
消息重復問題,它不能保證不重復,只能保證正常情況下不重復
-
不支持分布式事務
-
消息過濾功能擴展比較單一
?
消息順序
消息順序是只可以按照消息發送的順序進行消費。一個訂單產生3條消息,訂單創建、付款、訂單完成。消費時只有按照順序消費才有意義,不可能先消費付款消息再消費訂單創建消息,這樣就亂了。另外,多筆訂單又可以并行消費。如何保證呢?
一個訂單產生的消息只能發送給同一個MQ服務器中的同一個分區,并且按順序發送,這樣才能在理論上保證消費者消費時是按照順序消費的,因為一個分區就是一個邏輯隊列。生產者雖然按順序發送,但是第一條消息到達MQ的耗時比第二條多,那么第二條則會被先消費,這樣就又導致消費時不是順序的。那么如何解決呢?可以采取只有第一條被消費者消費成功后再發送第二條。看下圖:
但是如果第一條被發送到消費者后,消費者沒有響應(消費者發送響應但是因為網絡問題丟失或者消費者就沒有收到消息),那么在這種情況下你是繼續發送第二條還是重發第一條呢?如果是嚴格消息順序,那肯定是重發第一條,但是如果是消費者消費后的響應丟失了,那么重發第一條就會造成重復消費。
?
從另外一方面看,如果不考慮網絡異常,那么要實現嚴格消息,就必須采取一種一對一關系,生產者A的消息對應到MQ服務器1的X隊列,消費者A消費X隊列。這樣串行結構就會造成系統吞吐量太低;更多異常需要處理比如消費端出現問題,那么整個消息隊列就會出現阻塞。RocketMQ通過輪詢所有隊列來確定消息發送到哪一個隊列(負載均衡),比如相同訂單號的消息會被先后發送到統一隊列中。所以RocketMQ
?
消息重復
造成消費重復的根本原因是網絡不可達,只要有網絡,這種網絡的不穩定因素就存在你無法規避。所以解決這個問題的最好辦法就是繞過它。這就變成了,消費端收到兩個一樣的消息后如何處理,而不是從發送端解決不發送2個一樣的消息。對于消費端的要求就是:
-
消費端處理業務消息要保持冪等性,也就是同一個東西執行多次會得到相同結果
-
保證每條消息都有唯一編號切保證消息處理成功與去重表的日志同時出現
第一條好理解,第二條就是利用一張日志表來記錄已經處理成功的消息ID,如果新到的消息ID已經存在表中那么就不再處理這個消息。第一條是在消費端實現的,不屬于消息系統的功能;第二條可以是消息系統實現也可以是業務端實現,處于對消息系統的吞吐量和高可用考慮最好還是由消費端去處理。所以這也就是RocketMQ不解決消息重復的原因。
轉自:http://blog.51cto.com/littledevil/2068474
? ? ? ? ? ?http://blog.51cto.com/littledevil/2068548
? ? ? ? ? ?http://blog.51cto.com/littledevil/2068718