這里總結一些MQ(Message Queue,消息隊列)的相關知識。
消息隊列的優點
解耦
在傳統模式下,系統之間的耦合性太強,比如系統A在代碼中直接調用系統B和系統C的代碼,如果將來D系統接入,系統A還需要修改代碼。
如果將消息寫入消息隊列,需要消息的系統自己從消息隊列中訂閱,在D系統接入的時候系統A也不需要做任何修改,達到了解耦的效果。
異步
在傳統模式下,一些非必要的業務邏輯以同步的方式運行,需要等待上一個業務邏輯執行完畢才能開始執行下一個業務邏輯,耗費等待的時間。
如果將消息寫入消息隊列,非必要的業務邏輯就可以以異步的方式運行,加快了服務響應的速度。
削峰
在傳統模式下,當並發量大的時候,所有的請求都會直接懟到數據庫,造成數據庫連接異常,甚至宕機。
如果將消息寫入消息隊列,則系統A可以慢慢地按照數據庫能處理的並發量,從消息隊列中慢慢拉取消息。在生產中,這個短暫的高峰期積壓是允許的。
消息隊列的缺點
我們引入一個技術,要對這個技術的弊端有充分的認識,才能做好預防。一個使用了MQ的項目,如果連MQ的缺點都沒有考慮過,就把MQ引進去了,那就會給自己的項目帶來風險。
系統可用性降低
你想啊,本來其他系統只要運行好好的,那你的系統就是正常的。現在你非要加個消息隊列進去,那消息隊列掛了,系統也就掛了。用專業的術語來解釋,就是系統的可用性降低了。
系統復雜性增加
加了MQ之后要多考慮很多方面的問題,比如數據的一致性問題、如何避免消息被重復消費,如何保證消息可靠傳輸等。因此,需要考慮的東西更多,系統復雜性也就隨着增加了。
消息隊列的選型
既然在項目中用了MQ,肯定事先要對業界流行的MQ進行調研,如果連每種MQ的優缺點都沒了解清楚,隨便選用了某種MQ,就容易給項目挖坑。另外,如果面試官問為什么項目上選用這種MQ的時候,你直接回答是領導決定的,這種回答就很LOW了。所以了解一下各種MQ的優缺點和使用的場景還是有必要的。
這里只簡單說一下ActiveMQ、RabbitMQ、RocketMQ和Kafka四種MQ框架。
更新頻率
要了解各個MQ方案的更新頻率,可以上各個MQ的官方社區去看。
簡單了解一下就是,ActiveMQ幾個月才發一次版本,據說研究重心在他們的下一代產品Apollo;RabbitMQ的版本發布比ActiveMQ頻繁很多;RocketMQ和Kafka也比ActiveMQ活躍的多。
性能對比
用一個表格簡單比較下各個MQ的性能。
| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 開發語言 | java | erlang | java | scala |
| 單機吞吐量 | 萬級 | 萬級 | 10萬級 | 10萬級 |
| 時效性 | ms級 | us級 | ms級 | ms級以內 |
| 可用性 | 高(主從架構) | 高(主從架構) | 非常高(分布式架構) | 非常高(分布式架構) |
| 功能特性 | 成熟的產品,在很多公司得到應用;有較多的文檔;各種協議支持較好 | 基於erlang開發,所以並發能力很強,性能極其好,延時很低;管理界面較豐富 | MQ功能比較完備,擴展性佳 | 只支持主要的MQ功能,像一些消息查詢,消息回溯等功能沒有提供,畢竟是為大數據准備的,在大數據領域應用廣。 |
MQ選型的簡單總結
1.中小型軟件公司,建議選RabbitMQ。一方面,erlang語言天生具備高並發的特性,而且他的管理界面用起來十分方便。但是雖然RabbitMQ是開源的,國內能定制化開發erlang的程序員卻很少。所幸的是RabbitMQ的社區十分活躍,通過社區可以解決開發過程中遇到的大部分Bug,這點對於中小型公司來說十分重要。不考慮RocketMQ和Kafka的原因是,一方面中小型軟件公司不如互聯網公司,數據量沒那么大,選消息中間件,應首選功能比較完備的,所以Kafka排除。不考慮RocketMQ的原因是,RocketMQ是阿里出品,一旦哪一天阿里放棄維護RocketMQ,中小型公司一般是抽不出人來進行RocketMQ的定制化開發的,因此不推薦。
2.大型軟件公司,根據具體使用在RocketMQ和Kafka之間二選一。一方面,大型軟件公司,具備足夠的資金搭建分布式環境,也具備足夠大的數據量。針對RocketMQ,大型軟件公司也可以抽出人手對RocketMQ進行定制化開發,畢竟國內有能力改Java源碼的人,還是相當多的。至於Kafka,根據業務場景選擇,如果有日志采集功能,肯定是首選Kafka了。具體該選哪個,還是要看具體的使用場景。
保證消息隊列的高可用
前面說過了,引入消息隊列后,系統的可用性會降低。在生產中,沒人會使用單機模式的消息隊列,因此了解消息隊列的高可用是很必要的。實際上,要保證消息隊列的高可用,需要對消息隊列的集群模式有深刻了解。
RocketMQ保證消息隊列的高可用
以RocketMQ為例,他的集群就有多master模式、多master多slave異步復制模式和多master多slave同步雙寫模式。
下面是一張RocketMQ多master多slave模式部署的架構圖。

這樣的實現方式其實和Kafka很像,只是Name Server集群在Kafka中是用Zookeeper代替,都是用來保存和發現Master和Slave用的。通信過程如下:Producer與Name Server集群中的其中一個節點(隨機選擇)建立長連接,定期從Name Server集群獲取Topic路由信息,並向提供Topic服務的Broker Master建立長連接,且定時向Broker發送消息。Producer只能將消息發送到Broker Master,但是Consumer則不一樣,它同時和提供Topic服務的Master和Slave建立長連接,既可以從Broker Master訂閱消息,也可以從Broker Slave訂閱消息。
Kafka保證消息隊列的高可用
直接上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保證消息隊列的高可用
RabbitMQ也有普通集群和鏡像集群模式,比較簡單。
保證消息隊列高可用性的簡單總結
要保證消息隊列的高可用,需要從集群上入手,具體就是當消息隊列中的一個節點炸了,其他的節點還能繼續運行,保證消息隊列整體能正常運行。
另外,如果有相關的面試題,需要了解MQ集群架構並能敘述清楚架構中的邏輯關系。
保證消息不被重復消費
保證消息不被重復消費,也就是保證消息隊列的冪等性。這個問題可以認為是消息隊列領域的基本問題。
消息被重復消費的原因
無論是哪一種消息隊列,造成消息被重復消費的原因都是類似的。在正常的情況下,消費者在消費消息完畢后,會發送一個確認信息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發送的確認信息形式不同,例如RabbitMQ是發送一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標志,Kafka則實際上是有個offset的概念(每一個消息都有一個offset,Kafka消費過消息后,需要提交offset,讓消息隊列知道自己已經消費過了)。而造成重復消費的原因,就是因為網絡傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將該消息分發給其他的消費者。
消息被重復消費的解決方法
解決方法有很多,其實就是單機服務里冪等性的問題,這里簡單列舉幾個方法。
1.唯一索引。如果拿到的消息是做數據庫的insert操作,可以給這個消息做一個唯一主鍵,那么就算出現重復消費的情況,也會因為主鍵沖突而取消操作,有效避免數據庫出現臟數據。
2.原生冪等。如果拿到的消息是做Redis的set的操作,因為在Redis中set操作本來就算冪等操作,即無論set幾次結果都是一樣的,也就不怕消息被重復消費的問題了。
3.全局狀態機。准備一個第三方介質,來做一個消息消費的記錄。以Redis為例,給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V的形式寫入Redis。在消費者開始消費前,先去Redis中查詢有沒有消費記錄即可。
保證消息消費的可靠性傳輸
在使用消息隊列的過程中,應該做到消息不能多消費,也不能少消費。如果無法做到可靠性傳輸,可能給公司帶來千萬級別的財產損失。
而要保證消息消費的可靠性傳輸,每種MQ都要從三個角度來分析,分別是生產者弄丟數據、消息隊列弄丟數據和消費者弄丟數據。
這個內容太復雜了,暫時玩不轉,等以后再補充,嘿嘿。
保證消息的順序性
要保證消息的順序性,可以通過某種算法,將需要保持先后順序的消息放到同一個消息隊列中(Kafka中就是Partition,RabbitMQ中就是Queue),然后只用一個消費者去消費該隊列即可。
另外,如果為了吞吐量,有多個消費者去消費,這時候要保證消息的順序性,其實只要一個簡單的重試就好了。比如微博,可以分為發微博、寫評論和刪除微博這三個異步操作。如果這時候有一個消費者先執行了寫評論的操作,但是這時候,微博都還沒發,寫評論一定是失敗的,就可以等一段時間再重試。等另一個消費者,先執行發微博的操作后,再執行寫評論的操作,就可以成功。
實際上,要保證消息的順序性,大多數情況下只需要保證入隊有序就行,出隊以后的順序交給消費者自己去保證,沒有固定的套路。
"慢慢大家會明白的,無法跟喜歡的人在一起,其實是人生的常態。"
