為什么使用MQ瞻鹏?MQ的優(yōu)點(diǎn)
簡(jiǎn)答
異步處理 - 相比于傳統(tǒng)的串行、并行方式捺萌,提高了系統(tǒng)吞吐量档冬。
應(yīng)用解耦 - 系統(tǒng)間通過(guò)消息通信,不用關(guān)心其他系統(tǒng)的處理桃纯。
流量削鋒 - 可以通過(guò)消息隊(duì)列長(zhǎng)度控制請(qǐng)求量酷誓;可以緩解短時(shí)間內(nèi)的高并發(fā)請(qǐng)求。
日志處理 - 解決大量日志傳輸态坦。
消息通訊 - 消息隊(duì)列一般都內(nèi)置了高效的通信機(jī)制盐数,因此也可以用在純的消息通訊。比如實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)消息隊(duì)列伞梯,或者聊天室等玫氢。
詳答
主要是:解耦、異步谜诫、削峰漾峡。
解耦:A 系統(tǒng)發(fā)送數(shù)據(jù)到 BCD 三個(gè)系統(tǒng),通過(guò)接口調(diào)用發(fā)送喻旷。如果 E 系統(tǒng)也要這個(gè)數(shù)據(jù)呢生逸?那如果 C 系統(tǒng)現(xiàn)在不需要了呢?A 系統(tǒng)負(fù)責(zé)人幾乎崩潰…A 系統(tǒng)跟其它各種亂七八糟的系統(tǒng)嚴(yán)重耦合掰邢,A 系統(tǒng)產(chǎn)生一條比較關(guān)鍵的數(shù)據(jù)牺陶,很多系統(tǒng)都需要 A 系統(tǒng)將這個(gè)數(shù)據(jù)發(fā)送過(guò)來(lái)伟阔。如果使用 MQ辣之,A 系統(tǒng)產(chǎn)生一條數(shù)據(jù),發(fā)送到 MQ 里面去皱炉,哪個(gè)系統(tǒng)需要數(shù)據(jù)自己去 MQ 里面消費(fèi)怀估。如果新系統(tǒng)需要數(shù)據(jù),直接從 MQ 里消費(fèi)即可合搅;如果某個(gè)系統(tǒng)不需要這條數(shù)據(jù)了多搀,就取消對(duì) MQ 消息的消費(fèi)即可。這樣下來(lái)灾部,A 系統(tǒng)壓根兒不需要去考慮要給誰(shuí)發(fā)送數(shù)據(jù)康铭,不需要維護(hù)這個(gè)代碼,也不需要考慮人家是否調(diào)用成功赌髓、失敗超時(shí)等情況从藤。
就是一個(gè)系統(tǒng)或者一個(gè)模塊催跪,調(diào)用了多個(gè)系統(tǒng)或者模塊,互相之間的調(diào)用很復(fù)雜夷野,維護(hù)起來(lái)很麻煩懊蒸。但是其實(shí)這個(gè)調(diào)用是不需要直接同步調(diào)用接口的,如果用 MQ 給它異步化解耦悯搔。
異步:A 系統(tǒng)接收一個(gè)請(qǐng)求骑丸,需要在自己本地寫庫(kù),還需要在 BCD 三個(gè)系統(tǒng)寫庫(kù)妒貌,自己本地寫庫(kù)要 3ms通危,BCD 三個(gè)系統(tǒng)分別寫庫(kù)要 300ms、450ms灌曙、200ms黄鳍。最終請(qǐng)求總延時(shí)是 3 + 300 + 450 + 200 = 953ms,接近 1s平匈,用戶感覺(jué)搞個(gè)什么東西框沟,慢死了慢死了。用戶通過(guò)瀏覽器發(fā)起請(qǐng)求增炭。如果使用 MQ忍燥,那么 A 系統(tǒng)連續(xù)發(fā)送 3 條消息到 MQ 隊(duì)列中,假如耗時(shí) 5ms隙姿,A 系統(tǒng)從接受一個(gè)請(qǐng)求到返回響應(yīng)給用戶梅垄,總時(shí)長(zhǎng)是 3 + 5 = 8ms。
削峰:減少高峰時(shí)期對(duì)服務(wù)器壓力输玷。
消息隊(duì)列有什么優(yōu)缺點(diǎn)队丝?RabbitMQ有什么優(yōu)缺點(diǎn)?
優(yōu)點(diǎn)上面已經(jīng)說(shuō)了欲鹏,就是在特殊場(chǎng)景下有其對(duì)應(yīng)的好處机久,解耦、異步赔嚎、削峰膘盖。
缺點(diǎn)有以下幾個(gè):
系統(tǒng)可用性降低
本來(lái)系統(tǒng)運(yùn)行好好的,現(xiàn)在你非要加入個(gè)消息隊(duì)列進(jìn)去尤误,那消息隊(duì)列掛了侠畔,你的系統(tǒng)不是呵呵了。因此损晤,系統(tǒng)可用性會(huì)降低软棺;
系統(tǒng)復(fù)雜度提高
加入了消息隊(duì)列,要多考慮很多方面的問(wèn)題尤勋,比如:一致性問(wèn)題喘落、如何保證消息不被重復(fù)消費(fèi)德崭、如何保證消息可靠性傳輸?shù)取R虼艘九蹋枰紤]的東西更多眉厨,復(fù)雜性增大。
一致性問(wèn)題
A 系統(tǒng)處理完了直接返回成功了兽狭,人都以為你這個(gè)請(qǐng)求就成功了憾股;但是問(wèn)題是,要是 BCD 三個(gè)系統(tǒng)那里箕慧,BD 兩個(gè)系統(tǒng)寫庫(kù)成功了服球,結(jié)果 C 系統(tǒng)寫庫(kù)失敗了,咋整颠焦?你這數(shù)據(jù)就不一致了斩熊。
所以消息隊(duì)列實(shí)際是一種非常復(fù)雜的架構(gòu),你引入它有很多好處伐庭,但是也得針對(duì)它帶來(lái)的壞處做各種額外的技術(shù)方案和架構(gòu)來(lái)規(guī)避掉粉渠,做好之后,你會(huì)發(fā)現(xiàn)圾另,媽呀霸株,系統(tǒng)復(fù)雜度提升了一個(gè)數(shù)量級(jí),也許是復(fù)雜了 10 倍集乔。但是關(guān)鍵時(shí)刻去件,用,還是得用的扰路。
你們公司生產(chǎn)環(huán)境用的是什么消息中間件尤溜?
這個(gè)首先你可以說(shuō)下你們公司選用的是什么消息中間件,比如用的是RabbitMQ汗唱,然后可以初步給一些你對(duì)不同MQ中間件技術(shù)的選型分析宫莱。
舉個(gè)例子:比如說(shuō)ActiveMQ是老牌的消息中間件,國(guó)內(nèi)很多公司過(guò)去運(yùn)用的還是非常廣泛的渡嚣,功能很強(qiáng)大梢睛。
但是問(wèn)題在于沒(méi)法確認(rèn)ActiveMQ可以支撐互聯(lián)網(wǎng)公司的高并發(fā)肥印、高負(fù)載以及高吞吐的復(fù)雜場(chǎng)景识椰,在國(guó)內(nèi)互聯(lián)網(wǎng)公司落地較少。而且使用較多的是一些傳統(tǒng)企業(yè)深碱,用ActiveMQ做異步調(diào)用和系統(tǒng)解耦腹鹉。
然后你可以說(shuō)說(shuō)RabbitMQ,他的好處在于可以支撐高并發(fā)敷硅、高吞吐功咒、性能很高愉阎,同時(shí)有非常完善便捷的后臺(tái)管理界面可以使用。
另外力奋,他還支持集群化榜旦、高可用部署架構(gòu)、消息高可靠支持景殷,功能較為完善溅呢。
而且經(jīng)過(guò)調(diào)研,國(guó)內(nèi)各大互聯(lián)網(wǎng)公司落地大規(guī)模RabbitMQ集群支撐自身業(yè)務(wù)的case較多猿挚,國(guó)內(nèi)各種中小型互聯(lián)網(wǎng)公司使用RabbitMQ的實(shí)踐也比較多咐旧。
除此之外,RabbitMQ的開(kāi)源社區(qū)很活躍绩蜻,較高頻率的迭代版本铣墨,來(lái)修復(fù)發(fā)現(xiàn)的bug以及進(jìn)行各種優(yōu)化,因此綜合考慮過(guò)后办绝,公司采取了RabbitMQ伊约。
但是RabbitMQ也有一點(diǎn)缺陷,就是他自身是基于erlang語(yǔ)言開(kāi)發(fā)的孕蝉,所以導(dǎo)致較為難以分析里面的源碼碱妆,也較難進(jìn)行深層次的源碼定制和改造,畢竟需要較為扎實(shí)的erlang語(yǔ)言功底才可以昔驱。
然后可以聊聊RocketMQ疹尾,是阿里開(kāi)源的,經(jīng)過(guò)阿里的生產(chǎn)環(huán)境的超高并發(fā)骤肛、高吞吐的考驗(yàn)纳本,性能卓越,同時(shí)還支持分布式事務(wù)等特殊場(chǎng)景腋颠。
而且RocketMQ是基于Java語(yǔ)言開(kāi)發(fā)的繁成,適合深入閱讀源碼,有需要可以站在源碼層面解決線上生產(chǎn)問(wèn)題淑玫,包括源碼的二次開(kāi)發(fā)和改造巾腕。
另外就是Kafka。Kafka提供的消息中間件的功能明顯較少一些絮蒿,相對(duì)上述幾款MQ中間件要少很多尊搬。
但是Kafka的優(yōu)勢(shì)在于專為超高吞吐量的實(shí)時(shí)日志采集、實(shí)時(shí)數(shù)據(jù)同步土涝、實(shí)時(shí)數(shù)據(jù)計(jì)算等場(chǎng)景來(lái)設(shè)計(jì)佛寿。
因此Kafka在大數(shù)據(jù)領(lǐng)域中配合實(shí)時(shí)計(jì)算技術(shù)(比如Spark Streaming、Storm但壮、Flink)使用的較多冀泻。但是在傳統(tǒng)的MQ中間件使用場(chǎng)景中較少采用常侣。
Kafka、ActiveMQ弹渔、RabbitMQ胳施、RocketMQ 有什么優(yōu)缺點(diǎn)?
ActiveMQRabbitMQRocketMQKafkaZeroMQ
單機(jī)吞吐量比RabbitMQ低2.6w/s(消息做持久化)11.6w/s17.3w/s29w/s
開(kāi)發(fā)語(yǔ)言JavaErlangJavaScala/JavaC
主要維護(hù)者ApacheMozilla/SpringAlibabaApacheiMatix肢专,創(chuàng)始人已去世
成熟度成熟成熟開(kāi)源版本不夠成熟比較成熟只有C巾乳、PHP等版本成熟
訂閱形式點(diǎn)對(duì)點(diǎn)(p2p)、廣播(發(fā)布-訂閱)提供了4種:direct, topic ,Headers和fanout鸟召。fanout就是廣播模式基于topic/messageTag以及按照消息類型胆绊、屬性進(jìn)行正則匹配的發(fā)布訂閱模式基于topic以及按照topic進(jìn)行正則匹配的發(fā)布訂閱模式點(diǎn)對(duì)點(diǎn)(p2p)
持久化支持少量堆積支持少量堆積支持大量堆積支持大量堆積不支持
順序消息不支持不支持支持支持不支持
性能穩(wěn)定性好好一般較差很好
集群方式支持簡(jiǎn)單集群模式,比如’主-備’欧募,對(duì)高級(jí)集群模式支持不好压状。支持簡(jiǎn)單集群,'復(fù)制’模式跟继,對(duì)高級(jí)集群模式支持不好种冬。常用 多對(duì)’Master-Slave’ 模式,開(kāi)源版本需手動(dòng)切換Slave變成Master天然的‘Leader-Slave’無(wú)狀態(tài)集群舔糖,每臺(tái)服務(wù)器既是Master也是Slave不支持
管理界面一般較好一般無(wú)無(wú)
綜上娱两,各種對(duì)比之后,有如下建議:
一般的業(yè)務(wù)系統(tǒng)要引入 MQ金吗,最早大家都用 ActiveMQ落包,但是現(xiàn)在確實(shí)大家用的不多了始衅,沒(méi)經(jīng)過(guò)大規(guī)模吞吐量場(chǎng)景的驗(yàn)證蓖租,社區(qū)也不是很活躍絮姆,所以大家還是算了吧,我個(gè)人不推薦用這個(gè)了卫袒;
后來(lái)大家開(kāi)始用 RabbitMQ宵呛,但是確實(shí) erlang 語(yǔ)言阻止了大量的 Java 工程師去深入研究和掌控它,對(duì)公司而言夕凝,幾乎處于不可控的狀態(tài)宝穗,但是確實(shí)人家是開(kāi)源的,比較穩(wěn)定的支持码秉,活躍度也高逮矛;
不過(guò)現(xiàn)在確實(shí)越來(lái)越多的公司會(huì)去用 RocketMQ,確實(shí)很不錯(cuò)泡徙,畢竟是阿里出品橱鹏,但社區(qū)可能有突然黃掉的風(fēng)險(xiǎn)(目前 RocketMQ 已捐給Apache,但 GitHub 上的活躍度其實(shí)不算高)對(duì)自己公司技術(shù)實(shí)力有絕對(duì)自信的堪藐,推薦用 RocketMQ莉兰,否則回去老老實(shí)實(shí)用 RabbitMQ 吧,人家有活躍的開(kāi)源社區(qū)礁竞,絕對(duì)不會(huì)黃糖荒。
所以中小型公司,技術(shù)實(shí)力較為一般模捂,技術(shù)挑戰(zhàn)不是特別高捶朵,用 RabbitMQ 是不錯(cuò)的選擇;大型公司狂男,基礎(chǔ)架構(gòu)研發(fā)實(shí)力較強(qiáng)综看,用 RocketMQ 是很好的選擇。
如果是大數(shù)據(jù)領(lǐng)域的實(shí)時(shí)計(jì)算岖食、日志采集等場(chǎng)景红碑,用 Kafka 是業(yè)內(nèi)標(biāo)準(zhǔn)的,絕對(duì)沒(méi)問(wèn)題泡垃,社區(qū)活躍度很高析珊,絕對(duì)不會(huì)黃,何況幾乎是全世界這個(gè)領(lǐng)域的事實(shí)性規(guī)范蔑穴。
MQ 有哪些常見(jiàn)問(wèn)題忠寻?如何解決這些問(wèn)題?
MQ 的常見(jiàn)問(wèn)題有:
消息的順序問(wèn)題
消息的重復(fù)問(wèn)題
消息的順序問(wèn)題
消息有序指的是可以按照消息的發(fā)送順序來(lái)消費(fèi)存和。
假如生產(chǎn)者產(chǎn)生了 2 條消息:M1奕剃、M2,假定 M1 發(fā)送到 S1捐腿,M2 發(fā)送到 S2祭饭,如果要保證 M1 先于 M2 被消費(fèi),怎么做叙量?
img
解決方案:
(1)保證生產(chǎn)者 - MQServer - 消費(fèi)者是一對(duì)一對(duì)一的關(guān)系
img
缺陷:
并行度就會(huì)成為消息系統(tǒng)的瓶頸(吞吐量不夠)
更多的異常處理倡蝙,比如:只要消費(fèi)端出現(xiàn)問(wèn)題,就會(huì)導(dǎo)致整個(gè)處理流程阻塞绞佩,我們不得不花費(fèi)更多的精力來(lái)解決阻塞的問(wèn)題寺鸥。 (2)通過(guò)合理的設(shè)計(jì)或者將問(wèn)題分解來(lái)規(guī)避。
不關(guān)注亂序的應(yīng)用實(shí)際大量存在
隊(duì)列無(wú)序并不意味著消息無(wú)序 所以從業(yè)務(wù)層面來(lái)保證消息的順序而不僅僅是依賴于消息系統(tǒng)品山,是一種更合理的方式胆建。
消息的重復(fù)問(wèn)題
造成消息重復(fù)的根本原因是:網(wǎng)絡(luò)不可達(dá)。
所以解決這個(gè)問(wèn)題的辦法就是繞過(guò)這個(gè)問(wèn)題肘交。那么問(wèn)題就變成了:如果消費(fèi)端收到兩條一樣的消息笆载,應(yīng)該怎樣處理?
消費(fèi)端處理消息的業(yè)務(wù)邏輯保持冪等性。只要保持冪等性凉驻,不管來(lái)多少條重復(fù)消息腻要,最后處理的結(jié)果都一樣。保證每條消息都有唯一編號(hào)且保證消息處理成功與去重表的日志同時(shí)出現(xiàn)涝登。利用一張日志表來(lái)記錄已經(jīng)處理成功的消息的 ID雄家,如果新到的消息 ID 已經(jīng)在日志表中,那么就不再處理這條消息胀滚。
什么是RabbitMQ趟济?
RabbitMQ是一款開(kāi)源的,Erlang編寫的咽笼,基于AMQP協(xié)議的消息中間件
rabbitmq 的使用場(chǎng)景
(1)服務(wù)間異步通信
(2)順序消費(fèi)
(3)定時(shí)任務(wù)
(4)請(qǐng)求削峰
RabbitMQ基本概念
Broker: 簡(jiǎn)單來(lái)說(shuō)就是消息隊(duì)列服務(wù)器實(shí)體
Exchange: 消息交換機(jī)顷编,它指定消息按什么規(guī)則,路由到哪個(gè)隊(duì)列
Queue: 消息隊(duì)列載體剑刑,每個(gè)消息都會(huì)被投入到一個(gè)或多個(gè)隊(duì)列
Binding: 綁定媳纬,它的作用就是把exchange和queue按照路由規(guī)則綁定起來(lái)
Routing Key: 路由關(guān)鍵字,exchange根據(jù)這個(gè)關(guān)鍵字進(jìn)行消息投遞
VHost: vhost 可以理解為虛擬 broker 叛甫,即 mini-RabbitMQ server层宫。其內(nèi)部均含有獨(dú)立的 queue、exchange 和 binding 等其监,但最最重要的是萌腿,其擁有獨(dú)立的權(quán)限系統(tǒng),可以做到 vhost 范圍的用戶控制抖苦。當(dāng)然毁菱,從 RabbitMQ 的全局角度,vhost 可以作為不同權(quán)限隔離的手段(一個(gè)典型的例子就是不同的應(yīng)用可以跑在不同的 vhost 中)锌历。
Producer: 消息生產(chǎn)者贮庞,就是投遞消息的程序
Consumer: 消息消費(fèi)者,就是接受消息的程序
Channel: 消息通道究西,在客戶端的每個(gè)連接里窗慎,可建立多個(gè)channel,每個(gè)channel代表一個(gè)會(huì)話任務(wù)
由Exchange卤材、Queue遮斥、RoutingKey三個(gè)才能決定一個(gè)從Exchange到Queue的唯一的線路。
RabbitMQ的工作模式
一.simple模式(即最簡(jiǎn)單的收發(fā)模式)
img
1.消息產(chǎn)生消息扇丛,將消息放入隊(duì)列
2.消息的消費(fèi)者(consumer) 監(jiān)聽(tīng) 消息隊(duì)列,如果隊(duì)列中有消息,就消費(fèi)掉,消息被拿走后,自動(dòng)從隊(duì)列中刪除(隱患 消息可能沒(méi)有被消費(fèi)者正確處理,已經(jīng)從隊(duì)列中消失了,造成消息的丟失术吗,這里可以設(shè)置成手動(dòng)的ack,但如果設(shè)置成手動(dòng)ack,處理完后要及時(shí)發(fā)送ack消息給隊(duì)列帆精,否則會(huì)造成內(nèi)存溢出)较屿。
二.work工作模式(資源的競(jìng)爭(zhēng))
img
1.消息產(chǎn)生者將消息放入隊(duì)列消費(fèi)者可以有多個(gè),消費(fèi)者1,消費(fèi)者2同時(shí)監(jiān)聽(tīng)同一個(gè)隊(duì)列,消息被消費(fèi)隧魄。C1 C2共同爭(zhēng)搶當(dāng)前的消息隊(duì)列內(nèi)容,誰(shuí)先拿到誰(shuí)負(fù)責(zé)消費(fèi)消息(隱患:高并發(fā)情況下,默認(rèn)會(huì)產(chǎn)生某一個(gè)消息被多個(gè)消費(fèi)者共同使用,可以設(shè)置一個(gè)開(kāi)關(guān)(syncronize) 保證一條消息只能被一個(gè)消費(fèi)者使用)。
三.publish/subscribe發(fā)布訂閱(共享資源)
img
1隘蝎、每個(gè)消費(fèi)者監(jiān)聽(tīng)自己的隊(duì)列购啄;
2、生產(chǎn)者將消息發(fā)給broker末贾,由交換機(jī)將消息轉(zhuǎn)發(fā)到綁定此交換機(jī)的每個(gè)隊(duì)列闸溃,每個(gè)綁定交換機(jī)的隊(duì)列都將接收到消息整吆。
四.routing路由模式
img
1.消息生產(chǎn)者將消息發(fā)送給交換機(jī)按照路由判斷,路由是字符串(info) 當(dāng)前產(chǎn)生的消息攜帶路由字符(對(duì)象的方法),交換機(jī)根據(jù)路由的key,只能匹配上路由key對(duì)應(yīng)的消息隊(duì)列,對(duì)應(yīng)的消費(fèi)者才能消費(fèi)消息;
2.根據(jù)業(yè)務(wù)功能定義路由字符串
3.從系統(tǒng)的代碼邏輯中獲取對(duì)應(yīng)的功能字符串,將消息任務(wù)扔到對(duì)應(yīng)的隊(duì)列中拱撵。
4.業(yè)務(wù)場(chǎng)景:error 通知;EXCEPTION;錯(cuò)誤通知的功能;傳統(tǒng)意義的錯(cuò)誤通知;客戶通知;利用key路由,可以將程序中的錯(cuò)誤封裝成消息傳入到消息隊(duì)列中,開(kāi)發(fā)者可以自定義消費(fèi)者,實(shí)時(shí)接收錯(cuò)誤;
五.topic 主題模式(路由模式的一種)
img
1.星號(hào)井號(hào)代表通配符
2.星號(hào)代表多個(gè)單詞,井號(hào)代表一個(gè)單詞
3.路由功能添加模糊匹配
4.消息產(chǎn)生者產(chǎn)生消息,把消息交給交換機(jī)
5.交換機(jī)根據(jù)key的規(guī)則模糊匹配到對(duì)應(yīng)的隊(duì)列,由隊(duì)列的監(jiān)聽(tīng)消費(fèi)者接收消息消費(fèi)
(在我的理解看來(lái)就是routing查詢的一種模糊匹配,就類似sql的模糊查詢方式)
如何保證RabbitMQ消息的順序性表蝙?
拆分多個(gè) queue拴测,每個(gè) queue 一個(gè) consumer,就是多一些 queue 而已府蛇,確實(shí)是麻煩點(diǎn)集索;或者就一個(gè) queue 但是對(duì)應(yīng)一個(gè) consumer,然后這個(gè) consumer 內(nèi)部用內(nèi)存隊(duì)列做排隊(duì)汇跨,然后分發(fā)給底層不同的 worker 來(lái)處理务荆。
消息如何分發(fā)?
若該隊(duì)列至少有一個(gè)消費(fèi)者訂閱穷遂,消息將以循環(huán)(round-robin)的方式發(fā)送給消費(fèi)者函匕。每條消息只會(huì)分發(fā)給一個(gè)訂閱的消費(fèi)者(前提是消費(fèi)者能夠正常處理消息并進(jìn)行確認(rèn))。通過(guò)路由可實(shí)現(xiàn)多消費(fèi)的功能
消息怎么路由蚪黑?
消息提供方->路由->一至多個(gè)隊(duì)列消息發(fā)布到交換器時(shí)盅惜,消息將擁有一個(gè)路由鍵(routing key),在消息創(chuàng)建時(shí)設(shè)定忌穿。通過(guò)隊(duì)列路由鍵抒寂,可以把隊(duì)列綁定到交換器上。消息到達(dá)交換器后掠剑,RabbitMQ 會(huì)將消息的路由鍵與隊(duì)列的路由鍵進(jìn)行匹配(針對(duì)不同的交換器有不同的路由規(guī)則)屈芜;
常用的交換器主要分為一下三種:
fanout:如果交換器收到消息,將會(huì)廣播到所有綁定的隊(duì)列上
direct:如果路由鍵完全匹配朴译,消息就被投遞到相應(yīng)的隊(duì)列
topic:可以使來(lái)自不同源頭的消息能夠到達(dá)同一個(gè)隊(duì)列井佑。 使用 topic 交換器時(shí),可以使用通配符
消息基于什么傳輸动分?
由于 TCP 連接的創(chuàng)建和銷毀開(kāi)銷較大毅糟,且并發(fā)數(shù)受系統(tǒng)資源限制,會(huì)造成性能瓶頸澜公。RabbitMQ 使用信道的方式來(lái)傳輸數(shù)據(jù)姆另。信道是建立在真實(shí)的 TCP 連接內(nèi)的虛擬連接喇肋,且每條 TCP 連接上的信道數(shù)量沒(méi)有限制。
如何保證消息不被重復(fù)消費(fèi)迹辐?或者說(shuō)蝶防,如何保證消息消費(fèi)時(shí)的冪等性?
先說(shuō)為什么會(huì)重復(fù)消費(fèi):正常情況下明吩,消費(fèi)者在消費(fèi)消息的時(shí)候间学,消費(fèi)完畢后,會(huì)發(fā)送一個(gè)確認(rèn)消息給消息隊(duì)列印荔,消息隊(duì)列就知道該消息被消費(fèi)了低葫,就會(huì)將該消息從消息隊(duì)列中刪除;
但是因?yàn)榫W(wǎng)絡(luò)傳輸?shù)鹊裙收先月桑_認(rèn)信息沒(méi)有傳送到消息隊(duì)列嘿悬,導(dǎo)致消息隊(duì)列不知道自己已經(jīng)消費(fèi)過(guò)該消息了,再次將消息分發(fā)給其他的消費(fèi)者水泉。
針對(duì)以上問(wèn)題善涨,一個(gè)解決思路是:保證消息的唯一性,就算是多次傳輸草则,不要讓消息的多次消費(fèi)帶來(lái)影響钢拧;保證消息等冪性;
比如:在寫入消息隊(duì)列的數(shù)據(jù)做唯一標(biāo)示炕横,消費(fèi)消息時(shí)源内,根據(jù)唯一標(biāo)識(shí)判斷是否消費(fèi)過(guò);
假設(shè)你有個(gè)系統(tǒng)看锉,消費(fèi)一條消息就往數(shù)據(jù)庫(kù)里插入一條數(shù)據(jù)姿锭,要是你一個(gè)消息重復(fù)兩次,你不就插入了兩條伯铣,這數(shù)據(jù)不就錯(cuò)了呻此?但是你要是消費(fèi)到第二次的時(shí)候,自己判斷一下是否已經(jīng)消費(fèi)過(guò)了腔寡,若是就直接扔了焚鲜,這樣不就保留了一條數(shù)據(jù),從而保證了數(shù)據(jù)的正確性放前。
如何確保消息正確地發(fā)送至 RabbitMQ忿磅? 如何確保消息接收方消費(fèi)了消息?
發(fā)送方確認(rèn)模式
將信道設(shè)置成 confirm 模式(發(fā)送方確認(rèn)模式)凭语,則所有在信道上發(fā)布的消息都會(huì)被指派一個(gè)唯一的 ID葱她。
一旦消息被投遞到目的隊(duì)列后,或者消息被寫入磁盤后(可持久化的消息)似扔,信道會(huì)發(fā)送一個(gè)確認(rèn)給生產(chǎn)者(包含消息唯一 ID)吨些。
如果 RabbitMQ 發(fā)生內(nèi)部錯(cuò)誤從而導(dǎo)致消息丟失搓谆,會(huì)發(fā)送一條 nack(notacknowledged,未確認(rèn))消息豪墅。
發(fā)送方確認(rèn)模式是異步的泉手,生產(chǎn)者應(yīng)用程序在等待確認(rèn)的同時(shí),可以繼續(xù)發(fā)送消息偶器。當(dāng)確認(rèn)消息到達(dá)生產(chǎn)者應(yīng)用程序斩萌,生產(chǎn)者應(yīng)用程序的回調(diào)方法就會(huì)被觸發(fā)來(lái)處理確認(rèn)消息。
接收方確認(rèn)機(jī)制
消費(fèi)者接收每一條消息后都必須進(jìn)行確認(rèn)(消息接收和消息確認(rèn)是兩個(gè)不同操作)屏轰。只有消費(fèi)者確認(rèn)了消息颊郎,RabbitMQ 才能安全地把消息從隊(duì)列中刪除。
這里并沒(méi)有用到超時(shí)機(jī)制亭枷,RabbitMQ 僅通過(guò) Consumer 的連接中斷來(lái)確認(rèn)是否需要重新發(fā)送消息袭艺。也就是說(shuō)搀崭,只要連接不中斷叨粘,RabbitMQ 給了 Consumer 足夠長(zhǎng)的時(shí)間來(lái)處理消息。保證數(shù)據(jù)的最終一致性瘤睹;
下面羅列幾種特殊情況
如果消費(fèi)者接收到消息升敲,在確認(rèn)之前斷開(kāi)了連接或取消訂閱,RabbitMQ 會(huì)認(rèn)為消息沒(méi)有被分發(fā)轰传,然后重新分發(fā)給下一個(gè)訂閱的消費(fèi)者驴党。(可能存在消息重復(fù)消費(fèi)的隱患,需要去重)
如果消費(fèi)者接收到消息卻沒(méi)有確認(rèn)消息获茬,連接也未斷開(kāi)港庄,則 RabbitMQ 認(rèn)為該消費(fèi)者繁忙,將不會(huì)給該消費(fèi)者分發(fā)更多的消息恕曲。
如何保證RabbitMQ消息的可靠傳輸鹏氧?
消息不可靠的情況可能是消息丟失,劫持等原因佩谣;
丟失又分為:生產(chǎn)者丟失消息把还、消息列表丟失消息、消費(fèi)者丟失消息茸俭;
生產(chǎn)者丟失消息:從生產(chǎn)者弄丟數(shù)據(jù)這個(gè)角度來(lái)看吊履,RabbitMQ提供transaction和confirm模式來(lái)確保生產(chǎn)者不丟消息;
transaction機(jī)制就是說(shuō):發(fā)送消息前调鬓,開(kāi)啟事務(wù)(channel.txSelect()),然后發(fā)送消息艇炎,如果發(fā)送過(guò)程中出現(xiàn)什么異常,事務(wù)就會(huì)回滾(channel.txRollback()),如果發(fā)送成功則提交事務(wù)(channel.txCommit())腾窝。然而缀踪,這種方式有個(gè)缺點(diǎn):吞吐量下降腺晾;
confirm模式用的居多:一旦channel進(jìn)入confirm模式,所有在該信道上發(fā)布的消息都將會(huì)被指派一個(gè)唯一的ID(從1開(kāi)始)辜贵,一旦消息被投遞到所有匹配的隊(duì)列之后悯蝉;
rabbitMQ就會(huì)發(fā)送一個(gè)ACK給生產(chǎn)者(包含消息的唯一ID),這就使得生產(chǎn)者知道消息已經(jīng)正確到達(dá)目的隊(duì)列了托慨;
如果rabbitMQ沒(méi)能處理該消息鼻由,則會(huì)發(fā)送一個(gè)Nack消息給你,你可以進(jìn)行重試操作厚棵。
消息隊(duì)列丟數(shù)據(jù):消息持久化蕉世。
處理消息隊(duì)列丟數(shù)據(jù)的情況,一般是開(kāi)啟持久化磁盤的配置婆硬。
這個(gè)持久化配置可以和confirm機(jī)制配合使用狠轻,你可以在消息持久化磁盤后,再給生產(chǎn)者發(fā)送一個(gè)Ack信號(hào)彬犯。
這樣向楼,如果消息持久化磁盤之前,rabbitMQ陣亡了谐区,那么生產(chǎn)者收不到Ack信號(hào)湖蜕,生產(chǎn)者會(huì)自動(dòng)重發(fā)。
那么如何持久化呢宋列?
這里順便說(shuō)一下吧昭抒,其實(shí)也很容易,就下面兩步
將queue的持久化標(biāo)識(shí)durable設(shè)置為true,則代表是一個(gè)持久的隊(duì)列
發(fā)送消息的時(shí)候?qū)eliveryMode=2
這樣設(shè)置以后炼杖,即使rabbitMQ掛了灭返,重啟后也能恢復(fù)數(shù)據(jù)
消費(fèi)者丟失消息:消費(fèi)者丟數(shù)據(jù)一般是因?yàn)椴捎昧俗詣?dòng)確認(rèn)消息模式,改為手動(dòng)確認(rèn)消息即可坤邪!
消費(fèi)者在收到消息之后熙含,處理消息之前,會(huì)自動(dòng)回復(fù)RabbitMQ已收到消息罩扇;
如果這時(shí)處理消息失敗婆芦,就會(huì)丟失該消息;
解決方案:處理消息成功后喂饥,手動(dòng)回復(fù)確認(rèn)消息消约。
為什么不應(yīng)該對(duì)所有的 message 都使用持久化機(jī)制?
首先员帮,必然導(dǎo)致性能的下降或粮,因?yàn)閷懘疟P比寫 RAM 慢的多,message 的吞吐量可能有 10 倍的差距捞高。
其次氯材,message 的持久化機(jī)制用在 RabbitMQ 的內(nèi)置 cluster 方案時(shí)會(huì)出現(xiàn)“坑爹”問(wèn)題渣锦。矛盾點(diǎn)在于,若 message 設(shè)置了 persistent 屬性氢哮,但 queue 未設(shè)置 durable 屬性袋毙,那么當(dāng)該 queue 的 owner node 出現(xiàn)異常后,在未重建該 queue 前冗尤,發(fā)往該 queue 的 message 將被 blackholed 听盖;若 message 設(shè)置了 persistent 屬性,同時(shí) queue 也設(shè)置了 durable 屬性裂七,那么當(dāng) queue 的 owner node 異常且無(wú)法重啟的情況下皆看,則該 queue 無(wú)法在其他 node 上重建,只能等待其 owner node 重啟后背零,才能恢復(fù)該 queue 的使用腰吟,而在這段時(shí)間內(nèi)發(fā)送給該 queue 的 message 將被 blackholed 。
所以徙瓶,是否要對(duì) message 進(jìn)行持久化毛雇,需要綜合考慮性能需要,以及可能遇到的問(wèn)題倍啥。若想達(dá)到 100,000 條/秒以上的消息吞吐量(單 RabbitMQ 服務(wù)器)禾乘,則要么使用其他的方式來(lái)確保 message 的可靠 delivery ,要么使用非乘渎疲快速的存儲(chǔ)系統(tǒng)以支持全持久化(例如使用 SSD)。另外一種處理原則是:僅對(duì)關(guān)鍵消息作持久化處理(根據(jù)業(yè)務(wù)重要程度)蒲稳,且應(yīng)該保證關(guān)鍵消息的量不會(huì)導(dǎo)致性能瓶頸氮趋。
如何保證高可用的?RabbitMQ 的集群
RabbitMQ 是比較有代表性的江耀,因?yàn)槭腔谥鲝模ǚ欠植际剑┳龈呖捎眯缘氖P玻覀兙鸵?RabbitMQ 為例子講解第一種 MQ 的高可用性怎么實(shí)現(xiàn)。RabbitMQ 有三種模式:?jiǎn)螜C(jī)模式祥国、普通集群模式昵观、鏡像集群模式。
單機(jī)模式舌稀,就是 Demo 級(jí)別的啊犬,一般就是你本地啟動(dòng)了玩玩兒的?,沒(méi)人生產(chǎn)用單機(jī)模式
普通集群模式壁查,意思就是在多臺(tái)機(jī)器上啟動(dòng)多個(gè) RabbitMQ 實(shí)例觉至,每個(gè)機(jī)器啟動(dòng)一個(gè)。你創(chuàng)建的 queue睡腿,只會(huì)放在一個(gè) RabbitMQ 實(shí)例上语御,但是每個(gè)實(shí)例都同步 queue 的元數(shù)據(jù)(元數(shù)據(jù)可以認(rèn)為是 queue 的一些配置信息峻贮,通過(guò)元數(shù)據(jù),可以找到 queue 所在實(shí)例)应闯。你消費(fèi)的時(shí)候纤控,實(shí)際上如果連接到了另外一個(gè)實(shí)例,那么那個(gè)實(shí)例會(huì)從 queue 所在實(shí)例上拉取數(shù)據(jù)過(guò)來(lái)碉纺。這方案主要是提高吞吐量的嚼黔,就是說(shuō)讓集群中多個(gè)節(jié)點(diǎn)來(lái)服務(wù)某個(gè) queue 的讀寫操作。
鏡像集群模式:這種模式惜辑,才是所謂的 RabbitMQ 的高可用模式唬涧。跟普通集群模式不一樣的是,在鏡像集群模式下盛撑,你創(chuàng)建的 queue碎节,無(wú)論元數(shù)據(jù)還是 queue 里的消息都會(huì)存在于多個(gè)實(shí)例上,就是說(shuō)抵卫,每個(gè) RabbitMQ 節(jié)點(diǎn)都有這個(gè) queue 的一個(gè)完整鏡像狮荔,包含 queue 的全部數(shù)據(jù)的意思。然后每次你寫消息到 queue 的時(shí)候介粘,都會(huì)自動(dòng)把消息同步到多個(gè)實(shí)例的 queue 上殖氏。RabbitMQ 有很好的管理控制臺(tái),就是在后臺(tái)新增一個(gè)策略姻采,這個(gè)策略是鏡像集群模式的策略雅采,指定的時(shí)候是可以要求數(shù)據(jù)同步到所有節(jié)點(diǎn)的,也可以要求同步到指定數(shù)量的節(jié)點(diǎn)慨亲,再次創(chuàng)建 queue 的時(shí)候婚瓜,應(yīng)用這個(gè)策略,就會(huì)自動(dòng)將數(shù)據(jù)同步到其他的節(jié)點(diǎn)上去了刑棵。這樣的話巴刻,好處在于,你任何一個(gè)機(jī)器宕機(jī)了蛉签,沒(méi)事兒胡陪,其它機(jī)器(節(jié)點(diǎn))還包含了這個(gè) queue 的完整數(shù)據(jù),別的 consumer 都可以到其它節(jié)點(diǎn)上去消費(fèi)數(shù)據(jù)碍舍。壞處在于柠座,第一,這個(gè)性能開(kāi)銷也太大了吧乒验,消息需要同步到所有機(jī)器上愚隧,導(dǎo)致網(wǎng)絡(luò)帶寬壓力和消耗很重!RabbitMQ 一個(gè) queue 的數(shù)據(jù)都是放在一個(gè)節(jié)點(diǎn)里的,鏡像集群下狂塘,也是每個(gè)節(jié)點(diǎn)都放這個(gè) queue 的完整數(shù)據(jù)录煤。
如何解決消息隊(duì)列的延時(shí)以及過(guò)期失效問(wèn)題?消息隊(duì)列滿了以后該怎么處理荞胡?有幾百萬(wàn)消息持續(xù)積壓幾小時(shí)妈踊,說(shuō)說(shuō)怎么解決?
消息積壓處理辦法:臨時(shí)緊急擴(kuò)容:
先修復(fù) consumer 的問(wèn)題泪漂,確保其恢復(fù)消費(fèi)速度廊营,然后將現(xiàn)有 cnosumer 都停掉。
新建一個(gè) topic萝勤,partition 是原來(lái)的 10 倍露筒,臨時(shí)建立好原先 10 倍的 queue 數(shù)量。
然后寫一個(gè)臨時(shí)的分發(fā)數(shù)據(jù)的 consumer 程序敌卓,這個(gè)程序部署上去消費(fèi)積壓的數(shù)據(jù)慎式,消費(fèi)之后不做耗時(shí)的處理,直接均勻輪詢寫入臨時(shí)建立好的 10 倍數(shù)量的 queue趟径。
接著臨時(shí)征用 10 倍的機(jī)器來(lái)部署 consumer瘪吏,每一批 consumer 消費(fèi)一個(gè)臨時(shí) queue 的數(shù)據(jù)。這種做法相當(dāng)于是臨時(shí)將 queue 資源和 consumer 資源擴(kuò)大 10 倍蜗巧,以正常的 10 倍速度來(lái)消費(fèi)數(shù)據(jù)掌眠。
等快速消費(fèi)完積壓數(shù)據(jù)之后,得恢復(fù)原先部署的架構(gòu)幕屹,重新用原先的 consumer 機(jī)器來(lái)消費(fèi)消息蓝丙。
MQ中消息失效:假設(shè)你用的是 RabbitMQ,RabbtiMQ 是可以設(shè)置過(guò)期時(shí)間的香嗓,也就是 TTL迅腔。如果消息在 queue 中積壓超過(guò)一定的時(shí)間就會(huì)被 RabbitMQ 給清理掉,這個(gè)數(shù)據(jù)就沒(méi)了靠娱。那這就是第二個(gè)坑了。這就不是說(shuō)數(shù)據(jù)會(huì)大量積壓在 mq 里掠兄,而是大量的數(shù)據(jù)會(huì)直接搞丟像云。我們可以采取一個(gè)方案,就是批量重導(dǎo)蚂夕,這個(gè)我們之前線上也有類似的場(chǎng)景干過(guò)迅诬。就是大量積壓的時(shí)候,我們當(dāng)時(shí)就直接丟棄數(shù)據(jù)了婿牍,然后等過(guò)了高峰期以后侈贷,比如大家一起喝咖啡熬夜到晚上12點(diǎn)以后,用戶都睡覺(jué)了等脂。這個(gè)時(shí)候我們就開(kāi)始寫程序俏蛮,將丟失的那批數(shù)據(jù)撑蚌,寫個(gè)臨時(shí)程序,一點(diǎn)一點(diǎn)的查出來(lái)搏屑,然后重新灌入 mq 里面去争涌,把白天丟的數(shù)據(jù)給他補(bǔ)回來(lái)。也只能是這樣了辣恋。假設(shè) 1 萬(wàn)個(gè)訂單積壓在 mq 里面亮垫,沒(méi)有處理,其中 1000 個(gè)訂單都丟了伟骨,你只能手動(dòng)寫程序把那 1000 個(gè)訂單給查出來(lái)饮潦,手動(dòng)發(fā)到 mq 里去再補(bǔ)一次。
mq消息隊(duì)列塊滿了:如果消息積壓在 mq 里携狭,你很長(zhǎng)時(shí)間都沒(méi)有處理掉继蜡,此時(shí)導(dǎo)致 mq 都快寫滿了,咋辦暑中?這個(gè)還有別的辦法嗎壹瘟?沒(méi)有,誰(shuí)讓你第一個(gè)方案執(zhí)行的太慢了鳄逾,你臨時(shí)寫程序稻轨,接入數(shù)據(jù)來(lái)消費(fèi),消費(fèi)一個(gè)丟棄一個(gè)雕凹,都不要了殴俱,快速消費(fèi)掉所有的消息。然后走第二個(gè)方案枚抵,到了晚上再補(bǔ)數(shù)據(jù)吧线欲。
設(shè)計(jì)MQ思路
比如說(shuō)這個(gè)消息隊(duì)列系統(tǒng),我們從以下幾個(gè)角度來(lái)考慮一下:
首先這個(gè) mq 得支持可伸縮性吧汽摹,就是需要的時(shí)候快速擴(kuò)容李丰,就可以增加吞吐量和容量,那怎么搞逼泣?設(shè)計(jì)個(gè)分布式的系統(tǒng)唄趴泌,參照一下 kafka 的設(shè)計(jì)理念,broker -> topic -> partition拉庶,每個(gè) partition 放一個(gè)機(jī)器嗜憔,就存一部分?jǐn)?shù)據(jù)。如果現(xiàn)在資源不夠了氏仗,簡(jiǎn)單啊吉捶,給 topic 增加 partition,然后做數(shù)據(jù)遷移,增加機(jī)器呐舔,不就可以存放更多數(shù)據(jù)币励,提供更高的吞吐量了?
其次你得考慮一下這個(gè) mq 的數(shù)據(jù)要不要落地磁盤吧滋早?那肯定要了榄审,落磁盤才能保證別進(jìn)程掛了數(shù)據(jù)就丟了。那落磁盤的時(shí)候怎么落案唆铩搁进?順序?qū)懀@樣就沒(méi)有磁盤隨機(jī)讀寫的尋址開(kāi)銷昔头,磁盤順序讀寫的性能是很高的饼问,這就是 kafka 的思路。
其次你考慮一下你的 mq 的可用性敖腋莱革?這個(gè)事兒,具體參考之前可用性那個(gè)環(huán)節(jié)講解的 kafka 的高可用保障機(jī)制讹开。多副本 -> leader & follower -> broker 掛了重新選舉 leader 即可對(duì)外服務(wù)盅视。
能不能支持?jǐn)?shù)據(jù) 0 丟失啊旦万?可以的闹击,參考我們之前說(shuō)的那個(gè) kafka 數(shù)據(jù)零丟失方案。
作者:老pao說(shuō)Java
鏈接:http://www.reibang.com/p/e45e16baa9ce
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有成艘。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)赏半,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。