消息队列-MQ详解
來源:http://rrd.me/ep4hS
-
1、解耦
-
2、異步
-
3、削峰
-
使用了消息隊列會有什么缺點?
-
消息隊列如何選型?
-
如何保證消息隊列是高可用的?
-
如何保證消息不被重復消費?
-
如何保證消費的可靠性傳輸?
-
RabbitMQ
-
kafka
-
如何保證消息的順序性?
-
總結
一個用消息隊列的人,不知道為啥用,有點尷尬。沒有復習這點,很容易被問蒙,然后就開始胡扯了。
**回答:**這個問題, 咱只答三個最主要的應用場景,不可否認還有其他的,但是只答三個主要的,即以下六個字:
解耦、異步、削峰
1、解耦
傳統模式:
傳統模式的缺點:
系統間耦合性太強,如上圖所示,系統 A 在代碼中直接調用系統 B 和系統 C 的代碼,如果將來 D 系統接入,系統 A 還需要修改代碼,過于麻煩!
中間件模式:
中間件模式的的優點:
將消息寫入消息隊列,需要消息的系統自己從消息隊列中訂閱,從而系統 A 不需要做任何修改。
2、異步
傳統模式:
傳統模式的缺點:
一些非必要的業務邏輯以同步的方式運行,太耗費時間。
中間件模式:
中間件模式的的優點:
將消息寫入消息隊列,非必要的業務邏輯以異步的方式運行,加快響應速度
3、削峰
傳統模式
傳統模式的缺點:
并發量大的時候,所有的請求直接懟到數據庫,造成數據庫連接異常
中間件模式:
中間件模式的的優點:
系統 A 慢慢的按照數據庫能處理的并發量,從消息隊列中慢慢拉取消息。在生產中,這個短暫的高峰期積壓是允許的。
使用了消息隊列會有什么缺點?
分析: 一個使用了 MQ 的項目,如果連這個問題都沒有考慮過,就把 MQ 引進去了,那就給自己的項目帶來了風險。
我們引入一個技術,要對這個技術的弊端有充分的認識,才能做好預防。要記住,不要給公司挖坑!
回答: 回答也很容易,從以下兩個個角度來答
系統可用性降低:
你想啊,本來其他系統只要運行好好的,那你的系統就是正常的。
現在你非要加個消息隊列進去,那消息隊列掛了,你的系統不是呵呵了。因此,系統可用性降低
系統復雜性增加:
要多考慮很多方面的問題,比如一致性問題、如何保證消息不被重復消費,如何保證保證消息可靠傳輸。
因此,需要考慮的東西更多,系統復雜性增大。
但是,我們該用還是要用的。
消息隊列如何選型?
先說一下,博主只會 ActiveMQ,RabbitMQ,RocketMQ,Kafka,對什么 ZeroMQ 等其他 MQ 沒啥理解,因此只能基于這四種 MQ 給出回答。
分析: 既然在項目中用了 MQ,肯定事先要對業界流行的 MQ 進行調研,如果連每種 MQ 的優缺點都沒了解清楚,就拍腦袋依據喜好,用了某種 MQ,還是給項目挖坑。
如果面試官問:“你為什么用這種 MQ?。”
你直接回答 “領導決定的。”
這種回答就很 LOW 了。
還是那句話,不要給公司挖坑。
我們可以看出,RabbitMQ 版本發布比 ActiveMq 頻繁很多。至于 RocketMQ 和 kafka 就不帶大家看了,總之也比 ActiveMQ 活躍的多。詳情,可自行查閱。
再來一個性能對比表
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Zqocfibo-1657433335258)(https://mmbiz.qpic.cn/mmbiz_jpg/JdLkEI9sZfdHk9CSr9xiabvME3OKD2CibDEk6cyutD3umJvKxZKb7p3ns3jpcxwxNvmTs4cYzkAHUhaBpwNkt2mA/640?wx_fmt=jpeg)]
綜合上面的材料得出以下兩點:
1、中小型軟件公司,建議選 RabbitMQ.
一方面,erlang 語言天生具備高并發的特性,而且他的管理界面用起來十分方便。
正所謂,成也蕭何,敗也蕭何!他的弊端也在這里,雖然 RabbitMQ 是開源的,然而國內有幾個能定制化開發 erlang 的程序員呢?
所幸,RabbitMQ 的社區十分活躍,可以解決開發過程中遇到的 bug,這點對于中小型公司來說十分重要。
不考慮 rocketmq 和 kafka 的原因是,一方面中小型軟件公司不如互聯網公司,數據量沒那么大,選消息中間件,應首選功能比較完備的,所以 kafka 排除。
不考慮 rocketmq 的原因是,rocketmq 是阿里出品,如果阿里放棄維護 rocketmq,中小型公司一般抽不出人來進行 rocketmq 的定制化開發,因此不推薦。
2、大型軟件公司,根據具體使用在 rocketMq 和 kafka 之間二選一
一方面,大型軟件公司,具備足夠的資金搭建分布式環境,也具備足夠大的數據量。
針對 rocketMQ, 大型軟件公司也可以抽出人手對 rocketMQ 進行定制化開發,畢竟國內有能力改 JAVA 源碼的人,還是相當多的。
至于 kafka,根據業務場景選擇,如果有日志采集功能,肯定是首選 kafka 了。具體該選哪個,看使用場景。
如何保證消息隊列是高可用的?
在第二點說過了,引入消息隊列后,系統的可用性下降。
在生產中,沒人使用單機模式的消息隊列。
因此,作為一個合格的程序員,應該對消息隊列的高可用有很深刻的了解。
如果面試官問:“你們的消息中間件如何保證高可用的?”
如果你的回答只是表明自己只會訂閱和發布消息,面試官就會懷疑你是不是只是自己搭著玩,壓根沒在生產用過。
因此,請做一個愛思考,會思考,懂思考的程序員。
回答: 這問題,其實要對消息隊列的集群模式要有深刻了解,才好回答。
以 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 集群架構或清晰的敘述出來。
如何保證消息不被重復消費?
這個問題其實換一種問法就是,如何保證消息隊列的冪等性?
這個問題可以認為是消息隊列領域的基本問題。換句話來說,是在考察你的設計能力,這個問題的回答可以根據具體的業務場景來答,沒有固定的答案。
回答: 先來說一下為什么會造成重復消費?
其實無論是那種消息隊列,造成重復消費原因其實都是類似的。
正常情況下,消費者在消費消息時候,消費完畢后,會發送一個確認信息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發送的確認信息形式不同
例如 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 中查詢有沒消費記錄即可。
如何保證消費的可靠性傳輸?
分析: 我們在使用消息隊列的過程中,應該做到消息不能多消費,也不能少消費。如果無法做到可靠性傳輸,可能給公司帶來千萬級別的財產損失。
同樣的,如果可靠性傳輸在使用過程中,沒有考慮到,這不是給公司挖坑么,你可以拍拍屁股走了,公司損失的錢,誰承擔。
還是那句話,認真對待每一個項目,不要給公司挖坑
回答: 其實這個可靠性傳輸,每種 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
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kjSco4Rp-1657433335259)(https://mmbiz.qpic.cn/mmbiz_jpg/JdLkEI9sZfdHk9CSr9xiabvME3OKD2CibDwAicONicytibw1jFWWbgriaEuHhq9DUVdS52zDia7lEYLr2h1p5LXkeOjbA/640?wx_fmt=jpeg)]
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 開始消費。
解決方案也很簡單,改成手動提交即可。
如何保證消息的順序性?
分析: 其實并非所有的公司都有這種業務需求,但是還是對這個問題要有所復習。
回答: 針對這個問題,通過某種算法,將需要保持先后順序的消息放到同一個消息隊列中 (kafka 中就是 partition,rabbitMq 中就是 queue)。然后只用一個消費者去消費該隊列。
有的人會問: 那如果為了吞吐量,有多個消費者去消費怎么辦?
這個問題,沒有固定回答的套路。比如我們有一個微博的操作,發微博、寫評論、刪除微博,這三個異步操作。如果是這樣一個業務場景,那只要重試就行。
比如你一個消費者先執行了寫評論的操作,但是這時候,微博都還沒發,寫評論一定是失敗的,等一段時間。等另一個消費者,先執行寫評論的操作后,再執行,就可以成功。
總之,針對這個問題,我的觀點是保證入隊有序就行,出隊以后的順序交給消費者自己去保證,沒有固定套路。
總結
寫到這里,希望讀者把本文提出的這幾個問題,經過深刻的準備后,一般來說,能囊括大部分的消息隊列的知識點。
如果面試官不問這幾個問題怎么辦,簡單,自己把幾個問題講清楚,突出以下自己考慮的全面性。
最后,其實我不太提倡這樣突擊復習,希望大家打好基本功,做一個愛思考,懂思考,會思考的程序員。
總結
- 上一篇: php api接口验证签名错误,API常
- 下一篇: 如何安装、查看、更新python库