消息中間件選型

目錄

一、概述   

什麼是消息中間件?

二、消息隊列的使用場景  

爲什麼使用消息隊列?

消息隊列有什麼優缺點?

三、目前流行的消息隊列優缺點對比

四、總結

五、消息中間件的組成

六、消息中間件模式分類

1 點對點

2 發佈/訂閱

七、消息中間件的優勢

八、消息中間件應用場景

九、消息中間件常用協議

十、常見消息中間件MQ介紹

1 RocketMQ

2 RabbitMQ

3 ActiveMQ

4 Redis

5 Kafka

十一、比較

十二、如何保證消息隊列的高可用性

十三、如何保證消息消費時的冪等性

十四、分析一下RabbitMQ和Kafka的消息可靠性傳輸的問題。

(一)RabbitMQ

(二)Kafka

十五、如何保證消息的順序性

十六、如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以後該怎麼處理?有幾百萬消息持續積壓幾小時怎麼解決?

(一)大量消息在mq裏積壓了幾個小時了還沒解決

(二)消息隊列過期失效問題

  (三)   消息隊列滿了怎麼搞?


轉載:

https://blog.csdn.net/java_zyq/article/details/80022391

https://blog.csdn.net/wqc19920906/article/details/82193316

https://blog.csdn.net/qq_36236890/article/details/81174504

一、概述   

什麼是消息中間件?

消息隊列中間件(簡稱消息中間件)是指利用高效可靠的消息傳遞機制進行與平臺無關的數據交流,並基於數據通信來進行分佈式系統的集成。通過提供消息傳遞和消息排隊模型,它可以在分佈式環境下提供應用解耦、彈性伸縮、冗餘存儲、流量削峯、異步通信、數據同步等等功能,其作爲分佈式系統架構中的一個重要組件,有着舉足輕重的地位。個人感覺比價場景應用核心的有三個:解耦、異步、削峯。

二、消息隊列的使用場景  

爲什麼使用消息隊列?

其實這個話題也是面試官經常問詢的問題,問問你消息隊列都有哪些使用場景,然後你項目裏具體是什麼場景,說說你在這個場景裏用消息隊列是什麼

期望的一個回答是說,你們公司有個什麼業務場景,這個業務場景有個什麼技術挑戰,如果不用MQ可能會很麻煩,但是你現在用了MQ之後帶給了你很多的好處

現在你可以下想想你如何回答上述問題,想不起來?  好吧我這裏先介紹幾個常見使用場景,提醒下。。。

解耦:現場畫個圖來說明一下,

A系統發送個數據到BCD三個系統,接口調用發送,那如果E系統也要這個數據呢?那如果C系統現在不需要了呢?現在A系統又要發送第二種數據了呢?A系統負責人瀕臨崩潰中。。。再來點更加崩潰的事兒,A系統要時時刻刻考慮BCDE四個系統如果掛了咋辦?我要不要重發?我要不要把消息存起來?頭髮都白了啊。。。

這是你需要去考慮一下你負責的系統中是否有類似的場景,就是一個系統或者一個模塊,調用了多個系統或者模塊,互相之間的調用很複雜,維護起來很麻煩。但是其實這個調用是不需要直接同步調用接口的,如果用MQ給他異步化解耦,也是可以的,你就需要去考慮在你的項目裏(做過微服務項目的同學這裏是不是考慮下  消息總線 搭配Rabbitmq 做解耦 用於廣播配置文件的更改或者服務間的通訊?),是不是可以運用這個MQ去進行系統的解耦。在簡歷中體現出來這塊東西,用MQ作解耦。

異步:現場畫個圖來說明一下,

A系統接收一個請求,需要在自己本地寫庫,還需要在BCD三個系統寫庫,自己本地寫庫要3ms,BCD三個系統分別寫庫要300ms、450ms、200ms。最終請求總延時是3 + 300 + 450 + 200 = 953ms,接近1s,用戶感覺搞個什麼東西,慢死了慢死了。

更改爲 異步後當消息發送到消息隊列  自行讓對應系統進行消費即可  所以給用戶的體驗爲20 + 5 = 25ms  ,快 好快!

削峯:每天0點到11點,A系統風平浪靜,每秒併發請求數量就100個。結果每次一到11點~1點,每秒併發請求數量突然會暴增到1萬條。但是系統最大的處理能力就只能是每秒鐘處理1000個請求啊。。。尷尬了,系統會死。。。

消息隊列有什麼優缺點?

優點:特殊場景解耦、異步、削峯

缺點

系統可用性降低:系統引入的外部依賴越多,越容易掛掉,本來你就是A系統調用BCD三個系統的接口就好了,人ABCD四個系統好好的,沒啥問題,你偏加個MQ進來,萬一MQ掛了咋整?MQ掛了,整套系統崩潰了,你不就完了麼。

系統複雜性提高:硬生生加個MQ進來,你怎麼保證消息沒有重複消費?怎麼處理消息丟失的情況?怎麼保證消息傳遞的順序性?頭大頭大,問題一大堆,痛苦不已

一致性問題:A系統處理完了直接返回成功了,人都以爲你這個請求就成功了;但是問題是,要是BCD三個系統那裏,BD兩個系統寫庫成功了,結果C系統寫庫失敗了,咋整?你這數據就不一致了。

所以消息隊列實際是一種非常複雜的架構,你引入它有很多好處,但是也得針對它帶來的壞處做各種額外的技術方案和架構來規避掉,最好之後,你會發現,媽呀,系統複雜度提升了一個數量級,也許是複雜了10倍。但是關鍵時刻,用,還是得用的。。。

三、目前流行的消息隊列優缺點對比

kafka、activemq、rabbitmq、rocketmq都有什麼優點和缺點啊?

常見的MQ其實就這幾種,別的還有很多其他MQ,但是比較冷門的,那麼就別多說了

作爲一個碼農,你起碼得知道各種mq的優點和缺點吧,咱們來畫個表格看看

特性

ActiveMQ

RabbitMQ

RocketMQ

 

Kafka

單機吞吐量 萬級,吞吐量比RocketMQ和Kafka要低了一個數量級 萬級,吞吐量比RocketMQ和Kafka要低了一個數量級 10萬級,RocketMQ也是可以支撐高吞吐的一種MQ

10萬級別,這是kafka最大的優點,就是吞吐量高。

 

一般配合大數據類的系統來進行實時數據計算、日誌採集等場景

topic數量對吞吐量的影響    

topic可以達到幾百,幾千個的級別,吞吐量會有較小幅度的下降

 

 

這是RocketMQ的一大優勢,在同等機器下,可以支撐大量的topic

topic從幾十個到幾百個的時候,吞吐量會大幅度下降

 

所以在同等機器下,kafka儘量保證topic數量不要過多。如果要支撐大規模topic,需要增加更多的機器資源

時效性 ms級

微秒級,這是rabbitmq的一大特點,延遲是最低的

ms級

延遲在ms級以內

可用性 高,基於主從架構實現高可用性 高,基於主從架構實現高可用性 非常高,分佈式架構 非常高,kafka是分佈式的,一個數據多個副本,少數機器宕機,不會丟失數據,不會導致不可用
消息可靠性 有較低的概率丟失數據

基於erlang開發,所以併發能力很強,性能極其好,延時很低

MQ功能較爲完善,還是分佈式的,擴展性好

功能較爲簡單,主要支持簡單的MQ功能,在大數據領域的實時計算以及日誌採集被大規模使用,是事實上的標準

功能支持

MQ領域的功能極其完備

基於erlang開發,所以併發能力很強,性能極其好,延時很低

MQ功能較爲完善,還是分佈式的,擴展性好

功能較爲簡單,主要支持簡單的MQ功能,在大數據領域的實時計算以及日誌採集被大規模使用,是事實上的標準

優劣勢總結

非常成熟,功能強大,在業內大量的公司以及項目中都有應用

 

偶爾會有較低概率丟失消息

 

而且現在社區以及國內應用都越來越少,官方社區現在對ActiveMQ 5.x維護越來越少,幾個月才發佈一個版本

 

而且確實主要是基於解耦和異步來用的,較少在大規模吞吐的場景中使用

erlang語言開發,性能極其好,延時很低;

 

吞吐量到萬級,MQ功能比較完備

 

而且開源提供的管理界面非常棒,用起來很好用

 

社區相對比較活躍,幾乎每個月都發布幾個版本分

 

在國內一些互聯網公司近幾年用rabbitmq也比較多一些

 

但是問題也是顯而易見的,RabbitMQ確實吞吐量會低一些,這是因爲他做的實現機制比較重。

 

而且erlang開發,國內有幾個公司有實力做erlang源碼級別的研究和定製?如果說你沒這個實力的話,確實偶爾會有一些問題,你很難去看懂源碼,你公司對這個東西的掌控很弱,基本職能依賴於開源社區的快速維護和修復bug。

 

而且rabbitmq集羣動態擴展會很麻煩,不過這個我覺得還好。其實主要是erlang語言本身帶來的問題。很難讀源碼,很難定製和掌控。

接口簡單易用,而且畢竟在阿里大規模應用過,有阿里品牌保障

 

日處理消息上百億之多,可以做到大規模吞吐,性能也非常好,分佈式擴展也很方便,社區維護還可以,可靠性和可用性都是ok的,還可以支撐大規模的topic數量,支持複雜MQ業務場景

 

而且一個很大的優勢在於,阿里出品都是java系的,我們可以自己閱讀源碼,定製自己公司的MQ,可以掌控

 

社區活躍度相對較爲一般,不過也還可以,文檔相對來說簡單一些,然後接口這塊不是按照標準JMS規範走的有些系統要遷移需要修改大量代碼

 

還有就是阿里出臺的技術,你得做好這個技術萬一被拋棄,社區黃掉的風險,那如果你們公司有技術實力我覺得用RocketMQ挺好的

kafka的特點其實很明顯,就是僅僅提供較少的核心功能,但是提供超高的吞吐量,ms級的延遲,極高的可用性以及可靠性,而且分佈式可以任意擴展

 

同時kafka最好是支撐較少的topic數量即可,保證其超高吞吐量

 

而且kafka唯一的一點劣勢是有可能消息重複消費,那麼對數據準確性會造成極其輕微的影響,在大數據領域中以及日誌採集中,這點輕微影響可以忽略

 

這個特性天然適合大數據實時計算以及日誌收集

四、總結

一般的業務系統要引入MQ,最早大家都用ActiveMQ,但是現在確實大家用的不多了,沒經過大規模吞吐量場景的驗證,社區也不是很活躍,所以大家還是算了吧,我個人不推薦用這個了;

後來大家開始用RabbitMQ,但是確實erlang語言阻止了大量的java工程師去深入研究和掌控他,對公司而言,幾乎處於不可控的狀態,但是確實人是開源的,比較穩定的支持,活躍度也高;

不過現在確實越來越多的公司,會去用RocketMQ,確實很不錯,但是我提醒一下自己想好社區萬一突然黃掉的風險,對自己公司技術實力有絕對自信的,我推薦用RocketMQ,否則回去老老實實用RabbitMQ吧,人是活躍開源社區,絕對不會黃

所以中小型公司,技術實力較爲一般,技術挑戰不是特別高,用RabbitMQ是不錯的選擇(我們項目也正在使用這個^_^);大型公司,基礎架構研發實力較強,用RocketMQ是很好的選擇

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

五、消息中間件的組成

1 Broker

消息服務器,作爲server提供消息核心服務

2 Producer

消息生產者,業務的發起方,負責生產消息傳輸給broker,

3 Consumer

消息消費者,業務的處理方,負責從broker獲取消息並進行業務邏輯處理

4 Topic

主題,發佈訂閱模式下的消息統一彙集地,不同生產者向topic發送消息,由MQ服務器分發到不同的訂閱者,實現消息的       廣播

5 Queue

隊列,PTP模式下,特定生產者向特定queue發送消息,消費者訂閱特定的queue完成指定消息的接收

6 Message

消息體,根據不同通信協議定義的固定格式進行編碼的數據包,來封裝業務數據,實現消息的傳輸

六、消息中間件模式分類

1 點對點

PTP點對點:使用queue作爲通信載體

說明: 
消息生產者生產消息發送到queue中,然後消息消費者從queue中取出並且消費消息。 
消息被消費以後,queue中不再存儲,所以消息消費者不可能消費到已經被消費的消息。 Queue支持存在多個消費者,但是對一個消息而言,只會有一個消費者可以消費。

2 發佈/訂閱

Pub/Sub發佈訂閱(廣播):使用topic作爲通信載體 

說明: 
消息生產者(發佈)將消息發佈到topic中,同時有多個消息消費者(訂閱)消費該消息。和點對點方式不同,發佈到topic的消息會被所有訂閱者消費。

queue實現了負載均衡,將producer生產的消息發送到消息隊列中,由多個消費者消費。但一個消息只能被一個消費者接受,當沒有消費者可用時,這個消息會被保存直到有一個可用的消費者。 
topic實現了發佈和訂閱,當你發佈一個消息,所有訂閱這個topic的服務都能得到這個消息,所以從1到N個訂閱者都能得到一個消息的拷貝。

七、消息中間件的優勢

1 系統解耦

交互系統之間沒有直接的調用關係,只是通過消息傳輸,故系統侵入性不強,耦合度低。

2 提高系統響應時間

例如原來的一套邏輯,完成支付可能涉及先修改訂單狀態、計算會員積分、通知物流配送幾個邏輯才能完成;通過MQ架構設計,就可將緊急重要(需要立刻響應)的業務放到該調用方法中,響應要求不高的使用消息隊列,放到MQ隊列中,供消費者處理。

3 爲大數據處理架構提供服務

通過消息作爲整合,大數據的背景下,消息隊列還與實時處理架構整合,爲數據處理提供性能支持。

4 Java消息服務——JMS

Java消息服務(Java Message Service,JMS)應用程序接口是一個Java平臺中關於面向消息中間件(MOM)的API,用於在兩個應用程序之間,或分佈式系統中發送消息,進行異步通信。 
JMS中的P2P和Pub/Sub消息模式:點對點(point to point, queue)與發佈訂閱(publish/subscribe,topic)最初是由JMS定義的。這兩種模式主要區別或解決的問題就是發送到隊列的消息能否重複消費(多訂閱)。

八、消息中間件應用場景

1 異步通信

有些業務不想也不需要立即處理消息。消息隊列提供了異步處理機制,允許用戶把一個消息放入隊列,但並不立即處理它。想向隊列中放入多少消息就放多少,然後在需要的時候再去處理它們。

2 解耦

降低工程間的強依賴程度,針對異構系統進行適配。在項目啓動之初來預測將來項目會碰到什麼需求,是極其困難的。通過消息系統在處理過程中間插入了一個隱含的、基於數據的接口層,兩邊的處理過程都要實現這一接口,當應用發生變化時,可以獨立的擴展或修改兩邊的處理過程,只要確保它們遵守同樣的接口約束。

3 冗餘

有些情況下,處理數據的過程會失敗。除非數據被持久化,否則將造成丟失。消息隊列把數據進行持久化直到它們已經被完全處理,通過這一方式規避了數據丟失風險。許多消息隊列所採用的”插入-獲取-刪除”範式中,在把一個消息從隊列中刪除之前,需要你的處理系統明確的指出該消息已經被處理完畢,從而確保你的數據被安全的保存直到你使用完畢。

4 擴展性

因爲消息隊列解耦了你的處理過程,所以增大消息入隊和處理的頻率是很容易的,只要另外增加處理過程即可。不需要改變代碼、不需要調節參數。便於分佈式擴容。

5 過載保護

在訪問量劇增的情況下,應用仍然需要繼續發揮作用,但是這樣的突發流量無法提取預知;如果以爲了能處理這類瞬間峯值訪問爲標準來投入資源隨時待命無疑是巨大的浪費。使用消息隊列能夠使關鍵組件頂住突發的訪問壓力,而不會因爲突發的超負荷的請求而完全崩潰。

6 可恢復性

系統的一部分組件失效時,不會影響到整個系統。消息隊列降低了進程間的耦合度,所以即使一個處理消息的進程掛掉,加入隊列中的消息仍然可以在系統恢復後被處理。

7 順序保證

在大多使用場景下,數據處理的順序都很重要。大部分消息隊列本來就是排序的,並且能保證數據會按照特定的順序來處理。

8 緩衝

在任何重要的系統中,都會有需要不同的處理時間的元素。消息隊列通過一個緩衝層來幫助任務最高效率的執行,該緩衝有助於控制和優化數據流經過系統的速度。以調節系統響應時間。

9 數據流處理

分佈式系統產生的海量數據流,如:業務日誌、監控數據、用戶行爲等,針對這些數據流進行實時或批量採集彙總,然後進行大數據分析是當前互聯網的必備技術,通過消息隊列完成此類數據收集是最好的選擇。

九、消息中間件常用協議

1 AMQP協議

AMQP即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不同產品,不同開發語言等條件的限制。 
優點:可靠、通用

2 MQTT協議

MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸)是IBM開發的一個即時通訊協議,有可能成爲物聯網的重要組成部分。該協議支持所有平臺,幾乎可以把所有聯網物品和外部連接起來,被用來當做傳感器和致動器(比如通過Twitter讓房屋聯網)的通信協議。 
優點:格式簡潔、佔用帶寬小、移動端通信、PUSH、嵌入式系統

3 STOMP協議

STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息協議,是一種爲MOM(Message Oriented Middleware,面向消息的中間件)設計的簡單文本協議。STOMP提供一個可互操作的連接格式,允許客戶端與任意STOMP消息代理(Broker)進行交互。 
優點:命令模式(非topic\queue模式)

4 XMPP協議

XMPP(可擴展消息處理現場協議,Extensible Messaging and Presence Protocol)是基於可擴展標記語言(XML)的協議,多用於即時消息(IM)以及在線現場探測。適用於服務器之間的準即時操作。核心是基於XML流傳輸,這個協議可能最終允許因特網用戶向因特網上的其他任何人發送即時消息,即使其操作系統和瀏覽器不同。 
優點:通用公開、兼容性強、可擴展、安全性高,但XML編碼格式佔用帶寬大

5 其他基於TCP/IP自定義的協議

有些特殊框架(如:redis、kafka、zeroMq等)根據自身需要未嚴格遵循MQ規範,而是基於TCP\IP自行封裝了一套協議,通過網絡socket接口進行傳輸,實現了MQ的功能。

十、常見消息中間件MQ介紹

1 RocketMQ

阿里系下開源的一款分佈式、隊列模型的消息中間件,原名Metaq,3.0版本名稱改爲RocketMQ,是阿里參照kafka設計思想使用java實現的一套mq。同時將阿里系內部多款mq產品(Notify、metaq)進行整合,只維護核心功能,去除了所有其他運行時依賴,保證核心功能最簡化,在此基礎上配合阿里上述其他開源產品實現不同場景下mq的架構,目前主要多用於訂單交易系統。

具有以下特點:

  • 能夠保證嚴格的消息順序
  • 提供針對消息的過濾功能
  • 提供豐富的消息拉取模式
  • 高效的訂閱者水平擴展能力
  • 實時的消息訂閱機制
  • 億級消息堆積能力

官方提供了一些不同於kafka的對比差異: 
https://rocketmq.apache.org/docs/motivation/

2 RabbitMQ

https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html

使用Erlang編寫的一個開源的消息隊列,本身支持很多的協議:AMQP,XMPP, SMTP,STOMP,也正是如此,使的它變的非常重量級,更適合於企業級的開發。同時實現了Broker架構,核心思想是生產者不會將消息直接發送給隊列,消息在發送給客戶端時先在中心隊列排隊。對路由(Routing),負載均衡(Load balance)、數據持久化都有很好的支持。多用於進行企業級的ESB整合。

3 ActiveMQ

Apache下的一個子項目。使用Java完全支持JMS1.1和J2EE 1.4規範的 JMS Provider實現,少量代碼就可以高效地實現高級應用場景。可插拔的傳輸協議支持,比如:in-VM, TCP, SSL, NIO, UDP, multicast, JGroups and JXTA transports。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多種語言客戶端 C++、Java、.Net,、Python、 Php、 Ruby等。

4 Redis

使用C語言開發的一個Key-Value的NoSQL數據庫,開發維護很活躍,雖然它是一個Key-Value數據庫存儲系統,但它本身支持MQ功能,所以完全可以當做一個輕量級的隊列服務來使用。對於RabbitMQ和Redis的入隊和出隊操作,各執行100萬次,每10萬次記錄一次執行時間。測試數據分爲128Bytes、512Bytes、1K和10K四個不同大小的數據。實驗表明:入隊時,當數據比較小時Redis的性能要高於RabbitMQ,而如果數據大小超過了10K,Redis則慢的無法忍受;出隊時,無論數據大小,Redis都表現出非常好的性能,而RabbitMQ的出隊性能則遠低於Redis。

5 Kafka

http://kafka.apachecn.org/intro.html

Apache下的一個子項目,使用scala實現的一個高性能分佈式Publish/Subscribe消息隊列系統,具有以下特性:

  • 快速持久化:通過磁盤順序讀寫與零拷貝機制,可以在O(1)的系統開銷下進行消息持久化;
  • 高吞吐:在一臺普通的服務器上既可以達到10W/s的吞吐速率;
  • 高堆積:支持topic下消費者較長時間離線,消息堆積量大;
  • 完全的分佈式系統:Broker、Producer、Consumer都原生自動支持分佈式,依賴zookeeper自動實現複雜均衡;
  • 支持Hadoop數據並行加載:對於像Hadoop的一樣的日誌數據和離線分析系統,但又要求實時處理的限制,這是一個可行的解決方案。

6 ZeroMQ

號稱最快的消息隊列系統,專門爲高吞吐量/低延遲的場景開發,在金融界的應用中經常使用,偏重於實時數據通信場景。ZMQ能夠實現RabbitMQ不擅長的高級/複雜的隊列,但是開發人員需要自己組合多種技術框架,開發成本高。因此ZeroMQ具有一個獨特的非中間件的模式,更像一個socket library,你不需要安裝和運行一個消息服務器或中間件,因爲你的應用程序本身就是使用ZeroMQ API完成邏輯服務的角色。但是ZeroMQ僅提供非持久性的隊列,如果down機,數據將會丟失。如:Twitter的Storm中使用ZeroMQ作爲數據流的傳輸。

ZeroMQ套接字是與傳輸層無關的:ZeroMQ套接字對所有傳輸層協議定義了統一的API接口。默認支持 進程內(inproc) ,進程間(IPC) ,多播,TCP協議,在不同的協議之間切換隻要簡單的改變連接字符串的前綴。可以在任何時候以最小的代價從進程間的本地通信切換到分佈式下的TCP通信。ZeroMQ在背後處理連接建立,斷開和重連邏輯。

特性:

  • 無鎖的隊列模型:對於跨線程間的交互(用戶端和session)之間的數據交換通道pipe,採用無鎖的隊列算法CAS;在pipe的兩端註冊有異步事件,在讀或者寫消息到pipe的時,會自動觸發讀寫事件。
  • 批量處理的算法:對於批量的消息,進行了適應性的優化,可以批量的接收和發送消息。
  • 多核下的線程綁定,無須CPU切換:區別於傳統的多線程併發模式,信號量或者臨界區,zeroMQ充分利用多核的優勢,每個核綁定運行一個工作者線程,避免多線程之間的CPU切換開銷。

十一、比較

綜合選擇RabbitMq 

十二、如何保證消息隊列的高可用性

由於筆者只使用和實踐過RabbitMQ和Kafka,RocketMQ和ActiveMQ瞭解的不深,所以分析一下RabbitMQ和Kafka的高可用。
(一)RabbitMQ

RabbitMQ有三種模式:單機模式,普通集羣模式,鏡像集羣模式

(1)單機模式

單機模式平常使用在開發或者本地測試場景,一般就是測試是不是能夠正確的處理消息,生產上基本沒人去用單機模式,風險很大。

(2)普通集羣模式

普通集羣模式就是啓動多個RabbitMQ實例。在你創建的queue,只會放在一個rabbtimq實例上,但是每個實例都同步queue的元數據。在消費的時候完了,上如果連接到了另外一個實例,那麼那個實例會從queue所在實例上拉取數據過來。

這種方式確實很麻煩,也不怎麼好,沒做到所謂的分佈式,就是個普通集羣。因爲這導致你要麼消費者每次隨機連接一個實例然後拉取數據,要麼固定連接那個queue所在實例消費數據,前者有數據拉取的開銷,後者導致單實例性能瓶頸。

而且如果那個放queue的實例宕機了,會導致接下來其他實例就無法從那個實例拉取,如果你開啓了消息持久化,讓RabbitMQ落地存儲消息的話,消息不一定會丟,得等這個實例恢復了,然後纔可以繼續從這個queue拉取數據。

這方案主要是提高吞吐量的,就是說讓集羣中多個節點來服務某個queue的讀寫操作。

(3)鏡像集羣模式

鏡像集羣模式是所謂的RabbitMQ的高可用模式,跟普通集羣模式不一樣的是,你創建的queue,無論元數據還是queue裏的消息都會存在於多個實例上,然後每次你寫消息到queue的時候,都會自動把消息到多個實例的queue裏進行消息同步。

優點在於你任何一個實例宕機了,沒事兒,別的實例都可以用。缺點在於性能開銷太大和擴展性很低,同步所有實例,這會導致網絡帶寬和壓力很重,而且擴展性很低,每增加一個實例都會去包含已有的queue的所有數據,並沒有辦法線性擴展queue。

開啓鏡像集羣模式可以去RabbitMQ的管理控制檯去增加一個策略,指定要求數據同步到所有節點的,也可以要求就同步到指定數量的節點,然後你再次創建queue的時候,應用這個策略,就會自動將數據同步到其他的節點上去了。
(二)Kafka

Kafka天生就是一個分佈式的消息隊列,它可以由多個broker組成,每個broker是一個節點;你創建一個topic,這個topic可以劃分爲多個partition,每個partition可以存在於不同的broker上,每個partition就放一部分數據。

kafka 0.8以前,是沒有HA機制的,就是任何一個broker宕機了,那個broker上的partition就廢了,沒法寫也沒法讀,沒有什麼高可用性可言。

kafka 0.8以後,提供了HA機制,就是replica副本機制。kafka會均勻的將一個partition的所有replica分佈在不同的機器上,來提高容錯性。每個partition的數據都會同步到吉他機器上,形成自己的多個replica副本。然後所有replica會選舉一個leader出來,那麼生產和消費都去leader,其他replica就是follower,leader會同步數據給follower。當leader掛了會自動去找replica,然後會再選舉一個leader出來,這樣就具有高可用性了。

寫數據的時候,生產者就寫leader,然後leader將數據落地寫本地磁盤,接着其他follower自己主動從leader來pull數據。一旦所有follower同步好數據了,就會發送ack給leader,leader收到所有follower的ack之後,就會返回寫成功的消息給生產者。(當然,這只是其中一種模式,還可以適當調整這個行爲)

消費的時候,只會從leader去讀,但是隻有一個消息已經被所有follower都同步成功返回ack的時候,這個消息纔會被消費者讀到。


十三、如何保證消息消費時的冪等性

其實消息重複消費的主要原因在於回饋機制(RabbitMQ是ack,Kafka是offset),在某些場景中我們採用的回饋機制不同,原因也不同,例如消費者消費完消息後回覆ack, 但是剛消費完還沒來得及提交系統就重啓了,這時候上來就pull消息的時候由於沒有提交ack或者offset,消費的還是上條消息。

那麼如何怎麼來保證消息消費的冪等性呢?實際上我們只要保證多條相同的數據過來的時候只處理一條或者說多條處理和處理一條造成的結果相同即可,但是具體怎麼做要根據業務需求來定,例如入庫消息,先查一下消息是否已經入庫啊或者說搞個唯一約束啊什麼的,還有一些是天生保證冪等性就根本不用去管,例如redis就是天然冪等性。

還有一個問題,消費者消費消息的時候在某些場景下要放過消費不了的消息,遇到消費不了的消息通過日誌記錄一下或者搞個什麼措施以後再來處理,但是一定要放過消息,因爲在某些場景下例如spring-rabbitmq的默認回饋策略是出現異常就沒有提交ack,導致了一直在重發那條消費異常的消息,而且一直還消費不了,這就尷尬了,後果你會懂的。
六、如何保證消息的可靠性傳輸?

十四、分析一下RabbitMQ和Kafka的消息可靠性傳輸的問題。

(一)RabbitMQ

(1)生產者弄丟了數據
生產者將數據發送到RabbitMQ的時候,可能數據就在半路給搞丟了,因爲網絡啥的問題,都有可能。此時可以選擇用RabbitMQ提供的事務功能,就是生產者發送數據之前開啓RabbitMQ事務(channel.txSelect),然後發送消息,如果消息沒有成功被RabbitMQ接收到,那麼生產者會收到異常報錯,此時就可以回滾事務(channel.txRollback),然後重試發送消息;如果收到了消息,那麼可以提交事務(channel.txCommit)。但是問題是,RabbitMQ事務機制一搞,基本上吞吐量會下來,因爲太耗性能。

所以一般來說,如果你要確保說寫RabbitMQ的消息別丟,可以開啓confirm模式,在生產者那裏設置開啓confirm模式之後,你每次寫的消息都會分配一個唯一的id,然後如果寫入了RabbitMQ中,RabbitMQ會給你回傳一個ack消息,告訴你說這個消息ok了。如果RabbitMQ沒能處理這個消息,會回調你一個nack接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存裏維護每個消息id的狀態,如果超過一定時間還沒接收到這個消息的回調,那麼你可以重發。

事務機制和cnofirm機制最大的不同在於,事務機制是同步的,你提交一個事務之後會阻塞在那兒,但是confirm機制是異步的,你發送個消息之後就可以發送下一個消息,然後那個消息RabbitMQ接收了之後會異步回調你一個接口通知你這個消息接收到了。

所以一般在生產者這塊避免數據丟失,都是用confirm機制的。

(2)RabbitMQ弄丟了數據

就是RabbitMQ自己弄丟了數據,這個你必須開啓RabbitMQ的持久化,就是消息寫入之後會持久化到磁盤,哪怕是RabbitMQ自己掛了,恢復之後會自動讀取之前存儲的數據,一般數據不會丟。除非極其罕見的是,RabbitMQ還沒持久化,自己就掛了,可能導致少量數據會丟失的,但是這個概率較小。

設置持久化有兩個步驟第一個是創建queue的時候將其設置爲持久化的,這樣就可以保證RabbitMQ持久化queue的元數據,但是不會持久化queue裏的數據;第二個是發送消息的時候將消息的deliveryMode設置爲2,就是將消息設置爲持久化的,此時RabbitMQ就會將消息持久化到磁盤上去。必須要同時設置這兩個持久化才行,RabbitMQ哪怕是掛了,再次重啓,也會從磁盤上重啓恢復queue,恢復這個queue裏的數據。

而且持久化可以跟生產者那邊的confirm機制配合起來,只有消息被持久化到磁盤之後,纔會通知生產者ack了,所以哪怕是在持久化到磁盤之前,RabbitMQ掛了,數據丟了,生產者收不到ack,你也是可以自己重發的。

哪怕是你給RabbitMQ開啓了持久化機制,也有一種可能,就是這個消息寫到了RabbitMQ中,但是還沒來得及持久化到磁盤上,結果不巧,此時RabbitMQ掛了,就會導致內存裏的一點點數據會丟失。

(3)消費端弄丟了數據

RabbitMQ如果丟失了數據,主要是因爲你消費的時候,剛消費到,還沒處理,結果進程掛了,比如重啓了,那麼就尷尬了,RabbitMQ認爲你都消費了,這數據就丟了。

這個時候得用RabbitMQ提供的ack機制,簡單來說,就是你關閉RabbitMQ自動ack,可以通過一個api來調用就行,然後每次你自己代碼裏確保處理完的時候,再程序裏ack一把。這樣的話,如果你還沒處理完,不就沒有ack?那RabbitMQ就認爲你還沒處理完,這個時候RabbitMQ會把這個消費分配給別的consumer去處理,消息是不會丟的。

(二)Kafka

(1)消費端弄丟了數據

唯一可能導致消費者弄丟數據的情況,就是說,你那個消費到了這個消息,然後消費者那邊自動提交了offset,讓kafka以爲你已經消費好了這個消息,其實你剛準備處理這個消息,你還沒處理,你自己就掛了,此時這條消息就丟咯。

大家都知道kafka會自動提交offset,那麼只要關閉自動提交offset,在處理完之後自己手動提交offset,就可以保證數據不會丟。但是此時確實還是會重複消費,比如你剛處理完,還沒提交offset,結果自己掛了,此時肯定會重複消費一次,自己保證冪等性就好了。

生產環境碰到的一個問題,就是說我們的kafka消費者消費到了數據之後是寫到一個內存的queue裏先緩衝一下,結果有的時候,你剛把消息寫入內存queue,然後消費者會自動提交offset。

然後此時我們重啓了系統,就會導致內存queue裏還沒來得及處理的數據就丟失了

(2)kafka弄丟了數據

這塊比較常見的一個場景,就是kafka某個broker宕機,然後重新選舉partiton的leader時。大家想想,要是此時其他的follower剛好還有些數據沒有同步,結果此時leader掛了,然後選舉某個follower成leader之後,他不就少了一些數據?這就丟了一些數據啊。

生產環境也遇到過,我們也是,之前kafka的leader機器宕機了,將follower切換爲leader之後,就會發現說這個數據就丟了。

所以此時一般是要求起碼設置如下4個參數:

    給這個topic設置replication.factor參數:這個值必須大於1,要求每個partition必須有至少2個副本。
    在kafka服務端設置min.insync.replicas參數:這個值必須大於1,這個是要求一個leader至少感知到有至少一個follower還跟自己保持聯繫,沒掉隊,這樣才能確保leader掛了還有一個follower吧。
    在producer端設置acks=all:這個是要求每條數據,必須是寫入所有replica之後,才能認爲是寫成功了。
    在producer端設置retries=MAX(很大很大很大的一個值,無限次重試的意思):這個是要求一旦寫入失敗,就無限重試,卡在這裏了。

(3)生產者會不會弄丟數據

如果按照上述的思路設置了ack=all,一定不會丟,要求是,你的leader接收到消息,所有的follower都同步到了消息之後,才認爲本次寫成功了。如果沒滿足這個條件,生產者會自動不斷的重試,重試無限次


十五、如何保證消息的順序性

因爲在某些情況下我們扔進MQ中的消息是要嚴格保證順序的,尤其涉及到訂單什麼的業務需求,消費的時候也是要嚴格保證順序,不然會出大問題的。

先看看順序會錯亂的倆場景

    rabbitmq:一個queue,多個consumer,這不明顯亂了
    kafka:一個topic,一個partition,一個consumer,內部多線程,這不也明顯亂了
    如何來保證消息的順序性呢?
    rabbitmq:拆分多個queue,每個queue一個consumer,就是多一些queue而已,確實是麻煩點;或者就一個queue但是對應一個consumer,然後這個consumer內部用內存隊列做排隊,然後分發給底層不同的worker來處理。
    kafka:一個topic,一個partition,一個consumer,內部單線程消費,寫N個內存queue,然後N個線程分別消費一個內存queue即可。

十六、如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以後該怎麼處理?有幾百萬消息持續積壓幾小時怎麼解決?

(一)大量消息在mq裏積壓了幾個小時了還沒解決

幾千萬條數據在MQ裏積壓了七八個小時,從下午4點多,積壓到了晚上很晚,10點多,11點多
這個是我們真實遇到過的一個場景,確實是線上故障了,這個時候要不然就是修復consumer的問題,讓他恢復消費速度,然後傻傻的等待幾個小時消費完畢。這個肯定不能在面試的時候說吧。

一個消費者一秒是1000條,一秒3個消費者是3000條,一分鐘是18萬條,1000多萬條,所以如果你積壓了幾百萬到上千萬的數據,即使消費者恢復了,也需要大概1小時的時間才能恢復過來。

一般這個時候,只能操作臨時緊急擴容了,具體操作步驟和思路如下:

    先修復consumer的問題,確保其恢復消費速度,然後將現有cnosumer都停掉。
    新建一個topic,partition是原來的10倍,臨時建立好原先10倍或者20倍的queue數量。
    然後寫一個臨時的分發數據的consumer程序,這個程序部署上去消費積壓的數據,消費之後不做耗時的處理,直接均勻輪詢寫入臨時建立好的10倍數量的queue。
    接着臨時徵用10倍的機器來部署consumer,每一批consumer消費一個臨時queue的數據。

    這種做法相當於是臨時將queue資源和consumer資源擴大10倍,以正常的10倍速度來消費數據。
    等快速消費完積壓數據之後,得恢復原先部署架構,重新用原先的consumer機器來消費消息。

(二)消息隊列過期失效問題

假設你用的是rabbitmq,rabbitmq是可以設置過期時間的,就是TTL,如果消息在queue中積壓超過一定的時間就會被rabbitmq給清理掉,這個數據就沒了。那這就是第二個坑了。這就不是說數據會大量積壓在mq裏,而是大量的數據會直接搞丟。

這個情況下,就不是說要增加consumer消費積壓的消息,因爲實際上沒啥積壓,而是丟了大量的消息。我們可以採取一個方案,就是批量重導,這個我們之前線上也有類似的場景幹過。就是大量積壓的時候,我們當時就直接丟棄數據了,然後等過了高峯期以後,比如大家一起喝咖啡熬夜到晚上12點以後,用戶都睡覺了。

這個時候我們就開始寫程序,將丟失的那批數據,寫個臨時程序,一點一點的查出來,然後重新灌入mq裏面去,把白天丟的數據給他補回來。也只能是這樣了。

假設1萬個訂單積壓在mq裏面,沒有處理,其中1000個訂單都丟了,你只能手動寫程序把那1000個訂單給查出來,手動發到mq裏去再補一次。


  (三)   消息隊列滿了怎麼搞?

如果走的方式是消息積壓在mq裏,那麼如果你很長時間都沒處理掉,此時導致mq都快寫滿了,咋辦?這個還有別的辦法嗎?沒有,誰讓你第一個方案執行的太慢了,你臨時寫程序,接入數據來消費,消費一個丟棄一個,都不要了,快速消費掉所有的消息。

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