深入理解阿裡分散式消息中間件之消息隊列

来源:https://www.cnblogs.com/lfs2640666960/archive/2019/08/16/11366742.html
-Advertisement-
Play Games

寫這篇文章,就是希望讀者把本文提出的這幾個問題,經過深刻的準備後,一般來說,能囊括大部分的消息隊列的知識點。 ...


1、為什麼要使用消息隊列?

分析:一個用消息隊列的人,不知道為啥用,有點尷尬。沒有複習這點,很容易被問蒙,然後就開始胡扯了。

回答:這個問題,咱只答三個最主要的應用場景(不可否認還有其他的,但是只答三個主要的),即以下六個字:解耦、非同步、削峰

(1)解耦

傳統模式:

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

傳統模式的缺點:

  • 系統間耦合性太強,如上圖所示,系統A在代碼中直接調用系統B和系統C的代碼,如果將來D系統接入,系統A還需要修改代碼,過於麻煩!

 

中間件模式:

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

中間件模式的的優點:

  • 將消息寫入消息隊列,需要消息的系統自己從消息隊列中訂閱,從而系統A不需要做任何修改。

 

(2)非同步

傳統模式:

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

傳統模式的缺點:

  • 一些非必要的業務邏輯以同步的方式運行,太耗費時間。

中間件模式:

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

中間件模式的的優點:

  • 將消息寫入消息隊列,非必要的業務邏輯以非同步的方式運行,加快響應速度

(3)削峰

傳統模式

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

傳統模式的缺點:

  • 併發量大的時候,所有的請求直接懟到資料庫,造成資料庫連接異常

 

中間件模式:

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

中間件模式的的優點:

  • 系統A慢慢的按照資料庫能處理的併發量,從消息隊列中慢慢拉取消息。在生產中,這個短暫的高峰期積壓是允許的。

2、使用了消息隊列會有什麼缺點?

分析:一個使用了MQ的項目,如果連這個問題都沒有考慮過,就把MQ引進去了,那就給自己的項目帶來了風險。

我們引入一個技術,要對這個技術的弊端有充分的認識,才能做好預防。要記住,不要給公司挖坑!

回答:回答也很容易,從以下兩個個角度來答

  • 系統可用性降低:
  • 你想啊,本來其他系統只要運行好好的,那你的系統就是正常的。
  • 現在你非要加個消息隊列進去,那消息隊列掛了,你的系統不是呵呵了。因此,系統可用性降低
  • 系統複雜性增加:
  • 要多考慮很多方面的問題,比如一致性問題、如何保證消息不被重覆消費,如何保證保證消息可靠傳輸。
  • 因此,需要考慮的東西更多,系統複雜性增大。

但是,我們該用還是要用的。

3、消息隊列如何選型?

先說一下,博主只會ActiveMQ,RabbitMQ,RocketMQ,Kafka,對什麼ZeroMQ等其他MQ沒啥理解,因此只能基於這四種MQ給出回答。

分析:既然在項目中用了MQ,肯定事先要對業界流行的MQ進行調研,如果連每種MQ的優缺點都沒瞭解清楚,就拍腦袋依據喜好,用了某種MQ,還是給項目挖坑。

如果面試官問:"你為什麼用這種MQ?。"你直接回答"領導決定的。"這種回答就很LOW了。

還是那句話,不要給公司挖坑。

我們可以看出,RabbitMQ版本發佈比ActiveMq頻繁很多。至於RocketMQ和kafka就不帶大家看了,總之也比ActiveMQ活躍的多。詳情,可自行查閱。

再來一個性能對比表

 

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

 

綜合上面的材料得出以下兩點:

(1)中小型軟體公司,建議選RabbitMQ.

一方面,erlang語言天生具備高併發的特性,而且他的管理界面用起來十分方便。

正所謂,成也蕭何,敗也蕭何!他的弊端也在這裡,雖然RabbitMQ是開源的,然而國內有幾個能定製化開發erlang的程式員呢?

所幸,RabbitMQ的社區十分活躍,可以解決開發過程中遇到的bug,這點對於中小型公司來說十分重要。

不考慮rocketmq和kafka的原因是,一方面中小型軟體公司不如互聯網公司,數據量沒那麼大,選消息中間件,應首選功能比較完備的,所以kafka排除。

不考慮rocketmq的原因是,rocketmq是阿裡出品,如果阿裡放棄維護rocketmq,中小型公司一般抽不出人來進行rocketmq的定製化開發,因此不推薦。

(2)大型軟體公司,根據具體使用在rocketMq和kafka之間二選一

一方面,大型軟體公司,具備足夠的資金搭建分散式環境,也具備足夠大的數據量。

針對rocketMQ,大型軟體公司也可以抽出人手對rocketMQ進行定製化開發,畢竟國內有能力改JAVA源碼的人,還是相當多的。

至於kafka,根據業務場景選擇,如果有日誌採集功能,肯定是首選kafka了。具體該選哪個,看使用場景。

4、如何保證消息隊列是高可用的?

分析:在第二點說過了,引入消息隊列後,系統的可用性下降。在生產中,沒人使用單機模式的消息隊列。

因此,作為一個合格的程式員,應該對消息隊列的高可用有很深刻的瞭解。

如果面試的時候,面試官問,你們的消息中間件如何保證高可用的?

如果你的回答只是表明自己只會訂閱和發佈消息,面試官就會懷疑你是不是只是自己搭著玩,壓根沒在生產用過。

因此,請做一個愛思考,會思考,懂思考的程式員。

回答:這問題,其實要對消息隊列的集群模式要有深刻瞭解,才好回答。

以rcoketMQ為例,他的集群就有多master 模式、多master多slave非同步複製模式、多 master多slave同步雙寫模式。

多master多slave模式部署架構圖(網上找的,偷個懶,懶得畫):

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

其實博主第一眼看到這個圖,就覺得和kafka好像,只是NameServer集群,在kafka中是用zookeeper代替,都是用來保存和發現master和slave用的。

通信過程如下:

Producer 與 NameServer集群中的其中一個節點(隨機選擇)建立長連接,定期從 NameServer 獲取 Topic 路由信息,並向提供 Topic 服務的 Broker Master 建立長連接,且定時向 Broker 發送心跳。

Producer 只能將消息發送到 Broker master,但是 Consumer 則不一樣,它同時和提供 Topic 服務的 Master 和 Slave建立長連接,既可以從 Broker Master 訂閱消息,也可以從 Broker Slave 訂閱消息。

那麼kafka呢,為了對比說明直接上kafka的拓補架構圖(也是找的,懶得畫)

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

如上圖所示,一個典型的Kafka集群中包含若幹Producer(可以是web前端產生的Page View,或者是伺服器日誌,系統CPU、Memory等),若幹broker(Kafka支持水平擴展,一般broker數量越多,集群吞吐率越高),若幹Consumer Group,以及一個Zookeeper集群。

Kafka通過Zookeeper管理集群配置,選舉leader,以及在Consumer Group發生變化時進行rebalance。

Producer使用push模式將消息發佈到broker,Consumer使用pull模式從broker訂閱並消費消息。

至於rabbitMQ,也有普通集群和鏡像集群模式,自行去瞭解,比較簡單,兩小時即懂。

要求,在回答高可用的問題時,應該能邏輯清晰的畫出自己的MQ集群架構或清晰的敘述出來。

5、如何保證消息不被重覆消費?

分析:這個問題其實換一種問法就是,如何保證消息隊列的冪等性?

這個問題可以認為是消息隊列領域的基本問題。換句話來說,是在考察你的設計能力,這個問題的回答可以根據具體的業務場景來答,沒有固定的答案。

回答:先來說一下為什麼會造成重覆消費?

其實無論是那種消息隊列,造成重覆消費原因其實都是類似的。

正常情況下,消費者在消費消息時候,消費完畢後,會發送一個確認信息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發送的確認信息形式不同

例如RabbitMQ是發送一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標誌,kafka實際上有個offset的概念

簡單說一下(如果還不懂,可以關註我的公眾號:Java技術zhai,裡面有講解),就是每一個消息都有一個offset,kafka消費過消息後,需要提交offset,讓消息隊列知道自己已經消費過了。

那造成重覆消費的原因?

就是因為網路傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將該消息分發給其他的消費者。

如何解決?這個問題針對業務場景來答分以下幾點

(1)比如,你拿到這個消息做資料庫的insert操作。

那就容易了,給這個消息做一個唯一主鍵,那麼就算出現重覆消費的情況,就會導致主鍵衝突,避免資料庫出現臟數據。

(2)再比如,你拿到這個消息做redis的set的操作

那就容易了,不用解決。因為你無論set幾次結果都是一樣的,set操作本來就算冪等操作。

(3)如果上面兩種情況還不行,上大招。

準備一個第三方介質,來做消費記錄。以redis為例,給消息分配一個全局id,只要消費過該消息,將以K-V形式寫入redis。那消費者開始消費前,先去redis中查詢有沒消費記錄即可。

6、如何保證消費的可靠性傳輸?

分析:我們在使用消息隊列的過程中,應該做到消息不能多消費,也不能少消費。如果無法做到可靠性傳輸,可能給公司帶來千萬級別的財產損失。

同樣的,如果可靠性傳輸在使用過程中,沒有考慮到,這不是給公司挖坑麽,你可以拍拍屁股走了,公司損失的錢,誰承擔。

還是那句話,認真對待每一個項目,不要給公司挖坑

回答:其實這個可靠性傳輸,每種MQ都要從三個角度來分析:生產者弄丟數據、消息隊列弄丟數據、消費者弄丟數據

RabbitMQ

(1)生產者丟數據

從生產者弄丟數據這個角度來看,RabbitMQ提供transaction和confirm模式來確保生產者不丟消息。

transaction機制就是說,發送消息前,開啟事物(channel.txSelect()),然後發送消息,如果發送過程中出現什麼異常,事物就會回滾(channel.txRollback()),如果發送成功則提交事物(channel.txCommit())。

然而缺點就是吞吐量下降了。因此,按照博主的經驗,生產上用confirm模式的居多。

一旦channel進入confirm模式,所有在該通道上面發佈的消息都將會被指派一個唯一的ID(從1開始)

一旦消息被投遞到所有匹配的隊列之後,rabbitMQ就會發送一個Ack給生產者(包含消息的唯一ID)

這就使得生產者知道消息已經正確到達目的隊列了.如果rabiitMQ沒能處理該消息,則會發送一個Nack消息給你,你可以進行重試操作。

處理Ack和Nack的代碼如下所示(說好不上代碼的,偷偷上了):

 

channel.addConfirmListener(new ConfirmListener() {
 @Override
 public void handleNack(long deliveryTag, boolean multiple) throws IOException {
 System.out.println("nack: deliveryTag = "+deliveryTag+" multiple: "+multiple);
 }
 @Override
 public void handleAck(long deliveryTag, boolean multiple) throws IOException {
 System.out.println("ack: deliveryTag = "+deliveryTag+" multiple: "+multiple);
 }
});

 

(2)消息隊列丟數據

處理消息隊列丟數據的情況,一般是開啟持久化磁碟的配置。

這個持久化配置可以和confirm機制配合使用,你可以在消息持久化磁碟後,再給生產者發送一個Ack信號。

這樣,如果消息持久化磁碟之前,rabbitMQ陣亡了,那麼生產者收不到Ack信號,生產者會自動重發。

那麼如何持久化呢,這裡順便說一下吧,其實也很容易,就下麵兩步

1、將queue的持久化標識durable設置為true,則代表是一個持久的隊列

2、發送消息的時候將deliveryMode=2

這樣設置以後,rabbitMQ就算掛了,重啟後也能恢複數據

(3)消費者丟數據

消費者丟數據一般是因為採用了自動確認消息模式。

這種模式下,消費者會自動確認收到信息。這時rahbitMQ會立即將消息刪除,這種情況下如果消費者出現異常而沒能處理該消息,就會丟失該消息。

至於解決方案,採用手動確認消息即可。

kafka

一個天天用消息隊列的人,不知道為啥用 MQ,這就有點尷尬

 

Producer在發佈消息到某個Partition時,先通過ZooKeeper找到該Partition的Leader

然後無論該Topic的Replication Factor為多少(也即該Partition有多少個Replica),Producer只將該消息發送到該Partition的Leader。

Leader會將該消息寫入其本地Log。每個Follower都從Leader中pull數據。

針對上述情況,得出如下分析

(1)生產者丟數據

在kafka生產中,基本都有一個leader和多個follwer。follwer會去同步leader的信息。

因此,為了避免生產者丟數據,做如下兩點配置

  1. 第一個配置要在producer端設置acks=all。這個配置保證了,follwer同步完成後,才認為消息發送成功。
  2. 在producer端設置retries=MAX,一旦寫入失敗,這無限重試

(2)消息隊列丟數據

針對消息隊列丟數據的情況,無外乎就是,數據還沒同步,leader就掛了,這時zookpeer會將其他的follwer切換為leader,那數據就丟失了。

針對這種情況,應該做兩個配置。

  1. replication.factor參數,這個值必須大於1,即要求每個partition必須有至少2個副本
  2. min.insync.replicas參數,這個值必須大於1,這個是要求一個leader至少感知到有至少一個follower還跟自己保持聯繫

這兩個配置加上上面生產者的配置聯合起來用,基本可確保kafka不丟數據

(3)消費者丟數據

這種情況一般是自動提交了offset,然後你處理程式過程中掛了。kafka以為你處理好了。

再強調一次offset是幹嘛的

offset:指的是kafka的topic中的每個消費組消費的下標。

簡單的來說就是一條消息對應一個offset下標,每次消費數據的時候如果提交offset,那麼下次消費就會從提交的offset加一那裡開始消費。

比如一個topic中有100條數據,我消費了50條並且提交了,那麼此時的kafka服務端記錄提交的offset就是49(offset從0開始),那麼下次消費的時候offset就從50開始消費。

解決方案也很簡單,改成手動提交即可。

ActiveMQ和RocketMQ

大家自行查閱吧,如果想偷懶,也可以關註我的公眾號:Java技術zhai,裡面有講解過這一塊的內容。

7、如何保證消息的順序性?

分析:其實並非所有的公司都有這種業務需求,但是還是對這個問題要有所複習。

回答:針對這個問題,通過某種演算法,將需要保持先後順序的消息放到同一個消息隊列中(kafka中就是partition,rabbitMq中就是queue)。然後只用一個消費者去消費該隊列。

有的人會問:那如果為了吞吐量,有多個消費者去消費怎麼辦?

這個問題,沒有固定回答的套路。比如我們有一個微博的操作,發微博、寫評論、刪除微博,這三個非同步操作。如果是這樣一個業務場景,那隻要重試就行。

比如你一個消費者先執行了寫評論的操作,但是這時候,微博都還沒發,寫評論一定是失敗的,等一段時間。等另一個消費者,先執行寫評論的操作後,再執行,就可以成功。

總之,針對這個問題,我的觀點是保證入隊有序就行,出隊以後的順序交給消費者自己去保證,沒有固定套路。

總結

寫到這裡,希望讀者把本文提出的這幾個問題,經過深刻的準備後,一般來說,能囊括大部分的消息隊列的知識點。

如果面試官不問這幾個問題怎麼辦,簡單,自己把幾個問題講清楚,突出以下自己考慮的全面性。

最後,其實我不太提倡這樣突擊複習,希望大家打好基本功,做一個愛思考,懂思考,會思考的程式員。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Springboot 使用外部 Tomcat 1.修改 pom.xml,改為打 war 包 2.將 Springboot 內置 tomcat 作用域改為 3.重寫 SpringBootServletInitializer 4.maven 打包出 war 包後,放到 tomcat 的 webapps ...
  • 一、AOP是什麼 AOP(面向切麵編程),可以說是一種編程思想,其中的Spring AOP和AspectJ都是現實了這種編程思想。相對OOP(面向過程編程)來說,提供了另外一種編程方式,對於OOP過程中產生的橫切性問題,這些橫切性與業務無關,可以通過預編譯方式和運行期動態代理來實現。比如可以應用在: ...
  • 1.首先下載小程式開發工具 2.小程式中的wxml就相當於html , wxss就相當於css 3.佈局和html佈局幾乎一樣 4.寬度使用百分比 5.input框里的文字上下居中是用padding撐出來的 6.最下麵的文字靠右,view相當於一個塊元素,設定寬度後,text-align右對齊 簡單 ...
  • 1. JVM運行時劃分哪幾個區域?哪些區域是線程共用的?哪些區域是線程獨占的? JVM運行時一共劃分:程式計數器、虛擬機棧、堆、本地方法棧、方法區。 線程共用的數據區域:堆、方法區。 線程獨享的數據區域區域:程式計數器、虛擬機棧、本地方法棧。 2. 這幾個記憶體區域分別存放什麼數據? 程式計數器記錄當 ...
  • 一、賦值運算符 1.賦值類運算符包括兩種: (1)基本賦值運算符:= (2)擴展的賦值運算符: += -= *= /= &= 賦值類的運算符優先順序:先執行等號右邊的表達式,將執行結果賦值給左邊的變數 2.例子: 總結:擴展類的運算符不改變運算結果後的變數的類型 二、字元串的連接運算符 關於java中 ...
  • 前言 通過代碼片段分別介紹服務端渲染、客戶端渲染、對象緩存三種方式的寫法。 代碼片段僅供參考,具體實現需要根據業務場景自行適配,但思想都是一樣。 一、服務端渲染方式 1、介面返回html頁面的設置 2、先從緩存中取,有就返回。 3、緩存中沒有,就手動渲染。 springboot1.5.x的寫法: s ...
  • 3.1.如何實現可迭代對象和迭代器對象 結果 3.2如何使用生成器函數實現可迭代對象 3.3.如何進行反向迭代以及如何實現反向迭代 反向迭代 要想實現反向迭代必須實現__reversed__方法 ...
  • 最近再學習ajax,上課老師讓我們實現一個類似百度首頁實現搜索框的功能,剛開始做的時候沒有一點頭緒,查閱大量網上的資源後,發現之前的與我們現在的有些區別,所以在此寫出來,希望能對大家有所幫助. 下麵先展示下效果圖:(ps:圖片中的文字是參考的,不具有任何的攻擊意義) 項目的目錄結構: 一:首先是lo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...