消息中間件( Message Oriented Middleware,簡(jiǎn)稱MOM)在企業(yè)開發(fā)中變得越來(lái)越重要。本文介紹消息中間件中的四種消息投遞模型郊闯,主要是介紹模型的核心特性肌幽,以及不同模型之前的區(qū)別。這四種模型分別是:
PTP模型
Pub/Sub模型
Partition模型
Transfer模型(筆者自己起的名字)
?? ?? ? 其中PTP模型和Pub/Sub模型在JMS(Java Message Service)規(guī)范中有定義堆巧,消息中間件ActiveMQ就實(shí)現(xiàn)了JMS規(guī)范妄荔。JMS規(guī)范旨在于為消息中間件廠商提供了一個(gè)規(guī)范,使得Java應(yīng)用可以更加容易的訪問(wèn)不同的消息中間件產(chǎn)品谍肤,類似于我們可以通過(guò)JDBC規(guī)范定義的相關(guān)接口不同廠商的數(shù)據(jù)庫(kù)(Mysql/Oracle/SqlServer等)產(chǎn)品啦租。然而一些消息中間件,并沒有實(shí)現(xiàn)JMS規(guī)范荒揣,而是自己設(shè)計(jì)出了一套模型篷角,例如Kafka和RocketMQ就采用了Partition模型。此外業(yè)界還有一些其他的消息投遞模型系任,例如Transfer模型恳蹲,這是筆者自己起的名字虐块。
PTP模型
? ? ? ? Point-to-Point,點(diǎn)對(duì)點(diǎn)通信模型嘉蕾。PTP是基于隊(duì)列(Queue)的贺奠,一個(gè)隊(duì)列可以有多個(gè)生產(chǎn)者,和多個(gè)消費(fèi)者错忱。消息服務(wù)器按照收到消息的先后順序儡率,將消息放到隊(duì)列中。隊(duì)列中的每一條消息以清,只能由一個(gè)消費(fèi)者進(jìn)行消費(fèi)儿普,消費(fèi)之后就會(huì)從隊(duì)列中移除。
?
?? ?? ? 需要注意的是掷倔,盡管這里使用Queue的概念眉孩,但并不是先進(jìn)入隊(duì)列消息,一定會(huì)被先消費(fèi)勒葱。在存在多個(gè)下游Consumer情況下浪汪,一些消息中間件,例如ActiveMQ错森,為了提升消費(fèi)能力吟宦,會(huì)將隊(duì)列中的消息分發(fā)到不同Consumer并行進(jìn)行處理。這意味著消息發(fā)送的時(shí)候可能是有序的涩维,但是在消費(fèi)的時(shí)候殃姓,就變成無(wú)序了。
為了保證消費(fèi)的有序瓦阐,一些MQ提供了"專有消費(fèi)者”或者"排他消費(fèi)者”的概念蜗侈,在這種情況下,隊(duì)列中的消息僅允許一個(gè)消費(fèi)者進(jìn)行消費(fèi)睡蟋,如果存在多個(gè)消費(fèi)者踏幻,那么從中選擇一個(gè)。但是戳杀,這意味著在消息在處理中沒有了并行性该面。如果消息量很多的情況下,將會(huì)產(chǎn)生消息積壓信卡。
? ? ? ??為了解決"專有消費(fèi)者”的性能問(wèn)題隔缀,一些消息中間件采用分區(qū)的概念來(lái)解決性能問(wèn)題,我們將在后文進(jìn)行介紹傍菇。
Pub/Sub模型
?? ??? ?publish-and- subscribe猾瘸, 即發(fā)布訂閱模型。在Pub/Sub模型中,生產(chǎn)者將消息發(fā)布到一個(gè)主題(Topic)中牵触,訂閱了該Topic的所有下游消費(fèi)者淮悼,都可以接收到這條消息。如下圖:
?
?? ?? ? 通常情況下揽思,一個(gè)條消息只要被消費(fèi)一次就行了袜腥,那么什么情況下需要所有的消費(fèi)者都對(duì)這條消息進(jìn)行消費(fèi)呢?最典型的情況就是需要在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行緩存绰更,并需要實(shí)時(shí)進(jìn)行更新瞧挤。
例如,筆者做過(guò)一個(gè)違禁詞系統(tǒng)儡湾,對(duì)用戶輸入的評(píng)論內(nèi)容進(jìn)行違禁詞匯檢測(cè)。這個(gè)違禁詞系統(tǒng)执俩,部署了在N臺(tái)服務(wù)器上徐钠,為了提升檢測(cè)性能,每臺(tái)機(jī)器都會(huì)講違禁詞系統(tǒng)的詞庫(kù)全量加載內(nèi)存中役首,之后檢測(cè)時(shí)尝丐,對(duì)于用戶的評(píng)論內(nèi)容進(jìn)行分詞,判斷分詞后的內(nèi)容有沒有出現(xiàn)在違禁詞詞匯中衡奥。因?yàn)樵~庫(kù)中不可能包含所有的敏感詞爹袁,所以還要支持動(dòng)態(tài)的添加敏感詞。這個(gè)時(shí)候矮固,Pub/Sub模型就發(fā)揮作用了失息,每個(gè)機(jī)器上都啟動(dòng)一個(gè)consumer,當(dāng)生產(chǎn)者發(fā)送一個(gè)敏感詞到Topic中档址,所有的消費(fèi)者都會(huì)接受到這些消息盹兢,更新敏感詞庫(kù)。
Partition模型
為了解決在PTP模型下守伸,有序消息需要通過(guò)"專有消費(fèi)者”消費(fèi)帶來(lái)的性能問(wèn)題绎秒,一些消息中間件,如rocketmq尼摹,kafka采用了Partition模型见芹,即分區(qū)模型,如下所示:
?
?? ?? ? 生產(chǎn)者發(fā)送消息到某個(gè)Topic中時(shí)蠢涝,最終選擇其中一個(gè)Partition進(jìn)行發(fā)送玄呛。你可以將Parition模型中的分區(qū),理解為PTP模型的隊(duì)列惠赫,不同的是把鉴,PTP模型中的隊(duì)列存儲(chǔ)的是所有的消息,而每個(gè)Partition只會(huì)存儲(chǔ)部分?jǐn)?shù)據(jù)。
對(duì)于消息者庭砍,此時(shí)多了一個(gè)消費(fèi)者組的概念场晶,Paritition會(huì)在同一個(gè)消費(fèi)者組下的消費(fèi)者中進(jìn)行分配,每個(gè)消費(fèi)者只會(huì)消費(fèi)分配給自己的Paritition怠缸。例如上圖中诗轻,consumer 1分配到了parititon1,consumer 2分配到了parititon2和parititon3揭北,consumer N分配到了paritition 4扳炬。
?? ?? ? 通過(guò)這種方式,Paritition模式巧妙的將PTP模型和Pub/Sub模型結(jié)合在了一起搔体。
?? ??? ?對(duì)于PTP模型恨樟,一條消息只會(huì)由一個(gè)消費(fèi)者進(jìn)行消費(fèi),而Partition模型中每個(gè)分區(qū)最終也只會(huì)有一個(gè)消費(fèi)者進(jìn)行消費(fèi)疚俱。對(duì)于通過(guò)"專有消費(fèi)者"來(lái)保證全局消費(fèi)有序的場(chǎng)景劝术,在Partition模型中,只需保證創(chuàng)建的Topic只有一個(gè)Partition即可呆奕,這個(gè)Paritition最終也只會(huì)分配其中一個(gè)消費(fèi)者养晋。
?? ?? ? 另外,在絕大部分場(chǎng)景下梁钾,我們沒有必要保證全局有序绳泉,例如一個(gè)訂單產(chǎn)生了3條消息,分別是訂單創(chuàng)建姆泻,訂單付款零酪,訂單完成。消費(fèi)時(shí)麦射,要按照這個(gè)順序消費(fèi)才能有意義蛾娶。但是訂單之間是可以并行消費(fèi)的,例如將訂單1產(chǎn)生的3條消息發(fā)送到Partiton 1潜秋,將訂單2產(chǎn)生的3條消息發(fā)送到Partition 2蛔琅,如此便達(dá)到了不同訂單之間的并行消費(fèi)。
?? ?? ? 對(duì)于Pub/Sub模型峻呛,一條消息所有的下游消費(fèi)者都可以進(jìn)行消費(fèi)罗售。在Paritition模型中,只需要為每個(gè)消費(fèi)者設(shè)置成不同的消費(fèi)者組即可钩述。
?? ?? ? 然而寨躁,過(guò)多的消費(fèi)者組,會(huì)給消息中間件運(yùn)維帶來(lái)麻煩牙勘。所以一些消息中間件职恳,結(jié)合了Partition模型和Pub/Sub模型所禀。例例如RocketMQ,支持為消費(fèi)者組設(shè)置消費(fèi)模式放钦,如果是集群模式色徘,就按照上述描述進(jìn)行消費(fèi),如果是廣播模式操禀,就按照Pub/Sub模型進(jìn)行消費(fèi)褂策。
?? ?? ? 當(dāng)然,Partition模型也不全是優(yōu)點(diǎn)颓屑,其最大的限制在于Partition數(shù)量是固定的(雖然可以調(diào)整)斤寂,針對(duì)同一個(gè)消費(fèi)者組,一個(gè)Partition最多只可以分配給其中一個(gè)消費(fèi)者揪惦。當(dāng)消費(fèi)者的數(shù)量大于Partition數(shù)量時(shí)遍搞,這些多出來(lái)的消費(fèi)者將無(wú)法消費(fèi)到消息。一些消息中間件對(duì)此進(jìn)行了優(yōu)化器腋,即在對(duì)單個(gè)消費(fèi)者內(nèi)尾抑,同時(shí)啟動(dòng)多個(gè)線程,來(lái)消費(fèi)這個(gè)Partition中的數(shù)據(jù)蒂培,當(dāng)然前提是要求消息不是有序的,對(duì)于有序的消息榜苫,只能使用一個(gè)線程按順序消費(fèi)這個(gè)Partition中的數(shù)據(jù)护戳。
Transfer模型
?? ?? ? Paritition模型中的消費(fèi)者組概念很有用,同一個(gè)Topic下的消息可以由多個(gè)不同業(yè)務(wù)方進(jìn)行消費(fèi)垂睬,只要使用不同的消費(fèi)者組即可媳荒,不同消費(fèi)者組消費(fèi)到的位置單獨(dú)記錄,互不影響驹饺。?但是钳枕,Paritition模型還是限制了消費(fèi)者數(shù)量不能多于分區(qū)數(shù)。
?? ?? ? 因此赏壹,又有了另外一種消費(fèi)模型鱼炒,筆者稱之為Transfer模型,如下圖所示:
?
生產(chǎn)者還是將消息發(fā)送到Topic中蝌借,針對(duì)一個(gè)Topic昔瞧,可以創(chuàng)建多個(gè)通道,筆者稱之為channel菩佑。與分區(qū)不同的是自晰,發(fā)送到Topic中的每條消息,都會(huì)轉(zhuǎn)發(fā)到每個(gè)channel稍坯,因此每個(gè)channel都有這個(gè)Topic的全量數(shù)據(jù)酬荞。當(dāng)然,沒有必要把真的把消息體完整的拷貝一份到channel中,可以只記錄一下消息元數(shù)據(jù)混巧,表示有一條放到這個(gè)channel中了枪向。
?? ?? ? 消費(fèi)者在消費(fèi)消息時(shí),必須指定從哪個(gè)channel消費(fèi)牲剃。多個(gè)消費(fèi)者消費(fèi)同一個(gè)channel時(shí)遣疯,每條消息只會(huì)有一個(gè)消費(fèi)者消費(fèi)達(dá)到,這一點(diǎn)與PTP模型類似凿傅。事實(shí)上缠犀,我們可以認(rèn)為,消費(fèi)了同一個(gè)channel的消費(fèi)者聪舒,就自動(dòng)組成了一個(gè)消費(fèi)者組辨液。但是,與Partition模型不同的是箱残,這里沒有分區(qū)的概念滔迈,因此消費(fèi)者的數(shù)量可以是任意的。事實(shí)上被辑,GO語(yǔ)言編寫的NSQ消息中間件燎悍,采用的就是這種模型。
當(dāng)然盼理,這種模型與PTP一樣谈山,也不能保證被消息有序,除非通過(guò)類似于”專用消費(fèi)者”的概念宏怔。
免費(fèi)學(xué)習(xí)視頻歡迎關(guān)注云圖智聯(lián):https://e.yuntuzhilian.com/