一年前做過(guò)兩個(gè)比較膚淺的消息隊(duì)列的總結(jié),消息隊(duì)列作用垃它,消息隊(duì)列應(yīng)用-使用異步隊(duì)列就解耦了嗎
宇葱。如今回過(guò)頭來(lái)再梳理一下對(duì)消息隊(duì)列的認(rèn)知井佑。
某些大廠的云服務(wù)文檔對(duì)了解一些消息隊(duì)列基本知識(shí)還是比較有幫助比如亞馬遜消息隊(duì)列文檔
本文不梳理技術(shù)細(xì)節(jié)铅檩,僅總結(jié)自己在應(yīng)用層面的認(rèn)知唯卖。
消息隊(duì)列模型
消息隊(duì)列主要是兩種模型,隊(duì)列模型與主題模型躬柬。
- 隊(duì)列模型(queue)
也即點(diǎn)對(duì)點(diǎn)或者一對(duì)一模式拜轨,1個(gè)消息只會(huì)被一個(gè)消費(fèi)者消費(fèi)≡是啵可以有一個(gè)或者多個(gè)消費(fèi)者同時(shí)消費(fèi)一個(gè)隊(duì)列橄碾,但是被消費(fèi)的消息不會(huì)重復(fù)。 - 主題模型(topic)
也即發(fā)布訂閱模式颠锉,領(lǐng)域設(shè)計(jì)中也常說(shuō)扇出設(shè)計(jì)模式法牲。消息可以被多個(gè)消費(fèi)者消費(fèi)。此類(lèi)隊(duì)列一般被稱(chēng)為一個(gè)topic琼掠,所有訂閱該topic的consumer都可消費(fèi)所有消息拒垃。
消息隊(duì)列具體應(yīng)用
消息隊(duì)列功能
部分內(nèi)容參考消息隊(duì)列功能
消息隊(duì)列通常具備的功能如下:
- 異步通信
消息的生產(chǎn)者在消息成功發(fā)送到隊(duì)列中即可立即返回,不需等待消費(fèi)者接收并處理消息瓷蛙,生產(chǎn)和消費(fèi)完全異步悼瓮。該功能在隊(duì)列模型和主題模型均適用。 - 延遲隊(duì)列
許多消息隊(duì)列都支持為消息設(shè)置特定的傳送時(shí)間艰猬。如果需要為所有消息設(shè)置相同延遲横堡,可以設(shè)置一個(gè)延遲隊(duì)列。 - 死信隊(duì)列
死信隊(duì)列可以接收其他隊(duì)列發(fā)來(lái)的未成功處理的消息冠桃。這便于將此類(lèi)消息放在一邊以進(jìn)行深入檢測(cè)命贴,而不會(huì)妨礙隊(duì)列處理或?qū)?CPU 周期耗費(fèi)在可能永遠(yuǎn)無(wú)法成功處理的消息上。 - FIFO (先進(jìn)先出) 隊(duì)列
消息本身是有時(shí)間的先后順序的。生產(chǎn)者生產(chǎn)的消息到隊(duì)列也有到達(dá)順序胸蛛,消息隊(duì)列通常會(huì)提供盡力確保消息大致按其發(fā)送的順序進(jìn)行傳送污茵,且消息至少傳送一次。消費(fèi)者的消費(fèi)順序受網(wǎng)絡(luò)胚泌、確切到達(dá)時(shí)間省咨、處理速度等的影響,但有些消息隊(duì)列(比如kafka)玷室,采用特定規(guī)則(比如相同用戶(hù)的消息發(fā)送到一個(gè)partition零蓉,線程封閉的消費(fèi))可以保證消息的有序消費(fèi)。 - 至少一次 & 最多一次 & 精確一次
通常消息隊(duì)列都是支持至少一次消費(fèi)的穷缤。
精確一次可以通過(guò)至少一次+冪等梳理敌蜂。
最多一次會(huì)丟消息,除非消息可以丟失津肛,不然一般不用章喉。 - 隊(duì)列模型&主題模型
上文已討論 - 路由功能
topic模型下可以支持消息的路由。比如指定不同的路由key身坐,topic根據(jù)路由key分發(fā)到不同的consumer秸脱。再比如kafka中的partition其實(shí)也是一種路由方式,消息經(jīng)過(guò)hash后到達(dá)特定的partition部蛇,由某一個(gè)consumer線程消費(fèi)摊唇。 - 消息復(fù)用
topic模型下,同一個(gè)消息可以分發(fā)給多個(gè)consumer
消息隊(duì)列使用場(chǎng)景
消息隊(duì)列的作用大概可以用這么幾個(gè)詞來(lái)概括:異步涯鲁、削峰巷查、解耦、提高性能抹腿。
異步岛请,這是消息隊(duì)列最核心的功能,其他功能都依賴(lài)異步性實(shí)現(xiàn)警绩。生產(chǎn)者將消息發(fā)送到消息隊(duì)列后可以立即返回崇败,不用等待消費(fèi)者的響應(yīng)。
削峰房蝉,可以理解為異步帶來(lái)的好處之一僚匆,消息隊(duì)列能夠平衡生產(chǎn)和消費(fèi)的速度。生產(chǎn)的消息快可以暫時(shí)將消息留在隊(duì)列中搭幻,消費(fèi)者慢慢消費(fèi)咧擂。
解耦,這里主要指的是系統(tǒng)組件之間的通信方式的解耦檀蹋,也是異步帶來(lái)的好處之一松申,生產(chǎn)者不需要等待消費(fèi)者響應(yīng)云芦,在組件通信中,消息生產(chǎn)和消費(fèi)方都依賴(lài)消息隊(duì)列贸桶,兩者直接就無(wú)需直接耦合了舅逸。還有另外一種業(yè)務(wù)上的耦合,下文討論皇筛。
提高性能琉历,通過(guò)異步化,一方面可以對(duì)消息在消費(fèi)者里做聚合計(jì)算或者批量更新等處理水醋,提高性能旗笔。另一方面topic模型可以提高不同組件的并行度,下文討論拄踪。
圍繞著這幾個(gè)作用蝇恶,消息隊(duì)列的使用場(chǎng)景很多,舉幾個(gè)例子說(shuō)下消息隊(duì)列的部分使用場(chǎng)景:
- 提高通信的可靠性
網(wǎng)絡(luò)通信不可靠或者下游不可靠的情況下惶桐,可以使用消息隊(duì)列來(lái)確保消息的可靠投遞撮弧,增加系統(tǒng)的可靠性。消息生產(chǎn)者不需要過(guò)多關(guān)注下游或通信鏈路的穩(wěn)定性姚糊,交給消息隊(duì)列解決贿衍。為了提高消息投遞可靠性,可能會(huì)造成消費(fèi)者對(duì)消息的重復(fù)消費(fèi)救恨。 - 一對(duì)多消息發(fā)布
topic模型的應(yīng)用舌厨,生產(chǎn)者向多個(gè)訂閱者提供消息。比如一個(gè)訂單支付成功后忿薇,可能要很多其他組件做相應(yīng)處理,比如供應(yīng)鏈發(fā)貨躏哩、贈(zèng)送優(yōu)惠券署浩、核銷(xiāo)優(yōu)惠券、發(fā)送短信通知扫尺〗疃埃可以由訂單系統(tǒng)發(fā)送一個(gè)topic消息,供應(yīng)鏈組件正驻、優(yōu)惠券組件弊攘、通知組件訂閱消息,并行處理即可姑曙,解耦并且提高性能襟交。另外多用于領(lǐng)域事件發(fā)布場(chǎng)景,下文討論伤靠。
這個(gè)訂單的例子除了技術(shù)上的解耦外捣域,也是一種業(yè)務(wù)上的解耦。訂單服務(wù),不再依賴(lài)供應(yīng)鏈焕梅、優(yōu)惠券迹鹅、通知服務(wù)來(lái)完成訂單了。 - 屏蔽語(yǔ)言差異
有些公司存在多種技術(shù)棧并行的情況贞言,比如java斜棚、c#、Python等该窗。用消息隊(duì)列弟蚀,只需要與消息中間件交互即可,不存在語(yǔ)言差異挪捕。 - 系統(tǒng)組件的解耦
比如這么兩方面粗梭,生產(chǎn)者不需要等待消費(fèi)者響應(yīng)、上文提到的訂單和其他組件的解耦级零。 -
削峰
上文有提到断医。具體的case比如:秒殺場(chǎng)景下接口請(qǐng)求量巨大,除了用緩存等措施有效增強(qiáng)計(jì)算性能外奏纪,還可以通過(guò)消息隊(duì)列對(duì)接口請(qǐng)求做削峰處理鉴嗤。
- 提高計(jì)算性能
消費(fèi)方可以將消息聚合后做批量計(jì)算,比如批量寫(xiě)入數(shù)據(jù)庫(kù)序调,可以緩解數(shù)據(jù)庫(kù)壓力醉锅。另外如上文訂單例子,提高了計(jì)算并行度发绢。 - 優(yōu)先級(jí)隊(duì)列
根據(jù)topic的路由功能硬耍,可以針對(duì)某個(gè)特定的路由key加大計(jì)算量,優(yōu)先處理边酒。比如根據(jù)kafka的partition實(shí)現(xiàn)经柴。
消息隊(duì)列與解耦
通常情況下,我們說(shuō)的消息隊(duì)列的解耦功能墩朦,主要是由于其異步的特性帶來(lái)的技術(shù)上的解耦坯认,消息生產(chǎn)者不需要關(guān)注消費(fèi)者的消費(fèi)狀態(tài),可直接返回氓涣。
業(yè)務(wù)上來(lái)看牛哺,還是存在耦合的。比如業(yè)務(wù)耦合例子劳吠。耦合是不能完全避免的引润,需要我們做設(shè)計(jì)的過(guò)程中去權(quán)衡。這里根據(jù)經(jīng)驗(yàn)做下總結(jié):
- 如果消息隊(duì)列僅涉及到兩個(gè)系統(tǒng)組件痒玩,使用隊(duì)列模型椰拒,以消費(fèi)方為主和生產(chǎn)方協(xié)商好格式即可晶渠。
- 如果消息需要被重復(fù)消費(fèi),或者對(duì)接該消息的組件多于1個(gè)燃观,通常采用主題模型褒脯,由消息生產(chǎn)者定義格式,消費(fèi)者接收消息轉(zhuǎn)換成自己的邏輯語(yǔ)言處理缆毁。
- 涉及到領(lǐng)域事件的發(fā)布番川,通常使用主題模型,事件消費(fèi)方將所依賴(lài)的上下文的領(lǐng)域事件轉(zhuǎn)換自己所在的上下文通用語(yǔ)言做處理脊框。
總結(jié)來(lái)看颁督。消息隊(duì)列的解耦可以包括兩方面,技術(shù)上的解耦和業(yè)務(wù)上的解耦浇雹。業(yè)務(wù)上的耦合是不可避免的沉御,盡量做到降低耦合性以及消息的復(fù)用,我們的設(shè)計(jì)不能脫離具體的業(yè)務(wù)場(chǎng)景昭灵。
消息隊(duì)列與領(lǐng)域模型
DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))中的有一個(gè)概念叫領(lǐng)域事件吠裆。消息中間件是領(lǐng)域事件常用的存儲(chǔ)方式。
本文僅討論和消息隊(duì)列相關(guān)的內(nèi)容烂完,其他領(lǐng)域事件的用途试疙,比如用來(lái)做bug跟蹤、預(yù)測(cè)分析抠蚣、操作的撤銷(xiāo)(很多分布式存儲(chǔ)中的undo)祝旷,這里不做討論。
關(guān)于領(lǐng)域事件可以參考《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》這本書(shū)嘶窄』初耍看到一篇不錯(cuò)的文章,也是對(duì)該書(shū)內(nèi)容的總結(jié) 柄冲,同時(shí)擴(kuò)展了一些實(shí)現(xiàn)方式領(lǐng)域事件
領(lǐng)域事件
暫時(shí)沒(méi)有找到特別明確的定義敌完,《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中有過(guò)一些概念:領(lǐng)域事件是領(lǐng)域?qū)<宜P(guān)心的發(fā)生在領(lǐng)域中的一些事件等。
可以這么理解羊初,對(duì)于限界上下文中發(fā)生的每一件事,我們都用事件的形式予以捕獲并發(fā)布給訂閱方處理什湘。領(lǐng)域事件也應(yīng)該作為領(lǐng)域模型的通用語(yǔ)言的一部分长赞。(限界上下文和通用語(yǔ)言是領(lǐng)域模型戰(zhàn)略設(shè)計(jì)的核心)
領(lǐng)域事件的主要用途
- 保證聚合間的數(shù)據(jù)一致性
- 替換批量處理
- 實(shí)現(xiàn)事件源模式
- 進(jìn)行限界上下文集成
這些也可以理解為消息隊(duì)列的一些使用場(chǎng)景,當(dāng)然領(lǐng)域事件是不限于實(shí)現(xiàn)方式的闽撤。
領(lǐng)域事件的實(shí)現(xiàn)
最簡(jiǎn)單的領(lǐng)域事件的發(fā)布模式就是觀察者模式得哆,消息隊(duì)列或者領(lǐng)域設(shè)計(jì)中我們通常叫做發(fā)布-訂閱模式。
領(lǐng)域事件的消費(fèi)者可以是本地模塊也可以是遠(yuǎn)程模塊哟旗,對(duì)應(yīng)的領(lǐng)域事件存儲(chǔ)的方式包括共享內(nèi)存形式贩据、restful資源形式栋操、以及消息中間件等。
- 共享內(nèi)存的形式比較好理解饱亮,也即用代碼實(shí)現(xiàn)一個(gè)觀察者模式矾芙,將捕獲到的事件通過(guò)接口調(diào)用通知給訂閱方接口。
- restful資源的形式近上,可以這么理解剔宪,消息發(fā)布者通過(guò)restful的接口的形式來(lái)發(fā)布資源,消費(fèi)者根據(jù)消息區(qū)間(可以是時(shí)間或者id范圍)來(lái)拉取信息壹无,是一種拉的模式葱绒。這種方式,我在實(shí)踐過(guò)程中斗锭,在不同公司之間的數(shù)據(jù)同步場(chǎng)景用的比較多地淀。這種情況下
- 消息中間件的方式就是本文要討論的重點(diǎn)內(nèi)容了。在實(shí)踐過(guò)程中岖是,公司內(nèi)部多個(gè)系統(tǒng)組件之間的領(lǐng)域事件發(fā)布多采用這種形式帮毁。
這里主要討論共享內(nèi)存的形式無(wú)法處理的向遠(yuǎn)程限界上下文發(fā)布領(lǐng)域事件的方法。在不同的限界上下文見(jiàn)采用領(lǐng)域事件的形式通信時(shí)璧微,必須要保證最終一致性作箍。領(lǐng)域事件的發(fā)布通常是一個(gè)異步的過(guò)程,受限于各系統(tǒng)吞吐量等因素前硫,一個(gè)模型的改變可能要過(guò)一段時(shí)間才能提現(xiàn)到另外一個(gè)模型中胞得。
《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》書(shū)中討論了向遠(yuǎn)程限界上下文發(fā)布領(lǐng)域事件的三個(gè)問(wèn)題:
- 消息設(shè)施的一致性
無(wú)論是什么形式發(fā)布領(lǐng)域事件,要想保證最終一致性屹电,我們至少要保證兩個(gè)存儲(chǔ)的最終一致性:領(lǐng)域模型所使用的的持久化存儲(chǔ)和消息設(shè)施所使用的的持久化存儲(chǔ)阶剑。這樣保證了持久化領(lǐng)域模型是,領(lǐng)域事件也得以存儲(chǔ)并發(fā)布成功危号。如果這兩者不一致牧愁,會(huì)導(dǎo)致最終兩個(gè)限界上下文中的狀態(tài)錯(cuò)誤。
保證這個(gè)一致性有幾個(gè)辦法:
—領(lǐng)域模型和消息設(shè)施共享存儲(chǔ)外莲。在這種情況下猪半,模型和事件的提交在一個(gè)事務(wù)中完成,從而保證兩種的一致性偷线。
—領(lǐng)域模型的持久化和消息的持久化采用XA事務(wù)磨确,有個(gè)概念叫做事務(wù)消息。這種情況下声邦,模型和消息所用的持久化存儲(chǔ)可以分離乏奥,但會(huì)降低系統(tǒng)性能。
—領(lǐng)域模型的存儲(chǔ)中留一塊區(qū)域存儲(chǔ)領(lǐng)域事件亥曹,從而在本地事務(wù)中完成領(lǐng)域和事件的存儲(chǔ)邓了。然后恨诱,通過(guò)后臺(tái)服務(wù)將事件異步發(fā)送到消息隊(duì)列中。該方式和與消息設(shè)施共享存儲(chǔ)很像骗炉,區(qū)別在于該方式可以保證在一個(gè)本地事務(wù)提交照宝,還可通過(guò)restful的方式暴露事件資源。
一般情況下痕鳍,第三種硫豆,是比較優(yōu)雅的解決方案。
- 自治服務(wù)和系統(tǒng)
自治可以理解成沒(méi)有對(duì)遠(yuǎn)程RPC服務(wù)的調(diào)用笼呆,具備高度的獨(dú)立性熊响。RPC調(diào)用是具有可靠性、穩(wěn)定性風(fēng)險(xiǎn)诗赌。
通過(guò)領(lǐng)域事件的方式汗茄,自治服務(wù)將領(lǐng)域事件轉(zhuǎn)化為自己的通用語(yǔ)言進(jìn)行存儲(chǔ)。這里要注意铭若,消費(fèi)者不是對(duì)事件生產(chǎn)者的簡(jiǎn)單復(fù)制洪碳,而是要轉(zhuǎn)化成自己限界上下文的通用語(yǔ)言。 - 容許延時(shí)
事件的方式必然存在延時(shí)叼屠,數(shù)據(jù)的延時(shí)可能有比較嚴(yán)重的影響瞳腌,也可能幾無(wú)影響,只需要保證最終一致性即可镜雨,這塊使我們?cè)O(shè)計(jì)系統(tǒng)要考慮的重要因素嫂侍。
事務(wù)消息
消息生產(chǎn)者本地事務(wù)處理與消息發(fā)送可能存在不一致的情況,事務(wù)消息是用來(lái)實(shí)現(xiàn)消息生產(chǎn)者本地事務(wù)與消息發(fā)送的原子性荚坞,保證消息生產(chǎn)者本地事務(wù)處理成功與消息發(fā)送成功的最終一致挑宠。事務(wù)消息是分布式事務(wù)的一種解決方案。
事務(wù)消息有多種實(shí)現(xiàn)方式颓影,
有些是用戶(hù)配合消息中間件實(shí)現(xiàn)類(lèi)似 X/Open XA 的分布事務(wù)功能各淀。
有些是利用數(shù)據(jù)庫(kù)本地事務(wù),比如去哪兒開(kāi)源的qmq事務(wù)消息
也就是上文提到的消息一致性的第三種方案诡挂。
冪等性與消息去重
領(lǐng)域事件通乘榻剑可以理解為是一種值對(duì)象。值對(duì)象可以沒(méi)有唯一標(biāo)識(shí)璃俗。
但是在有些情況下奴璃,消息系統(tǒng)可能多次向消費(fèi)者發(fā)送重復(fù)的消息,這時(shí)候就需要做好消息的冪等性處理旧找,也就是消息的去重。
做消息去重可以在消息隊(duì)列處理也就是保證精確發(fā)送一次麦牺,也可以在訂閱方(消費(fèi)者)處理钮蛛。利用消息隊(duì)列處理將會(huì)很麻煩鞭缭。通常情況下我們?cè)谟嗛喎皆谙M(fèi)時(shí)基于自己的領(lǐng)域模型做冪等處理。而一種簡(jiǎn)單的方式便是發(fā)布方在發(fā)布事件消息時(shí)設(shè)置一個(gè)唯一的消息id作為事件的唯一標(biāo)識(shí)魏颓。
事件唯一標(biāo)識(shí)岭辣,將它作為通用屬性進(jìn)行管理,本身對(duì)領(lǐng)域建模影響不大甸饱,但對(duì)技術(shù)處理好處巨大沦童。當(dāng)我們需要將領(lǐng)域事件發(fā)布到外部的限界上下文時(shí),唯一標(biāo)識(shí)就是一種必然叹话。為了保證事件投遞的冪等性偷遗,在發(fā)送端,我們可能會(huì)進(jìn)行多次發(fā)送嘗試驼壶,直至明確發(fā)送成功為止氏豌;而在接收端,當(dāng)接收到事件后热凹,需要對(duì)事件進(jìn)行重復(fù)性檢測(cè)泵喘,以保障事件處理的冪等性。此時(shí)般妙,事件的唯一標(biāo)識(shí)便可以作為事件去重的依據(jù)纪铺。