天天都用消息隊列,卻不知道為啥要用MQ,這就有點尴尬了
2019-08-16 15:26
Java技術劍
閱讀(877)
評論(1)
編輯
收藏
舉報
1、為什麼要使用消息隊列?
分析:一個用消息隊列的人,不知道為啥用,有點尴尬。沒有複習這點,很容易被問蒙,然後就開始胡扯了。
回答:這個問題,咱隻答三個最主要的應用場景(不可否認還有其他的,但是隻答三個主要的),即以下六個字:解耦、異步、削峰
(1)解耦
傳統模式:
傳統模式的缺點:
- 系統間耦合性太強,如上圖所示,系統A在代碼中直接調用系統B和系統C的代碼,如果将來D系統接入,系統A還需要修改代碼,過于麻煩!
中間件模式:
中間件模式的的優點:
- 将消息寫入消息隊列,需要消息的系統自己從消息隊列中訂閱,進而系統A不需要做任何修改。
(2)異步
傳統模式:
傳統模式的缺點:
- 一些非必要的業務邏輯以同步的方式運作,太耗費時間。
中間件模式:
中間件模式的的優點:
- 将消息寫入消息隊列,非必要的業務邏輯以異步的方式運作,加快響應速度
(3)削峰
傳統模式
傳統模式的缺點:
- 并發量大的時候,所有的請求直接怼到資料庫,造成資料庫連接配接異常
中間件模式:
中間件模式的的優點:
- 系統A慢慢的按照資料庫能處理的并發量,從消息隊列中慢慢拉取消息。在生産中,這個短暫的高峰期積壓是允許的。
2、使用了消息隊列會有什麼缺點?
分析:一個使用了MQ的項目,如果連這個問題都沒有考慮過,就把MQ引進去了,那就給自己的項目帶來了風險。
我們引入一個技術,要對這個技術的弊端有充分的認識,才能做好預防。要記住,不要給公司挖坑!
回答:回答也很容易,從以下兩個個角度來答
- 系統可用性降低:
- 你想啊,本來其他系統隻要運作好好的,那你的系統就是正常的。
- 現在你非要加個消息隊列進去,那消息隊列挂了,你的系統不是呵呵了。是以,系統可用性降低
- 系統複雜性增加:
- 要多考慮很多方面的問題,比如一緻性問題、如何保證消息不被重複消費,如何保證保證消息可靠傳輸。
- 是以,需要考慮的東西更多,系統複雜性增大。
但是,我們該用還是要用的。
3、消息隊列如何選型?
先說一下,部落客隻會ActiveMQ,RabbitMQ,RocketMQ,Kafka,對什麼ZeroMQ等其他MQ沒啥了解,是以隻能基于這四種MQ給出回答。
分析:既然在項目中用了MQ,肯定事先要對業界流行的MQ進行調研,如果連每種MQ的優缺點都沒了解清楚,就拍腦袋依據喜好,用了某種MQ,還是給項目挖坑。
如果面試官問:"你為什麼用這種MQ?。"你直接回答"上司決定的。"這種回答就很LOW了。
還是那句話,不要給公司挖坑。
我們可以看出,RabbitMQ版本釋出比ActiveMq頻繁很多。至于RocketMQ和kafka就不帶大家看了,總之也比ActiveMQ活躍的多。詳情,可自行查閱。
再來一個性能對比表
綜合上面的材料得出以下兩點:
(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模式部署架構圖(網上找的,偷個懶,懶得畫):
其實部落客第一眼看到這個圖,就覺得和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的拓補架構圖(也是找的,懶得畫)
如上圖所示,一個典型的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的概念
簡單說一下(如果還不懂,出門找一個kafka入門到精通教程),就是每一個消息都有一個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的代碼如下所示(說好不上代碼的,偷偷上了):
(2)消息隊列丢資料
處理消息隊列丢資料的情況,一般是開啟持久化磁盤的配置。
這個持久化配置可以和confirm機制配合使用,你可以在消息持久化磁盤後,再給生産者發送一個Ack信号。
這樣,如果消息持久化磁盤之前,rabbitMQ陣亡了,那麼生産者收不到Ack信号,生産者會自動重發。
那麼如何持久化呢,這裡順便說一下吧,其實也很容易,就下面兩步
1、将queue的持久化辨別durable設定為true,則代表是一個持久的隊列
2、發送消息的時候将deliveryMode=2
這樣設定以後,rabbitMQ就算挂了,重新開機後也能恢複資料
(3)消費者丢資料
消費者丢資料一般是因為采用了自動确認消息模式。
這種模式下,消費者會自動确認收到資訊。這時rahbitMQ會立即将消息删除,這種情況下如果消費者出現異常而沒能處理該消息,就會丢失該消息。
至于解決方案,采用手動确認消息即可。
kafka
Producer在釋出消息到某個Partition時,先通過ZooKeeper找到該Partition的Leader
然後無論該Topic的Replication Factor為多少(也即該Partition有多少個Replica),Producer隻将該消息發送到該Partition的Leader。
Leader會将該消息寫入其本地Log。每個Follower都從Leader中pull資料。
針對上述情況,得出如下分析
(1)生産者丢資料
在kafka生産中,基本都有一個leader和多個follwer。follwer會去同步leader的資訊。
是以,為了避免生産者丢資料,做如下兩點配置
- 第一個配置要在producer端設定acks=all。這個配置保證了,follwer同步完成後,才認為消息發送成功。
- 在producer端設定retries=MAX,一旦寫入失敗,這無限重試
(2)消息隊列丢資料
針對消息隊列丢資料的情況,無外乎就是,資料還沒同步,leader就挂了,這時zookpeer會将其他的follwer切換為leader,那資料就丢失了。
針對這種情況,應該做兩個配置。
- replication.factor參數,這個值必須大于1,即要求每個partition必須有至少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
大家自行查閱吧
7、如何保證消息的順序性?
分析:其實并非所有的公司都有這種業務需求,但是還是對這個問題要有所複習。
回答:針對這個問題,通過某種算法,将需要保持先後順序的消息放到同一個消息隊列中(kafka中就是partition,rabbitMq中就是queue)。然後隻用一個消費者去消費該隊列。
有的人會問:那如果為了吞吐量,有多個消費者去消費怎麼辦?
這個問題,沒有固定回答的套路。比如我們有一個微網誌的操作,發微網誌、寫評論、删除微網誌,這三個異步操作。如果是這樣一個業務場景,那隻要重試就行。
比如你一個消費者先執行了寫評論的操作,但是這時候,微網誌都還沒發,寫評論一定是失敗的,等一段時間。等另一個消費者,先執行寫評論的操作後,再執行,就可以成功。
總之,針對這個問題,我的觀點是保證入隊有序就行,出隊以後的順序交給消費者自己去保證,沒有固定套路。
總結
寫到這裡,希望讀者把本文提出的這幾個問題,經過深刻的準備後,一般來說,能囊括大部分的消息隊列的知識點。
如果面試官不問這幾個問題怎麼辦,簡單,自己把幾個問題講清楚,突出以下自己考慮的全面性。
最後,其實我不太提倡這樣突擊複習,希望大家打好基本功,做一個愛思考,懂思考,會思考的程式員。
- 标簽 “Java”
