首先拋出問題
為什么使用消息隊列梆健料身?消息隊列有什么優(yōu)點和缺點啊斑举?kafka赐纱、activemq脊奋、rabbitmq、rocketmq都有什么優(yōu)點和缺點扒?
如何保證消息隊列的高可用八饴瘛淫痰?
如何保證消息不被重復(fù)消費啊(如何進行消息隊列的冪等性問題)整份?
如何保證消息的可靠性傳輸(如何處理消息丟失的問題)待错?
如何保證消息的順序性?
如何解決消息隊列的延時以及過期失效問題烈评?消息隊列滿了以后該怎么處理火俄?有幾百萬消息持續(xù)積壓幾小時,說說怎么解決讲冠?
如果讓你寫一個消息隊列瓜客,該如何進行架構(gòu)設(shè)計啊竿开?說一下你的思路.
解決問題
為什么使用消息隊列捌滓恰?
異步, 解耦, 削峰.
- 異步. A系統(tǒng)需要發(fā)送個請求給B系統(tǒng)處理,由于B系統(tǒng)需要查詢更新數(shù)據(jù)庫花費時間較長,以至于A系統(tǒng)要等待B系統(tǒng)處理完畢后再發(fā)送下個請求,造成A系統(tǒng)資源浪費.使用消息隊列后,A系統(tǒng)生產(chǎn)完消息后直接丟進消息消息隊列,就完成一次請求,繼續(xù)處理下個請求.
- 解耦. A系統(tǒng)發(fā)送個數(shù)據(jù)到BCD三個系統(tǒng)否彩,接口調(diào)用發(fā)送疯攒,那如果E系統(tǒng)也要這個數(shù)據(jù)呢?那如果C系統(tǒng)現(xiàn)在不需要了呢列荔?現(xiàn)在A系統(tǒng)又要發(fā)送第二種數(shù)據(jù)了呢敬尺?A系統(tǒng)負(fù)責(zé)人瀕臨崩潰中枚尼。。砂吞。再來點更加崩潰的事兒署恍,A系統(tǒng)要時時刻刻考慮BCDE四個系統(tǒng)如果掛了咋辦?我要不要重發(fā)呜舒?我要不要把消息存起來锭汛?使用消息隊列就能解決這個問題,A系統(tǒng)只負(fù)責(zé)生產(chǎn)數(shù)據(jù),不需要考慮消息被哪個系統(tǒng)來消費.
- 削峰. A系統(tǒng)調(diào)用B系統(tǒng)處理數(shù)據(jù),每天0點到11點,A系統(tǒng)風(fēng)平浪靜袭蝗,每秒并發(fā)請求數(shù)量就100個唤殴。結(jié)果每次一到11點~1點,每秒并發(fā)請求數(shù)量突然會暴增到1萬條到腥。但是B系統(tǒng)最大的處理能力就只能是每秒鐘處理1000個請求啊朵逝。。乡范。尷尬了配名,系統(tǒng)會崩掉。晋辆。渠脉。引入消息隊列,把請求數(shù)據(jù)先存入消息中間件系統(tǒng)中,消費系統(tǒng)慢慢拉取消費.
消息隊列有什么優(yōu)點和缺點啊瓶佳?
- 優(yōu)點就是異步,解耦 ,削峰.
- 缺點:
系統(tǒng)可用性降低:系統(tǒng)引入的外部依賴越多芋膘,越容易掛掉,本來你就是A系統(tǒng)調(diào)用BCD三個系統(tǒng)的接口就好了霸饲,人ABCD四個系統(tǒng)好好的为朋,沒啥問題,你偏加個MQ進來厚脉,萬一MQ掛了咋整习寸?MQ掛了,整套系統(tǒng)崩潰了傻工,你不就完了么霞溪。
系統(tǒng)復(fù)雜性提高:硬生生加個MQ進來,你怎么保證消息沒有重復(fù)消費中捆?怎么處理消息丟失的情況威鹿?怎么保證消息傳遞的順序性?頭大頭大轨香,問題一大堆忽你,痛苦不已
一致性問題:A系統(tǒng)處理完了直接返回成功了,人都以為你這個請求就成功了臂容;但是問題是科雳,要是BCD三個系統(tǒng)那里根蟹,BD兩個系統(tǒng)寫庫成功了,結(jié)果C系統(tǒng)寫庫失敗了糟秘,咋整简逮?你這數(shù)據(jù)就不一致了。
activemq尿赚、rabbitmq散庶、rocketmq都有什么優(yōu)點和缺點啊凌净?
不啰嗦,上個表格.
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
單機吞吐量 | 萬級悲龟,吞吐量比RocketMQ和Kafka要低了一個數(shù)量級 | 萬級,吞吐量比RocketMQ和Kafka要低了一個數(shù)量級 | 10萬級冰寻,RocketMQ也是可以支撐高吞吐的一種MQ | 10萬級別须教,這是kafka最大的優(yōu)點,就是吞吐量高斩芭。一般配合大數(shù)據(jù)類的系統(tǒng)來進行實時數(shù)據(jù)計算轻腺、日志采集等場景 |
topic數(shù)量對吞吐量的影響 | topic可以達到幾百,幾千個的級別划乖,吞吐量會有較小幅度的下降,這是RocketMQ的一大優(yōu)勢贬养,在同等機器下,可以支撐大量的topic | topic從幾十個到幾百個的時候琴庵,吞吐量會大幅度下降,所以在同等機器下误算,kafka盡量保證topic數(shù)量不要過多。如果要支撐大規(guī)模topic细卧,需要增加更多的機器資源 | ||
時效性 | ms級 | 微秒級, 這是rabbitmq的一大特點,延遲是最低的 | ms級 | 延遲在ms級以內(nèi) |
可用性 | 高尉桩,基于主從架構(gòu)實現(xiàn)高可用性 | 高筒占,基于主從架構(gòu)實現(xiàn)高可用性 | 非常高贪庙,分布式架構(gòu) | 非常高,kafka是分布式的翰苫,一個數(shù)據(jù)多個副本止邮,少數(shù)機器宕機,不會丟失數(shù)據(jù)奏窑,不會導(dǎo)致不可用 |
消息可靠性 | 有較低的概率丟失數(shù)據(jù) | 一般情況不會丟失消息 | 經(jīng)過參數(shù)優(yōu)化配置导披,消息可以做到0丟失 | 經(jīng)過參數(shù)優(yōu)化配置,消息可以做到0丟失 |
功能支持 | MQ領(lǐng)域的功能極其完備 | 基于erlang開發(fā)埃唯,所以并發(fā)能力很強撩匕,性能極其好,延時很低 | MQ功能較為完善墨叛,還是分布式的止毕,擴展性好 | 功能較為簡單模蜡,主要支持簡單的MQ功能,在大數(shù)據(jù)領(lǐng)域的實時計算以及日志采集被大規(guī)模使用扁凛,是事實上的標(biāo)準(zhǔn) |
優(yōu)劣勢總結(jié) | 非常成熟忍疾,功能強大,在業(yè)內(nèi)大量的公司以及項目中都有應(yīng)用.偶爾會有較低概率丟失消息,而且現(xiàn)在社區(qū)以及國內(nèi)應(yīng)用都越來越少谨朝,官方社區(qū)現(xiàn)在對ActiveMQ 5.x維護越來越少卤妒,幾個月才發(fā)布一個版本,而且確實主要是基于解耦和異步來用的,較少在大規(guī)模吞吐的場景中使用. | erlang語言開發(fā)字币,性能極其好则披,延時很低;吞吐量到萬級纬朝,MQ功能比較完備,而且開源提供的管理界面非常棒收叶,用起來很好用.社區(qū)相對比較活躍,幾乎每個月都發(fā)布幾個版本,在國內(nèi)一些互聯(lián)網(wǎng)公司近幾年用rabbitmq也比較多一些.但是問題也是顯而易見的共苛,RabbitMQ確實吞吐量會低一些判没,這是因為他做的實現(xiàn)機制比較重。而且erlang開發(fā)隅茎,國內(nèi)有幾個公司有實力做erlang源碼級別的研究和定制澄峰?如果說你沒這個實力的話,確實偶爾會有一些問題辟犀,你很難去看懂源碼俏竞,你公司對這個東西的掌控很弱,基本職能依賴于開源社區(qū)的快速維護和修復(fù)bug堂竟。而且rabbitmq集群動態(tài)擴展會很麻煩魂毁,不過這個我覺得還好。其實主要是erlang語言本身帶來的問題出嘹。很難讀源碼席楚,很難定制和控制 | 接口簡單易用,而且畢竟在阿里大規(guī)模應(yīng)用過税稼,有阿里品牌保障,日處理消息上百億之多烦秩,可以做到大規(guī)模吞吐,性能也非常好郎仆,分布式擴展也很方便只祠,社區(qū)維護還可以,可靠性和可用性都是ok的扰肌,還可以支撐大規(guī)模的topic數(shù)量抛寝,支持復(fù)雜MQ業(yè)務(wù)場景.而且一個很大的優(yōu)勢在于,阿里出品都是java系的,我們可以自己閱讀源碼盗舰,定制自己公司的MQ猴凹,可以掌控社區(qū)活躍度相對較為一般,不過也還可以岭皂,文檔相對來說簡單一些郊霎,然后接口這塊不是按照標(biāo)準(zhǔn)JMS規(guī)范走的有些系統(tǒng)要遷移需要修改大量代碼.還有就是阿里出臺的技術(shù),你得做好這個技術(shù)萬一被拋棄爷绘,社區(qū)黃掉的風(fēng)險书劝,那如果你們公司有技術(shù)實力我覺得用RocketMQ挺好的 | kafka的特點其實很明顯,就是僅僅提供較少的核心功能土至,但是提供超高的吞吐量购对,ms級的延遲,極高的可用性以及可靠性陶因,而且分布式可以任意擴展同時kafka最好是支撐較少的topic數(shù)量即可骡苞,保證其超高吞吐量,而且kafka唯一的一點劣勢是有可能消息重復(fù)消費,那么對數(shù)據(jù)準(zhǔn)確性會造成極其輕微的影響楷扬,在大數(shù)據(jù)領(lǐng)域中以及日志采集中解幽,這點輕微影響可以忽略,這個特性天然適合大數(shù)據(jù)實時計算以及日志收集 |
如何保證消息隊列的高可用啊烘苹?
RabbitMQ是比較有代表性的躲株,因為是基于主從做高可用性的,我們就以他為例子講解第一種MQ的高可用性怎么實現(xiàn)镣衡。
rabbitmq有三種模式:單機模式霜定,普通集群模式,鏡像集群模式
單機模式
就是demo級別的廊鸥,一般就是你本地啟動了玩玩兒的望浩,沒人生產(chǎn)用單機模式.普通集群模式
意思就是在多臺機器上啟動多個rabbitmq實例,每個機器啟動一個磨德。但是你創(chuàng)建的queue,只會放在一個rabbtimq實例上助被,但是每個實例都同步queue的元數(shù)據(jù)剖张。完了你消費的時候切诀,實際上如果連接到了另外一個實例揩环,那么那個實例會從queue所在實例上拉取數(shù)據(jù)過來。這種方式確實很麻煩幅虑,也不怎么好丰滑,沒做到所謂的分布式,就是個普通集群。因為這導(dǎo)致你要么消費者每次隨機連接一個實例然后拉取數(shù)據(jù)褒墨,要么固定連接那個queue所在實例消費數(shù)據(jù)炫刷,前者有數(shù)據(jù)拉取的開銷,后者導(dǎo)致單實例性能瓶頸郁妈。而且如果那個放queue的實例宕機了浑玛,會導(dǎo)致接下來其他實例就無法從那個實例拉取,如果你開啟了消息持久化噩咪,讓rabbitmq落地存儲消息的話顾彰,消息不一定會丟,得等這個實例恢復(fù)了胃碾,然后才可以繼續(xù)從這個queue拉取數(shù)據(jù)涨享。所以這個事兒就比較尷尬了,這就沒有什么所謂的高可用性可言了仆百,這方案主要是提高吞吐量的厕隧,就是說讓集群中多個節(jié)點來服務(wù)某個queue的讀寫操作。鏡像集群模式
這種模式俄周,才是所謂的rabbitmq的高可用模式吁讨,跟普通集群模式不一樣的是,你創(chuàng)建的queue峦朗,無論元數(shù)據(jù)還是queue里的消息都會存在于多個實例上挡爵,然后每次你寫消息到queue的時候,都會自動把消息到多個實例的queue里進行消息同步甚垦。
這樣的話茶鹃,好處在于,你任何一個機器宕機了艰亮,沒事兒闭翩,別的機器都可以用。壞處在于迄埃,第一疗韵,這個性能開銷也太大了吧,消息同步所有機器侄非,導(dǎo)致網(wǎng)絡(luò)帶寬壓力和消耗很重蕉汪!第二,這么玩兒逞怨,就沒有擴展性可言了者疤,如果某個queue負(fù)載很重,你加機器叠赦,新增的機器也包含了這個queue的所有數(shù)據(jù)驹马,并沒有辦法線性擴展你的queue.那么怎么開啟這個鏡像集群模式呢?我這里簡單說一下,避免面試人家問你你不知道糯累,其實很簡單rabbitmq有很好的管理控制臺算利,就是在后臺新增一個策略,這個策略是鏡像集群模式的策略泳姐,指定的時候可以要求數(shù)據(jù)同步到所有節(jié)點的效拭,也可以要求就同步到指定數(shù)量的節(jié)點,然后你再次創(chuàng)建queue的時候胖秒,應(yīng)用這個策略允耿,就會自動將數(shù)據(jù)同步到其他的節(jié)點上去了。
如何保證消息不被重復(fù)消費鞍遣馈(如何進行消息隊列的冪等性問題)较锡?
比如你拿個數(shù)據(jù)要寫庫,你先根據(jù)主鍵查一下盗痒,如果這數(shù)據(jù)都有了蚂蕴,你就別插入了,update一下好吧.
比如你是寫redis俯邓,那沒問題了骡楼,反正每次都是set,天然冪等性.
比如你不是上面兩個場景稽鞭,那做的稍微復(fù)雜一點鸟整,你需要讓生產(chǎn)者發(fā)送每條數(shù)據(jù)的時候,里面加一個全局唯一的id朦蕴,類似訂單id之類的東西篮条,然后你這里消費到了之后,先根據(jù)這個id去比如redis里查一下吩抓,之前消費過嗎涉茧?如果沒有消費過,你就處理疹娶,然后這個id寫redis伴栓。如果消費過了,那你就別處理了雨饺,保證別重復(fù)處理相同的消息即可钳垮。
如何保證消息的可靠性傳輸(如何處理消息丟失的問題)?
生產(chǎn)者弄丟了數(shù)據(jù),生產(chǎn)者將數(shù)據(jù)發(fā)送到rabbitmq的時候额港,可能數(shù)據(jù)就在半路給搞丟了饺窿,因為網(wǎng)絡(luò)啥的問題,都有可能锹安。此時可以選擇用rabbitmq提供的事務(wù)功能短荐,就是生產(chǎn)者發(fā)送數(shù)據(jù)之前開啟rabbitmq事務(wù)(channel.txSelect),然后發(fā)送消息叹哭,如果消息沒有成功被rabbitmq接收到忍宋,那么生產(chǎn)者會收到異常報錯,此時就可以回滾事務(wù)(channel.txRollback)风罩,然后重試發(fā)送消息糠排;如果收到了消息,那么可以提交事務(wù)(channel.txCommit)超升。但是問題是入宦,rabbitmq事務(wù)機制一搞,基本上吞吐量會下來室琢,因為太耗性能乾闰。
所以一般來說,如果你要確保說寫rabbitmq的消息別丟盈滴,可以開啟confirm模式涯肩,在生產(chǎn)者那里設(shè)置開啟confirm模式之后,你每次寫的消息都會分配一個唯一的id巢钓,然后如果寫入了rabbitmq中病苗,rabbitmq會給你回傳一個ack消息,告訴你說這個消息ok了症汹。如果rabbitmq沒能處理這個消息硫朦,會回調(diào)你一個nack接口,告訴你這個消息接收失敗背镇,你可以重試咬展。而且你可以結(jié)合這個機制自己在內(nèi)存里維護每個消息id的狀態(tài),如果超過一定時間還沒接收到這個消息的回調(diào)瞒斩,那么你可以重發(fā)挚赊。
事務(wù)機制和cnofirm機制最大的不同在于,事務(wù)機制是同步的济瓢,你提交一個事務(wù)之后會阻塞在那兒荠割,但是confirm機制是異步的,你發(fā)送個消息之后就可以發(fā)送下一個消息旺矾,然后那個消息rabbitmq接收了之后會異步回調(diào)你一個接口通知你這個消息接收到了蔑鹦。所以一般在生產(chǎn)者這塊避免數(shù)據(jù)丟失,都是用confirm機制的箕宙。rabbitmq弄丟了數(shù)據(jù),就是rabbitmq自己弄丟了數(shù)據(jù)嚎朽,這個你必須開啟rabbitmq的持久化,就是消息寫入之后會持久化到磁盤柬帕,哪怕是rabbitmq自己掛了哟忍,恢復(fù)之后會自動讀取之前存儲的數(shù)據(jù)狡门,一般數(shù)據(jù)不會丟。除非極其罕見的是锅很,rabbitmq還沒持久化其馏,自己就掛了,可能導(dǎo)致少量數(shù)據(jù)會丟失的爆安,但是這個概率較小叛复。
設(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ù)蹈垢。
而且持久化可以跟生產(chǎn)者那邊的confirm機制配合起來,只有消息被持久化到磁盤之后袖裕,才會通知生產(chǎn)者ack了曹抬,所以哪怕是在持久化到磁盤之前,rabbitmq掛了急鳄,數(shù)據(jù)丟了谤民,生產(chǎn)者收不到ack,你也是可以自己重發(fā)的疾宏。
哪怕是你給rabbitmq開啟了持久化機制张足,也有一種可能,就是這個消息寫到了rabbitmq中坎藐,但是還沒來得及持久化到磁盤上为牍,結(jié)果不巧,此時rabbitmq掛了岩馍,就會導(dǎo)致內(nèi)存里的一點點數(shù)據(jù)會丟失碉咆。消費端弄丟了數(shù)據(jù),rabbitmq如果丟失了數(shù)據(jù),主要是因為你消費的時候蛀恩,剛消費到疫铜,還沒處理,結(jié)果進程掛了双谆,比如重啟了壳咕,那么就尷尬了席揽,rabbitmq認(rèn)為你都消費了,這數(shù)據(jù)就丟了谓厘。這個時候得用rabbitmq提供的ack機制幌羞,簡單來說,就是你關(guān)閉rabbitmq自動ack庞呕,可以通過一個api來調(diào)用就行新翎,然后每次你自己代碼里確保處理完的時候程帕,再程序里ack一把住练。這樣的話,如果你還沒處理完愁拭,不就沒有ack讲逛?那rabbitmq就認(rèn)為你還沒處理完,這個時候rabbitmq會把這個消費分配給別的consumer去處理岭埠,消息是不會丟的盏混。
如何保證消息的順序性?
rabbitmq:拆分多個queue惜论,每個queue一個consumer许赃,就是多一些queue而已,確實是麻煩點馆类;或者就一個queue但是對應(yīng)一個consumer混聊,然后這個consumer內(nèi)部用內(nèi)存隊列做排隊,然后分發(fā)給底層不同的worker來處理.
如何解決消息隊列的延時以及過期失效問題乾巧?消息隊列滿了以后該怎么處理句喜?有幾百萬消息持續(xù)積壓幾小時,說說怎么解決沟于?
rabbitmq是可以設(shè)置過期時間的咳胃,就是TTL,如果消息在queue中積壓超過一定的時間就會被rabbitmq給清理掉旷太,這個數(shù)據(jù)就沒了展懈。那這就是第二個坑了。這就不是說數(shù)據(jù)會大量積壓在mq里供璧,而是大量的數(shù)據(jù)會直接搞丟标沪。
這個情況下,就不是說要增加consumer消費積壓的消息嗜傅,因為實際上沒啥積壓金句,而是丟了大量的消息。我們可以采取一個方案吕嘀,就是批量重導(dǎo)违寞,這個時候我們就開始寫程序贞瞒,將丟失的那批數(shù)據(jù),寫個臨時程序趁曼,一點一點的查出來军浆,然后重新灌入mq里面去,把白天丟的數(shù)據(jù)給他補回來挡闰。也只能是這樣了乒融。
如果讓你寫一個消息隊列,該如何進行架構(gòu)設(shè)計吧忝酢赞季?說一下你的思路
首先這個mq得支持可伸縮性吧,就是需要的時候快速擴容奢驯,就可以增加吞吐量和容量申钩,那怎么搞?設(shè)計個分布式的系統(tǒng)唄瘪阁,參照一下kafka的設(shè)計理念撒遣,broker -> topic -> partition,每個partition放一個機器管跺,就存一部分?jǐn)?shù)據(jù)义黎。如果現(xiàn)在資源不夠了,簡單啊豁跑,給topic增加partition廉涕,然后做數(shù)據(jù)遷移,增加機器贩绕,不就可以存放更多數(shù)據(jù)火的,提供更高的吞吐量了?
其次你得考慮一下這個mq的數(shù)據(jù)要不要落地磁盤吧淑倾?那肯定要了馏鹤,落磁盤,才能保證別進程掛了數(shù)據(jù)就丟了娇哆。那落磁盤的時候怎么落芭壤邸?順序?qū)懓郑@樣就沒有磁盤隨機讀寫的尋址開銷治力,磁盤順序讀寫的性能是很高的,這就是kafka的思路勃黍。
其次你考慮一下你的mq的可用性跋场?這個事兒覆获,具體參考我們之前可用性那個環(huán)節(jié)講解的kafka的高可用保障機制马澈。多副本 -> leader & follower -> broker掛了重新選舉leader即可對外服務(wù)瓢省。
能不能支持?jǐn)?shù)據(jù)0丟失啊痊班?
注: 大部分摘抄至中華石杉老師筆記.