1.為什么要使用消息隊列
解耦、異步、削峰
(1)解耦
傳統模式的缺點:
- 系統間耦合性太強,如下圖所示,系統A在代碼中直接調用系統B和系統C的代碼,如果將來D系統接入,系統A還需要修改代碼,過於麻煩!

中間件模式(消息隊列)優點:
- 將消息寫入消息隊列,需要消息的系統自己從消息隊列中訂閱,從而系統A不需要做任何修改。

(2) 異步
傳統模式:
- 一些非必要的業務邏輯以同步的方式運行,太耗費時間。

中間件模式(消息隊列)優點:

(3) 削峰
傳統模式缺點
- 並發量大的時間,所有的請求直接懟到數據庫,造成數據庫連接異常

中間件模式(消息隊列)優點:
- 系統A慢慢的按照數據庫能處理的並發量,從消息隊列中慢慢拉取消息。在生產中,這個短暫的高峰期積壓是允許的。

2 使用消息隊列有哪些缺點
- 系統可用性降低:你想呀,本來其他系統只要運行好好的,那你的系統就是正常的。現在你非要加入個消息隊列進去,那消息隊列掛了,你的系統不是呵呵了。因此,系統可用性會降低
- 系統復雜性增加:加入了消息隊列,要多考慮很多方面的問題,比如:一致性問題、如何保證消息不被重復消費、如何保證消息可靠性傳輸等。因此,需要考慮的東西更多,刺痛復雜性增大。
3 消息隊列如何選型?
(1)中小型軟件公司,建議選RabbitMQ.一方面,erlang語言天生具備高並發的特性,而且他的管理界面用起來十分方便。
(2)大型軟件公司,根據具體使用在rocketMq和kafka之間二選一。一方面,大型軟件公司,具備足夠的資金搭建分布式環境,也具備足夠大的數據量。針對rocketMQ,大型軟件公司也可以抽出人手對rocketMQ進行定制化開發,畢竟國內有能力改JAVA源碼的人,還是相當多的。至於kafka,根據業務場景選擇,如果有日志采集功能,肯定是首選kafka了。具體該選哪個,看使用場景。

5.如何保證消息不被重復消費
其實無論是哪種消息隊列,造成重復消費原因其實都是類似的。正常情況下,消費者在消費消息的時候,消費完畢后,會發送一個確認消息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。
重復消費的原因:
就是因為網絡傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將消息分發給其他的消費者。
6.如何保證消費的可靠性傳輸?
- 生產者弄丟數據
- 消息隊列弄丟數據
- 消費者弄丟數據
(1)生產者丟數據
從生產者弄丟數據這個角度來看,RabbitMQ提供transaction和confirm模式來確保生產者不丟消息。
transaction機制就是說,發送消息前,開啟事務(channel.txSelect()),然后發送消息,如果發送過程中出現什么異常,事務就會回滾(channel.txRollback()),如果發送成功則提交事務(channel.txCommit())。
(2)消息隊列丟數據
處理消息隊列丟數據的情況,一般是開啟持久化磁盤的配置。這個持久化配置可以和confirm機制配合使用,你可以在消息持久化磁盤后,再給生產者發送一個Ack信號。這樣,如果消息持久化磁盤之前,rabbitMQ陣亡了,那么生產者收不到Ack信號,生產者會自動重發。
那么如何持久化呢,這里順便說一下吧,其實也很容易,就下面兩步
- 將queue的持久化標識durable設置為true,則代表是一個持久的隊列
- 發送消息的時候將deliveryMode=2
這樣設置以后,即使rabbitMQ掛了,重啟后也能恢復數據.
(3)消費者丟數據
消費者丟數據一般是因為采用了自動確認消息模式。這種模式下,消費者會自動確認收到信息。這時rabbitMQ會立即將消息刪除,這種情況下,如果消費者出現異常而未能處理消息,就會丟失該消息。
7. 如何確保消息正確地發送至RabbitMQ?
RabbitMQ使用發送方確認模式,確保消息正確地發送到RabbitMQ。
發送方確認模式:將信道設置成confirm模式(發送方確認模式),則所有在信道上發布的消息都會被指派一個唯一的ID。一旦消息被投遞到目的隊列后,或者消息被寫入磁盤后(可持久化的消息),信道會發送一個確認給生產者(包含消息唯一ID)。如果RabbitMQ發生內部錯誤從而導致消息丟失,會發送一條nack(not acknowledged,未確認)消息。
發送方確認模式是異步的,生產者應用程序在等待確認的同時,可以繼續發送消息。當確認消息到達生產者應用程序,生產者應用程序的回調方法就會被觸發來處理確認消息。
8. 如何確保消息接收方消費了消息?
接收方消息確認機制:消費者接收每一條消息后都必須進行確認(消息接收和消息確認是兩個不同操作)。只有消費者確認了消息,RabbitMQ才能安全地把消息從隊列中刪除。
這里並沒有用到超時機制,RabbitMQ僅通過Consumer的連接中斷來確認是否需要重新發送消息。也就是說,只要連接不中斷,RabbitMQ給了Consumer足夠長的時間來處理消息。
下面羅列幾種特殊情況:
- 如果消費者接收到消息,在確認之前斷開了連接或取消訂閱,RabbitMQ會認為消息沒有被分發,然后重新分發給下一個訂閱的消費者。(可能存在消息重復消費的隱患,需要根據bizId去重)
- 如果消費者接收到消息卻沒有確認消息,連接也未斷開,則RabbitMQ認為該消費者繁忙,將不會給該消費者分發更多的消息。
9. 如何避免消息重復投遞或重復消費?
在消息生產時,MQ內部針對每條生產者發送的消息生成一個inner-msg-id,作為去重和冪等的依據(消息投遞失敗並重傳),避免重復的消息進入隊列;在消息消費時,要求消息體中必須要有一個bizId(對於同一業務全局唯一,如支付ID、訂單ID、帖子ID等)作為去重和冪等的依據,避免同一條消息被重復消費。
10. 消息基於什么傳輸?
由於TCP連接的創建和銷毀開銷較大,且並發數受系統資源限制,會造成性能瓶頸。RabbitMQ使用信道的方式來傳輸數據。信道是建立在真實的TCP連接內的虛擬連接,且每條TCP連接上的信道數量沒有限制。
11. 消息如何分發?
若該隊列至少有一個消費者訂閱,消息將以循環(round-robin)的方式發送給消費者。每條消息只會分發給一個訂閱的消費者(前提是消費者能夠正常處理消息並進行確認)。
12. 消息怎么路由?
從概念上來說,消息路由必須有三部分:交換器、路由、綁定。生產者把消息發布到交換器上;綁定決定了消息如何從路由器路由到特定的隊列;消息最終到達隊列,並被消費者接收。
- 消息發布到交換器時,消息將擁有一個路由鍵(routing key),在消息創建時設定。
- 通過隊列路由鍵,可以把隊列綁定到交換器上。
- 消息到達交換器后,RabbitMQ會將消息的路由鍵與隊列的路由鍵進行匹配(針對不同的交換器有不同的路由規則)。如果能夠匹配到隊列,則消息會投遞到相應隊列中;如果不能匹配到任何隊列,消息將進入 “黑洞”。
常用的交換器主要分為一下三種:
- direct:如果路由鍵完全匹配,消息就被投遞到相應的隊列
- fanout:如果交換器收到消息,將會廣播到所有綁定的隊列上
- topic:可以使來自不同源頭的消息能夠到達同一個隊列。 使用topic交換器時,可以使用通配符,比如:“*” 匹配特定位置的任意文本, “.” 把路由鍵分為了幾部分,“#” 匹配所有規則等。特別注意:發往topic交換器的消息不能隨意的設置選擇鍵(routing_key),必須是由"."隔開的一系列的標識符組成。
14. 如何確保消息不丟失?
消息持久化的前提是:將交換器/隊列的durable屬性設置為true,表示交換器/隊列是持久交換器/隊列,在服務器崩潰或重啟之后不需要重新創建交換器/隊列(交換器/隊列會自動創建)。
如果消息想要從Rabbit崩潰中恢復,那么消息必須:
- 在消息發布前,通過把它的 “投遞模式” 選項設置為2(持久)來把消息標記成持久化
- 將消息發送到持久交換器
- 消息到達持久隊列
RabbitMQ確保持久性消息能從服務器重啟中恢復的方式是,將它們寫入磁盤上的一個持久化日志文件,當發布一條持久性消息到持久交換器上時,Rabbit會在消息提交到日志文件后才發送響應(如果消息路由到了非持久隊列,它會自動從持久化日志中移除)。一旦消費者從持久隊列中消費了一條持久化消息,RabbitMQ會在持久化日志中把這條消息標記為等待垃圾收集。如果持久化消息在被消費之前RabbitMQ重啟,那么Rabbit會自動重建交換器和隊列(以及綁定),並重播持久化日志文件中的消息到合適的隊列或者交換器上。
15. 使用RabbitMQ有什么好處?
- 應用解耦(系統拆分)
- 異步處理(預約掛號業務處理成功后,異步發送短信、推送消息、日志記錄等)
- 消息分發
- 流量削峰
- 消息緩沖
- ......
16. 其他
RabbitMQ是 消息投遞服務,在應用程序和服務器之間扮演路由器的角色,而應用程序或服務器可以發送和接收包裹。其通信方式是一種 “發后即忘(fire-and-forget)” 的單向方式。
其中消息包含兩部分內容:有效載荷(payload)和標簽(label)。
17 Redis與RabbitMQ作為消息隊列的比較
可靠消費
Redis:沒有相應的機制保證消息的消費,當消費者消費失敗的時候,消息體丟失,需要手動處理
RabbitMQ:具有消息消費確認,即使消費者消費失敗,也會自動使消息體返回原隊列,同時可全程持久化,保證消息體被正確消費
可靠發布
Reids:不提供,需自行實現
RabbitMQ:具有發布確認功能,保證消息被發布到服務器
高可用
Redis:采用主從模式,讀寫分離,但是故障轉移還沒有非常完善的官方解決方案
RabbitMQ:集群采用磁盤、內存節點,任意單點故障都不會影響整個隊列的操作
持久化
Redis:將整個Redis實例持久化到磁盤
RabbitMQ:隊列,消息,都可以選擇是否持久化
消費者負載均衡
Redis:不提供,需自行實現
RabbitMQ:根據消費者情況,進行消息的均衡分發
隊列監控
Redis:不提供,需自行實現
RabbitMQ:后台可以監控某個隊列的所有信息,(內存,磁盤,消費者,生產者,速率等)
流量控制
Redis:不提供,需自行實現
RabbitMQ:服務器過載的情況,對生產者速率會進行限制,保證服務可靠性
出入隊性能
對於RabbitMQ和Redis的入隊和出隊操作,各執行100萬次,每10萬次記錄一次執行時間。
測試數據分為128Bytes、512Bytes、1K和10K四個不同大小的數據。
應用場景分析
Redis:輕量級,高並發,延遲敏感
即時數據分析、秒殺計數器、緩存等
RabbitMQ:重量級,高並發,異步
批量數據異步處理、並行任務串行化,高負載任務的負載均衡等
轉載
https://chaser520.iteye.com/blog/2428253
