1. RabbitMQ 基本概念

-
Message
消息,消息是不具名的,它由消息頭和消息體組成。消息體是不透明的,而消息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對于其他消息的優(yōu)先權(quán))、delivery-mode(指出該消息可能需要持久性存儲)等。 -
Publisher
消息的生產(chǎn)者,也是一個向交換器發(fā)布消息的客戶端應(yīng)用程序。 -
Exchange
交換器,用來接收生產(chǎn)者發(fā)送的消息并將這些消息路由給服務(wù)器中的隊列。 -
Binding
綁定,用于消息隊列和交換器之間的關(guān)聯(lián)。一個綁定就是基于路由鍵將交換器和消息隊列連接起來的路由規(guī)則,所以可以將交換器理解成一個由綁定構(gòu)成的路由表。 -
Queue
消息隊列,用來保存消息直到發(fā)送給消費(fèi)者。它是消息的容器,也是消息的終點(diǎn)。一個消息可投入一個或多個隊列。消息一直在隊列里面,等待消費(fèi)者連接到這個隊列將其取走。 -
Connection
網(wǎng)絡(luò)連接,比如一個TCP連接。 -
Channel
信道,多路復(fù)用連接中的一條獨(dú)立的雙向數(shù)據(jù)流通道。信道是建立在真實的TCP連接內(nèi)地虛擬連接,AMQP 命令都是通過信道發(fā)出去的,不管是發(fā)布消息、訂閱隊列還是接收消息,這些動作都是通過信道完成。因為對于操作系統(tǒng)來說建立和銷毀 TCP 都是非常昂貴的開銷,所以引入了信道的概念,以復(fù)用一條 TCP 連接。 -
Consumer
消息的消費(fèi)者,表示一個從消息隊列中取得消息的客戶端應(yīng)用程序。 -
Virtual Host
虛擬主機(jī),表示一批交換器、消息隊列和相關(guān)對象。虛擬主機(jī)是共享相同的身份認(rèn)證和加密環(huán)境的獨(dú)立服務(wù)器域。每個 vhost 本質(zhì)上就是一個 mini 版的 RabbitMQ 服務(wù)器,擁有自己的隊列、交換器、綁定和權(quán)限機(jī)制。vhost 是 AMQP 概念的基礎(chǔ),必須在連接時指定,RabbitMQ 默認(rèn)的 vhost 是 / 。 -
Broker
表示消息隊列服務(wù)器實體。
2. Exchange 類型
-
direct
direct
消息中的路由鍵(routing key)如果和 Binding 中的 binding key 一致, 交換器就將消息發(fā)到對應(yīng)的隊列中。路由鍵與隊列名完全匹配,如果一個隊列綁定到交換機(jī)要求路由鍵為“dog”,則只轉(zhuǎn)發(fā) routing key 標(biāo)記為“dog”的消息,不會轉(zhuǎn)發(fā)“dog.puppy”,也不會轉(zhuǎn)發(fā)“dog.guard”等等。它是完全匹配、單播的模式。 -
fanout
fanout
每個發(fā)到 fanout 類型交換器的消息都會分到所有綁定的隊列上去。fanout 交換器不處理路由鍵,只是簡單的將隊列綁定到交換器上,每個發(fā)送到交換器的消息都會被轉(zhuǎn)發(fā)到與該交換器綁定的所有隊列上。很像子網(wǎng)廣播,每臺子網(wǎng)內(nèi)的主機(jī)都獲得了一份復(fù)制的消息。fanout 類型轉(zhuǎn)發(fā)消息是最快的。 -
topic
topic
topic 交換器通過模式匹配分配消息的路由鍵屬性,將路由鍵和某個模式進(jìn)行匹配,此時隊列需要綁定到一個模式上。它將路由鍵和綁定鍵的字符串切分成單詞,這些單詞之間用點(diǎn)隔開。它同樣也會識別兩個通配符:符號“#”和符號“”。#匹配0個或多個單詞,匹配不多不少一個單詞。
3. RabbitMQ 使用常見問題
1.RabbitMQ的高可用性
- 普通集群模式
意思就是在多臺機(jī)器上啟動多個rabbitmq實例,每個機(jī)器啟動一個。但是你創(chuàng)建的queue,只會放在一個rabbtimq實例上,但是每個實例都同步queue的元數(shù)據(jù)。完了你消費(fèi)的時候,實際上如果連接到了另外一個實例,那么那個實例會從queue所在實例上拉取數(shù)據(jù)過來。
這種方式確實很麻煩,也不怎么好,沒做到所謂的分布式,就是個普通集群。因為這導(dǎo)致你要么消費(fèi)者每次隨機(jī)連接一個實例然后拉取數(shù)據(jù),要么固定連接那個queue所在實例消費(fèi)數(shù)據(jù),前者有數(shù)據(jù)拉取的開銷,后者導(dǎo)致單實例性能瓶頸。
而且如果那個放queue的實例宕機(jī)了,會導(dǎo)致接下來其他實例就無法從那個實例拉取,如果你開啟了消息持久化,讓rabbitmq落地存儲消息的話,消息不一定會丟,得等這個實例恢復(fù)了,然后才可以繼續(xù)從這個queue拉取數(shù)據(jù)。
所以這個事兒就比較尷尬了,這就沒有什么所謂的高可用性可言了,這方案主要是提高吞吐量的,就是說讓集群中多個節(jié)點(diǎn)來服務(wù)某個queue的讀寫操作。
- 鏡像集群模式
這種模式,才是所謂的rabbitmq的高可用模式,跟普通集群模式不一樣的是,你創(chuàng)建的queue,無論元數(shù)據(jù)還是queue里的消息都會存在于多個實例上,然后每次你寫消息到queue的時候,都會自動把消息到多個實例的queue里進(jìn)行消息同步。
這樣的話,好處在于,你任何一個機(jī)器宕機(jī)了,沒事兒,別的機(jī)器都可以用。壞處在于,第一,這個性能開銷也太大了吧,消息同步所有機(jī)器,導(dǎo)致網(wǎng)絡(luò)帶寬壓力和消耗很重!第二,這么玩兒,就沒有擴(kuò)展性可言了,如果某個queue負(fù)載很重,你加機(jī)器,新增的機(jī)器也包含了這個queue的所有數(shù)據(jù),并沒有辦法線性擴(kuò)展你的queue
那么怎么開啟這個鏡像集群模式呢?我這里簡單說一下,避免面試人家問你你不知道,其實很簡單rabbitmq有很好的管理控制臺,就是在后臺新增一個策略,這個策略是鏡像集群模式的策略,指定的時候可以要求數(shù)據(jù)同步到所有節(jié)點(diǎn)的,也可以要求就同步到指定數(shù)量的節(jié)點(diǎn),然后你再次創(chuàng)建queue的時候,應(yīng)用這個策略,就會自動將數(shù)據(jù)同步到其他的節(jié)點(diǎn)上去了。
2.RabbitMQ消息重復(fù)消費(fèi)問題
可以消費(fèi)端消費(fèi)時,利用redis加一個消費(fèi)標(biāo)志,如果消費(fèi)過了就不繼續(xù)
3.RabbitMQ消息丟失問題
保證消息不丟失可以通過三個操作
- 1.生產(chǎn)者發(fā)送消息時,使用confirm機(jī)制
- 2.broker端設(shè)置消息時持久化,設(shè)置持久化有兩個步驟,第一個是創(chuàng)建queue的時候?qū)⑵湓O(shè)置為持久化的,這樣就可以保證rabbitmq持久化queue的元數(shù)據(jù),但是不會持久化queue里的數(shù)據(jù);第二個是發(fā)送消息的時候?qū)⑾⒌膁eliveryMode設(shè)置為2,就是將消息設(shè)置為持久化的,此時rabbitmq就會將消息持久化到磁盤上去。必須要同時設(shè)置這兩個持久化才行,rabbitmq哪怕是掛了,再次重啟,也會從磁盤上重啟恢復(fù)queue,恢復(fù)這個queue里的數(shù)據(jù)。
- 3.消費(fèi)端用rabbitmq提供的ack機(jī)制,關(guān)閉rabbitmq自動ack
4.RabbitMQ消息順序問題

將需要順序的消息,寫到同一個queue里,然后單獨(dú)指定一個消費(fèi)者消費(fèi)這個queue
5.RabbitMQ消息積壓問題
(1)如果你積壓了幾百萬到上千萬的數(shù)據(jù),即使消費(fèi)者恢復(fù)了,也需要大概1小時的時間才能恢復(fù)過來
一般這個時候,只能操作臨時緊急擴(kuò)容了,具體操作步驟和思路如下:
1)先修復(fù)consumer的問題,確保其恢復(fù)消費(fèi)速度,然后將現(xiàn)有cnosumer都停掉
2)新建一個topic,partition是原來的10倍,臨時建立好原先10倍或者20倍的queue數(shù)量
3)然后寫一個臨時的分發(fā)數(shù)據(jù)的consumer程序,這個程序部署上去消費(fèi)積壓的數(shù)據(jù),消費(fèi)之后不做耗時的處理,直接均勻輪詢寫入臨時建立好的10倍數(shù)量的queue
4)接著臨時征用10倍的機(jī)器來部署consumer,每一批consumer消費(fèi)一個臨時queue的數(shù)據(jù)
5)這種做法相當(dāng)于是臨時將queue資源和consumer資源擴(kuò)大10倍,以正常的10倍速度來消費(fèi)數(shù)據(jù)
6)等快速消費(fèi)完積壓數(shù)據(jù)之后,得恢復(fù)原先部署架構(gòu),重新用原先的consumer機(jī)器來消費(fèi)消息(2)這里我們假設(shè)再來第二個坑
假設(shè)你用的是rabbitmq,rabbitmq是可以設(shè)置過期時間的,就是TTL,如果消息在queue中積壓超過一定的時間就會被rabbitmq給清理掉,這個數(shù)據(jù)就沒了。那這就是第二個坑了。這就不是說數(shù)據(jù)會大量積壓在mq里,而是大量的數(shù)據(jù)會直接搞丟。
這個情況下,就不是說要增加consumer消費(fèi)積壓的消息,因為實際上沒啥積壓,而是丟了大量的消息。我們可以采取一個方案,就是批量重導(dǎo),這個我們之前線上也有類似的場景干過。就是大量積壓的時候,我們當(dāng)時就直接丟棄數(shù)據(jù)了,然后等過了高峰期以后,比如大家一起喝咖啡熬夜到晚上12點(diǎn)以后,用戶都睡覺了。
這個時候我們就開始寫程序,將丟失的那批數(shù)據(jù),寫個臨時程序,一點(diǎn)一點(diǎn)的查出來,然后重新灌入mq里面去,把白天丟的數(shù)據(jù)給他補(bǔ)回來。也只能是這樣了。
假設(shè)1萬個訂單積壓在mq里面,沒有處理,其中1000個訂單都丟了,你只能手動寫程序把那1000個訂單給查出來,手動發(fā)到mq里去再補(bǔ)一次
- (3)然后我們再來假設(shè)第三個坑
如果走的方式是消息積壓在mq里,那么如果你很長時間都沒處理掉,此時導(dǎo)致mq都快寫滿了,咋辦?這個還有別的辦法嗎?沒有,誰讓你第一個方案執(zhí)行的太慢了,你臨時寫程序,接入數(shù)據(jù)來消費(fèi),消費(fèi)一個丟棄一個,都不要了,快速消費(fèi)掉所有的消息。然后走第二個方案,到了晚上再補(bǔ)數(shù)據(jù)吧。


