##背景:做部門技術(shù)分享時(shí)邮偎,學(xué)習(xí)整理了消息隊(duì)列管跺。
一、應(yīng)用場(chǎng)景
消息隊(duì)列中間件是分布式系統(tǒng)中重要的組件禾进。主要解決 異步消息豁跑、應(yīng)用耦合、流量削鋒泻云、日志收集 等問題艇拍,實(shí)現(xiàn)高性能,高可用宠纯,可伸縮和最終一致性架構(gòu)卸夕。
異步處理? 場(chǎng)景:用戶注冊(cè)后,需要發(fā)注冊(cè)郵件和短信婆瓜。
傳統(tǒng)的做法有兩種:a) 串行的方式快集;b) 并行方式贡羔。
a) 串行方式:將注冊(cè)信息寫入數(shù)據(jù)庫(kù)成功后,發(fā)送注冊(cè)郵件个初,再發(fā)送注冊(cè)短信乖寒。
以上三個(gè)任務(wù)全部完成后,返回給客戶端院溺。
b) 并行方式:將注冊(cè)信息寫入數(shù)據(jù)庫(kù)成功后楣嘁,發(fā)送注冊(cè)郵件的同時(shí),發(fā)送注冊(cè)短信珍逸。
以上三個(gè)任務(wù)完成后逐虚,返回給客戶端。
兩種方式對(duì)比:假設(shè)三個(gè)業(yè)務(wù)節(jié)點(diǎn)每個(gè)使用50毫秒鐘弄息,不考慮網(wǎng)絡(luò)等其他開銷痊班。由于CPU在單位時(shí)間內(nèi)處理的請(qǐng)求數(shù)是一定的,假設(shè)CPU 1秒內(nèi)吞吐量是100次摹量。
1涤伐、串行方式的執(zhí)行時(shí)間是150毫秒,并行的時(shí)間是100毫秒缨称。
2凝果、串行方式1秒內(nèi)CPU可處理的請(qǐng)求量是7次(1000/150)
3、并行方式處理的請(qǐng)求量是10次(1000/100)
引入消息隊(duì)列睦尽,將不是必須的業(yè)務(wù)邏輯器净,異步處理。改造后的架構(gòu)如下:
1当凡、用戶的響應(yīng)時(shí)間55毫秒山害。
2、系統(tǒng)的吞吐量提高到每秒20 QPS沿量,比串行提高了3倍浪慌,比并行提高了2倍。
應(yīng)用解耦? 場(chǎng)景:用戶下單后朴则,訂單系統(tǒng)需要通知庫(kù)存系統(tǒng)权纤。
傳統(tǒng)的做法是,訂單系統(tǒng)調(diào)用庫(kù)存系統(tǒng)的接口乌妒。如下圖:
傳統(tǒng)模式的缺點(diǎn):
1汹想、假如庫(kù)存系統(tǒng)無(wú)法訪問,則訂單減庫(kù)存將失敗撤蚊,從而導(dǎo)致訂單失敗古掏。
2、訂單系統(tǒng)與庫(kù)存系統(tǒng)存在依賴侦啸。
引入消息隊(duì)列槽唾,改進(jìn)后架構(gòu)如下:
訂單系統(tǒng):用戶下單后席镀,完成持久化處理,將消息寫入消息隊(duì)列夏漱,返回用戶訂單成功。
庫(kù)存系統(tǒng):訂閱下單的消息顶捷,采用拉/推的方式挂绰,獲取下單信息,進(jìn)行庫(kù)存操作服赎。
1葵蒂、假如在下單時(shí)庫(kù)存系統(tǒng)不能正常使用,也不影響正常下單重虑。因?yàn)橄聠魏蠹叮唵蜗到y(tǒng)寫入消息隊(duì)列就不再關(guān)心其他的后續(xù)操作了。
2缺厉、實(shí)現(xiàn)訂單系統(tǒng)與庫(kù)存系統(tǒng)的應(yīng)用解耦永高。
流量削峰? 場(chǎng)景:秒殺活動(dòng),一般會(huì)因?yàn)榱髁窟^大提针,導(dǎo)致流量暴增命爬,應(yīng)用掛掉。
為解決這個(gè)問題辐脖,一般需要在應(yīng)用前端加入消息隊(duì)列饲宛。用戶的請(qǐng)求,服務(wù)器接收后嗜价,首先寫入消息隊(duì)列艇抠。假如消息隊(duì)列長(zhǎng)度超過最大數(shù)量,則直接拋棄用戶請(qǐng)求或跳轉(zhuǎn)到錯(cuò)誤頁(yè)面久锥,秒殺業(yè)務(wù)根據(jù)消息隊(duì)列中的請(qǐng)求信息家淤,再做后續(xù)處理。如圖:
1奴拦、可以控制活動(dòng)的人數(shù)媒鼓。
2、可以緩解短時(shí)間內(nèi)高流量壓垮應(yīng)用错妖。
日志處理? 場(chǎng)景:將消息隊(duì)列用在日志處理中绿鸣,比如Kafka,解決大量日志傳輸?shù)膯栴}暂氯。
1潮模、日志采集客戶端,負(fù)責(zé)日志數(shù)據(jù)采集痴施,定時(shí)寫受寫入Kafka隊(duì)列擎厢。
2究流、Kafka消息隊(duì)列,負(fù)責(zé)日志數(shù)據(jù)的接收动遭,存儲(chǔ)和轉(zhuǎn)發(fā)芬探。
3、日志處理應(yīng)用:訂閱并消費(fèi)kafka隊(duì)列中的日志數(shù)據(jù)厘惦。
二偷仿、兩種模式
消息隊(duì)列的兩種模式:點(diǎn)對(duì)點(diǎn)、發(fā)布訂閱宵蕉。
p2p(點(diǎn)對(duì)點(diǎn))包含三個(gè)角色:消息隊(duì)列(Queue)酝静、發(fā)送者(Sender)、接收者(Receiver)
1羡玛、每個(gè)消息都被發(fā)送到一個(gè)特定的隊(duì)列别智,接收者從隊(duì)列中獲取消息。
2稼稿、隊(duì)列保留著消息薄榛,直到他們被消費(fèi)或超時(shí)。
P2P模式的特點(diǎn):
1让歼、每個(gè)消息只有一個(gè)消費(fèi)者(Consumer)(即一旦被消費(fèi)蛇数,消息就不再在消息隊(duì)列中)。
2是越、發(fā)送者和接收者之間在時(shí)間上沒有依賴性耳舅。
Pub/Sub(發(fā)布訂閱)包含三個(gè)角色:主題(Topic)、發(fā)布者(Publisher)倚评、訂閱者(Subscriber)
1浦徊、多個(gè)發(fā)布者將消息發(fā)送到Topic,系統(tǒng)將這些消息傳遞給多個(gè)訂閱者天梧。
Pub/Sub的特點(diǎn):
1盔性、每個(gè)消息可以有多個(gè)消費(fèi)者。
2呢岗、發(fā)布者和訂閱者之間有時(shí)間上的依賴性冕香。
3、為了消費(fèi)消息后豫,訂閱者必須保持運(yùn)行的狀態(tài)悉尾。
三、組成部分
幾個(gè)重要概念
Producer:消息生產(chǎn)者挫酿,就是投遞消息的程序构眯。
Broker:消息隊(duì)列服務(wù)器實(shí)體。
Consumer:消息消費(fèi)者早龟,就是接受消息的程序惫霸。
Queue:消息隊(duì)列載體猫缭,每個(gè)消息都會(huì)被投入到一個(gè)或多個(gè)隊(duì)列。
消息隊(duì)列邏輯結(jié)構(gòu)如下壹店,其中中間是queue猜丹。(實(shí)際載體為broker)
消息隊(duì)列的本質(zhì):兩次RPC加一次轉(zhuǎn)儲(chǔ)
1、RPC通信協(xié)議:負(fù)載均衡硅卢、服務(wù)發(fā)現(xiàn)居触、通信協(xié)議、序列化協(xié)議老赤。
2、高可用:依賴于RPC和存儲(chǔ)的高可用來(lái)做的制市。
3抬旺、服務(wù)端承載消息堆積的能力(依賴4)
a) 為了滿足錯(cuò)峰/流控/最終可達(dá)等一系列需求,把消息存儲(chǔ)下來(lái)祥楣,然后選擇時(shí)機(jī)投遞开财;
b) 存儲(chǔ)可以做成很多方式。比如存儲(chǔ)在內(nèi)存里误褪,存儲(chǔ)在分布式KV里责鳍,存儲(chǔ)在磁盤里,存儲(chǔ)在數(shù)據(jù)庫(kù)里等等兽间。歸結(jié)起來(lái)历葛,主要有持久化和非持久化兩種。持久化的形式能更大程度地保證消息的可靠性(如斷電等不可抗外力)嘀略,并且理論上能承載更大限度的消息堆積(外存的空間遠(yuǎn)大于內(nèi)存)
4恤溶、存儲(chǔ)子系統(tǒng)選型:從速度來(lái)看,文件系統(tǒng)>分布式KV(持久化)>分布式文件系統(tǒng)>數(shù)據(jù)庫(kù)帜羊,可靠性截然相反咒程。
5、消費(fèi)關(guān)系選型
a) 解析發(fā)送接收關(guān)系讼育,進(jìn)行正確的消息投遞了帐姻;
b) 發(fā)送關(guān)系的維護(hù),發(fā)送關(guān)系變更時(shí)的通知奶段,如config server饥瓷、zookeeper等。
四痹籍、典型問題
順序有序:指的是可以按照消息的發(fā)送順序來(lái)消費(fèi)
例如:一筆訂單產(chǎn)生了 3 條消息扛伍,分別是訂單創(chuàng)建、訂單付款词裤、訂單完成刺洒。消費(fèi)時(shí)鳖宾,要按照順序依次消費(fèi)才有意義。與此同時(shí)多筆訂單之間又是可以并行消費(fèi)的逆航。示例:假如生產(chǎn)者產(chǎn)生了2條消息:M1鼎文、M2,要保證這兩條消息的順序因俐,應(yīng)該怎樣做拇惋?
假定M1發(fā)送到S1抹剩,M2發(fā)送到S2撑帖,如果要保證M1先于M2被消費(fèi),那么需要M1到達(dá)消費(fèi)端被消費(fèi)后澳眷,通知S2胡嘿,然后S2再將M2發(fā)送到消費(fèi)端。如果M1和M2分別發(fā)送到兩臺(tái)Server上钳踊,就不能保證M1先達(dá)到MQ集群衷敌,也不能保證M1被先消費(fèi)。換個(gè)角度看拓瞪,如果M2先于M1達(dá)到MQ集群缴罗,甚至M2被消費(fèi)后,M1才達(dá)到消費(fèi)端祭埂,這時(shí)消息也就亂序了面氓。說明以上模型是不能保證消息的順序的。
如何才能在MQ集群保證消息的順序蛆橡?
一種簡(jiǎn)單的方式就是將M1侧但、M2發(fā)送到同一個(gè)Server上:
這樣可以保證M1先于M2到達(dá)MQServer(生產(chǎn)者等待M1發(fā)送成功后再發(fā)送M2)航罗,根據(jù)先達(dá)到先被消費(fèi)的原則禀横,M1會(huì)先于M2被消費(fèi),這樣就保證了消息的順序粥血。這個(gè)模型也僅僅是理論上可以保證消息的順序柏锄,在實(shí)際場(chǎng)景中可能會(huì)遇到下面的問題:M1晚于M2到達(dá)消費(fèi)端。
如果發(fā)送M1耗時(shí)大于發(fā)送M2的耗時(shí)复亏,那么M2就仍將被先消費(fèi)趾娃。即使M1和M2同時(shí)到達(dá)消費(fèi)端,由于2個(gè)消費(fèi)端負(fù)載不同缔御,仍然可能出現(xiàn)M2先消費(fèi)抬闷。
那如何解決這個(gè)問題?
將M1和M2發(fā)往同一個(gè)消費(fèi)者,且發(fā)送M1后笤成,需要消費(fèi)端響應(yīng)成功后才能發(fā)送M2评架。M1被發(fā)送到消費(fèi)端后,消費(fèi)端1沒有響應(yīng)炕泳,那是繼續(xù)發(fā)送M2呢纵诞,還是重新發(fā)送M1?一般為了保證消息一定被消費(fèi)培遵,肯定會(huì)選擇重發(fā)M1到另外一個(gè)消費(fèi)端2浙芙。
總結(jié)起來(lái),要實(shí)現(xiàn)嚴(yán)格的順序消息籽腕,簡(jiǎn)單且可行的辦法就是:保證? 生產(chǎn)者 - Server - 消費(fèi)者是一對(duì)一對(duì)一的關(guān)系
這樣的設(shè)計(jì)雖然簡(jiǎn)單易行嗡呼,但也會(huì)存在一些很嚴(yán)重的問題,比如:
1皇耗、并行度就會(huì)成為消息系統(tǒng)的瓶頸(吞吐量不夠)
2南窗、更多的異常處理,比如:只要消費(fèi)端出現(xiàn)問題廊宪,就會(huì)導(dǎo)致整個(gè)處理流程阻塞。
消息重復(fù)
消費(fèi)端1沒有響應(yīng)Server時(shí)有兩種情況:
1女轿、M1確實(shí)沒有到達(dá)(數(shù)據(jù)在網(wǎng)絡(luò)傳送中丟失)箭启;
2、消費(fèi)端已經(jīng)消費(fèi)M1且已經(jīng)發(fā)送響應(yīng)消息蛉迹,只是MQ Server端沒有收到傅寡。
如果是第二種情況,重發(fā)M1北救,就會(huì)造成M1被重復(fù)消費(fèi)荐操。也就引入了消息重復(fù)問題。
造成消息重復(fù)的根本原因是:網(wǎng)絡(luò)不可達(dá)珍策。
解決辦法:
1托启、消費(fèi)端處理消息的業(yè)務(wù)邏輯保持冪等性。
2攘宙、保證每條消息都有唯一編號(hào)且保證消息處理成功與去重表的日志同時(shí)出現(xiàn)屯耸。
版本號(hào)應(yīng)用
示例:一個(gè)產(chǎn)品的狀態(tài)有上線、下線蹭劈。消息M1是上線疗绣,M2是下線。不巧M1判重失敗铺韧,被投遞了兩次多矮,且第二次發(fā)生在M2之后,如果不做重復(fù)性判斷哈打,顯然最終狀態(tài)是錯(cuò)誤的塔逃。
引入版本號(hào):每個(gè)消息自帶一個(gè)版本號(hào)讯壶。
每次只接受比當(dāng)前版本號(hào)大的消息。初始版本為0患雏,當(dāng)消息1到達(dá)時(shí)鹏溯,將版本號(hào)更新為1。消息2到來(lái)時(shí)淹仑,因?yàn)榘姹咎?hào)>1.可以接收丙挽。? ? 同時(shí)更新版本號(hào)為2.當(dāng)另一條下線消息到來(lái)時(shí),如果版本號(hào)是3.則是真實(shí)的下線消息匀借。如果是1颜阐,則是重復(fù)投遞的消息。
新的問題:但很多時(shí)候吓肋,消息到來(lái)的順序錯(cuò)亂了凳怨。比如應(yīng)該的順序是12,到來(lái)的順序是21是鬼。
解決方案:只處理版本+1肤舞,如果想讓亂序的消息最后能夠正確的被組織,那么就應(yīng)該只接收比當(dāng)前版本號(hào)大一的消息均蜜。
參考及引用資料