什么是隊(duì)列(queue)舶胀?
隊(duì)列是一種存儲(chǔ)、組織數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)面殖,最大的特點(diǎn)就是先進(jìn)先出(FIFO)。
什么是消息隊(duì)列哭廉?
服務(wù)之間最常見的通信方式是直接調(diào)用彼此來通信脊僚,消息從一端發(fā)出后立即就可以達(dá)到另一端,稱為即時(shí)消息通訊(同步通信)遵绰;
消息從某一端發(fā)出后辽幌,首先進(jìn)入一個(gè)容器進(jìn)行臨時(shí)存儲(chǔ),當(dāng)達(dá)到某種條件后椿访,再由這個(gè)容器發(fā)送給另一端乌企,稱為延遲消息通訊 (異步通信)。
而容器的一個(gè)具體實(shí)現(xiàn)就是MQ(Message Queue)成玫。
可以通過小紅和小明讀書的故事來理解一下消息隊(duì)列加酵。
什么是RabbitMQ?
RabbitMQ是一個(gè)實(shí)現(xiàn)了AMQP(Advanced Message Queuing Protocol)高級(jí)消息隊(duì)列協(xié)議的消息隊(duì)列服務(wù)哭当,用Erlang語言編寫猪腕,Erlang語言是為電話交換機(jī)開發(fā)的語言,天生自帶高并發(fā)光環(huán)钦勘,和高可用特性陋葡。
rabbitmq模擬器:http://tryrabbitmq.com/,可以直觀的理解消息的傳遞方式彻采。
使用方法:
1腐缤、直接將畫框左面的圖標(biāo)拖進(jìn)畫圖區(qū),構(gòu)建想要的拓?fù)鋱D肛响;
2岭粤、按住ALT或SHIFT鍵,鼠標(biāo)點(diǎn)擊需要連接的圖標(biāo)(如果不能連接终惑,試試相反方向,例如先點(diǎn)擊queue再點(diǎn)擊exchange) ;
3门扇、雙擊擊圖標(biāo)進(jìn)行編輯設(shè)置各項(xiàng)功能雹有;
4偿渡、binding key不在queue上設(shè)定,雙擊擊連線中間bindingkey霸奕。
什么是AMQP協(xié)議溜宽?
全稱為Advanced Message Queuing Protocol,一個(gè)提供統(tǒng)一消息服務(wù)的應(yīng)用層標(biāo)準(zhǔn)高級(jí)消息隊(duì)列協(xié)議质帅,是一個(gè)通用的應(yīng)用層協(xié)議适揉。消息發(fā)送與接受的雙方遵守這個(gè)協(xié)議可以實(shí)現(xiàn)異步通訊,這個(gè)協(xié)議約定了消息的格式和工作方式煤惩。
為什么選擇RabbitMQ嫉嘀?
現(xiàn)在的市面上有很多MQ可以選擇,比如ActiveMQ魄揉、ZeroMQ剪侮、Appche Qpid,那問題來了為什么要選擇RabbitMQ洛退?
- 除了Qpid瓣俯,RabbitMQ是唯一一個(gè)實(shí)現(xiàn)了AMQP標(biāo)準(zhǔn)的消息服務(wù)器;
- 可靠性兵怯,RabbitMQ的持久化支持彩匕,保證了消息的穩(wěn)定性;
- 高并發(fā)媒区,RabbitMQ使用了Erlang開發(fā)語言驼仪,Erlang是為電話交換機(jī)開發(fā)的語言,天生自帶高并發(fā)光環(huán)驻仅,和高可用特性谅畅;
- 集群部署簡(jiǎn)單,正是應(yīng)為Erlang使得RabbitMQ集群部署變的超級(jí)簡(jiǎn)單噪服;
- 社區(qū)活躍度高毡泻,根據(jù)網(wǎng)上資料來看,RabbitMQ也是首選粘优。
RabbitMQ的流程與原理
角色
生產(chǎn)者:消息的創(chuàng)建者仇味,負(fù)責(zé)創(chuàng)建和推送數(shù)據(jù)到消息服務(wù)器;
消費(fèi)者:消息的接收方雹顺,用于處理數(shù)據(jù)和確認(rèn)消息丹墨;
代理人:就是RabbitMQ本身,用于扮演“快遞”的角色嬉愧,本身不生產(chǎn)消息贩挣,只是扮演“快遞”的角色。
名詞解釋
ConnectionFactory(連接管理器):應(yīng)用程序與Rabbit之間建立連接的管理器,程序代碼中使用王财;
Channel(信道):消息推送使用的通道卵迂,1個(gè)或多個(gè);
Broker(代理人):即RabbitMQ Server绒净,不是集群见咒,就是1個(gè),否則就是多個(gè)挂疆;
Virtual host(虛擬機(jī)):簡(jiǎn)稱vhost改览,1個(gè)或多個(gè),當(dāng)多個(gè)不同的用戶使用同一個(gè)RabbitMQ server提供的服務(wù)時(shí)缤言,可以劃分出多個(gè)vhost宝当,每個(gè)用戶在自己的vhost創(chuàng)建exchange/queue
Exchange(交換器):用于接受、分配消息墨闲,1個(gè)或多個(gè)今妄;
Queue(隊(duì)列):用于存儲(chǔ)生產(chǎn)者的消息,1個(gè)或多個(gè)鸳碧;
RoutingKey(路由鍵):用于把生產(chǎn)者的數(shù)據(jù)分配到交換器上盾鳞;
BindingKey(綁定鍵):用于把交換器的消息綁定到隊(duì)列上。
個(gè)人理解RoutingKey和BindingKey是指同一個(gè)關(guān)系瞻离,只不過是站在消費(fèi)者和和生產(chǎn)者的角度腾仅,叫法不一樣,取名不一樣套利。
原理
1推励、建立信道(Channel)
首先必須連接到RabbitMQ才能發(fā)布和消費(fèi)消息,那怎么連接和發(fā)送消息的呢肉迫?
我們的應(yīng)用程序和Broker(RabbitMQ Server)之間會(huì)創(chuàng)建一個(gè)TCP連接验辞,一旦TCP打開,并通過了認(rèn)證喊衫,認(rèn)證就是我們?cè)噲D連接RabbitMQ之前發(fā)送的RabbitMQ服務(wù)器連接信息跌造、用戶名和密碼,有點(diǎn)像程序連接數(shù)據(jù)庫族购,一旦認(rèn)證通過你的應(yīng)用程序和RabbitMQ就創(chuàng)建了一條AMQP信道(Channel)壳贪。
信道是創(chuàng)建在“真實(shí)”TCP上的虛擬連接,AMQP命令都是通過信道發(fā)送出去的寝杖,每個(gè)信道都會(huì)有一個(gè)唯一的ID违施,不論是發(fā)布消息,訂閱隊(duì)列或者介紹消息都是通過信道完成的瑟幕。
連接管理器管理著所有的信道磕蒲。
2留潦、創(chuàng)建/定位虛擬機(jī)(vhost)
默認(rèn)vhost為/,在建立連接時(shí)將作為參數(shù)傳遞給Broker(可以理解就是RabbitMQ本身)辣往,Broker會(huì)幫助我們創(chuàng)建好vhost愤兵,每個(gè)RabbitMQ可以創(chuàng)建多個(gè)vhost,每個(gè)vhost可以理解成一個(gè)mini版的RabbitMQ排吴,個(gè)人覺得也可以把vhost理解成一個(gè)命名空間。
3懦鼠、創(chuàng)建/定位隊(duì)列(Queue)钻哩,存放消息
如上圖所示,創(chuàng)建隊(duì)列1(名稱為Q1)肛冶,創(chuàng)建完隊(duì)列后街氢,會(huì)將隊(duì)列1(Q1)與交換機(jī)1(E1)建議綁定關(guān)系(E1Q1),這個(gè)關(guān)系名稱我們稱之為BindingKey睦袖。
到了這一步珊肃,生產(chǎn)者可以通過BindingKey(E1Q1)找到目標(biāo)隊(duì)列(Q1),將消息存入目標(biāo)隊(duì)列中(Q1)馅笙。
4伦乔、監(jiān)聽消息隊(duì)列,消費(fèi)消息
如上圖所示董习,消費(fèi)者1通過建立信道烈和,提供RoutingKey(E1Q1),即可監(jiān)聽到目標(biāo)隊(duì)列(Q1)皿淋,處理生產(chǎn)者1發(fā)送的消息招刹。
RabbitMQ的消息轉(zhuǎn)發(fā)類型(Exchange type)
如果把RabbitMQ比作一個(gè)傳遞消息的游戲,那么以上提到角色和名詞都是游戲參與者窝趣,為了讓游戲變得多樣有趣(滿足不同需求)疯暑,RabbitMQ還有幾個(gè)游戲規(guī)則,就是我們要說的消息轉(zhuǎn)發(fā)類型哑舒。
有3種玩法:
1妇拯、發(fā)布與訂閱(Direct exchange)
Direct Exchange 是 RabbitMQ 默認(rèn)的 Exchange。推薦使用這種散址,原理是通過消息中的routing key乖阵,與binding中的binding-key進(jìn)行比對(duì),若二者匹配预麸,則將消息發(fā)送到這個(gè)消息隊(duì)列或者獲取消息瞪浸。
我們用上面提到的rabbitmq模擬器來示意一下:
2、廣播(Fanout exchange)
3吏祸、主題(topic exchange)
比如設(shè)置BindingKey為*.Q1.*
对蒲,那么E1.Q1.C1
就會(huì)匹配上钩蚊,消息就發(fā)送給了消費(fèi)者1,如下圖:
怎樣保證RabbitMQ的可靠性
在默認(rèn)的情況下蹈矮,重啟服務(wù)器會(huì)導(dǎo)致消息隊(duì)列丟失砰逻,那么怎么保證Rabbit在重啟的時(shí)候不丟失呢?答案就是消息持久化泛鸟。
當(dāng)我們把消息發(fā)送到Rabbit服務(wù)器的時(shí)候蝠咆,需要選擇是否要進(jìn)行持久化,但這并不能保證Rabbit能從崩潰中恢復(fù)北滥,想要Rabbit消息能恢復(fù)必須滿足3個(gè)條件:
- 投遞消息的時(shí)候durable設(shè)置為true刚操,消息持久化,代碼:
channel.queueDeclare(x, true, false, false, null)
再芋,參數(shù)2設(shè)置為true持久化菊霜;
設(shè)置投遞模式deliveryMode設(shè)置為2(持久),代碼:channel.basicPublish(x, x,MessageProperties.PERSISTENT_TEXT_PLAIN,x)
济赎,參數(shù)3設(shè)置為存儲(chǔ)純文本到磁盤鉴逞; - 消息已經(jīng)到達(dá)持久化交換器上;
- 消息已經(jīng)到達(dá)持久化的隊(duì)列司训。
持久化工作原理
Rabbit會(huì)將我們的持久化消息寫入磁盤上的持久化日志文件构捡,等消息被消費(fèi)之后,Rabbit會(huì)把這條消息標(biāo)識(shí)為等待垃圾回收壳猜。
持久化的缺點(diǎn)
消息持久化的優(yōu)點(diǎn)顯而易見叭喜,但缺點(diǎn)也很明顯,那就是性能蓖谢,因?yàn)橐獙懭胗脖P要比寫入內(nèi)存性能較低很多捂蕴,從而降低了服務(wù)器的吞吐量,盡管使用SSD硬盤可以使事情得到緩解闪幽,但他仍然吸干了Rabbit的性能砸喻,當(dāng)消息成千上萬條要寫入磁盤的時(shí)候具篇,性能是很低的蒋伦。
所以我們要根據(jù)業(yè)務(wù)場(chǎng)景鬼譬,選擇適合自己的方式。
崩潰處理
再回到上面的流程示例圖腕够,圖中標(biāo)有1级乍、2、3數(shù)字標(biāo)示帚湘,下面我們來分析一下如果故障發(fā)生在1玫荣、2、3處大诸,RabbitMQ有什么解決方案捅厂?
1贯卦、如圖數(shù)字1處為交換器1,當(dāng)生產(chǎn)者1把消息傳給交換器1時(shí)焙贷,由于網(wǎng)絡(luò)原因撵割,發(fā)生了丟包怎么處理?
答:如何知道消息有沒有正確到達(dá)交換器呢辙芍?
(1)啡彬、通過AMQP提供的事務(wù)機(jī)制實(shí)現(xiàn)
(2)、通過生產(chǎn)者消息確認(rèn)機(jī)制(publisher confirm)實(shí)現(xiàn)
2故硅、如圖數(shù)字2處為隊(duì)列1外遇,當(dāng)生產(chǎn)者1把消息已經(jīng)傳到了隊(duì)列1,服務(wù)器斷電時(shí)契吉,消息丟失了怎么處理?
答:將保存在內(nèi)存中的數(shù)據(jù)都寫入磁盤诡渴,防止服務(wù)器重啟后數(shù)據(jù)丟失捐晶。
有哪些數(shù)據(jù)需要持久化保存呢?
元數(shù)據(jù)妄辩、消息需要持久化到磁盤惑灵;
磁盤節(jié)點(diǎn):持久化的消息在到達(dá)隊(duì)列時(shí)就被寫入到磁盤,并且如果可以眼耀,持久化的消息也會(huì)在內(nèi)存中保存一份備份英支,這樣可以提高一定的性能,只有在內(nèi)存吃緊的時(shí)候才會(huì)從內(nèi)存中清除哮伟;
內(nèi)存節(jié)點(diǎn):非持久化的消息一般只保存在內(nèi)存中干花,在內(nèi)存吃緊的時(shí)候會(huì)被換入到磁盤中,以節(jié)省內(nèi)存空間楞黄。
3池凄、如圖數(shù)字3處為信道3,當(dāng)消費(fèi)者1把消息從隊(duì)列1中取出鬼廓,消息還沒來得及處理就宕機(jī)丟失了怎么處理肿仑?
答:為了避免這種情況發(fā)生,我們可以要求消費(fèi)者在消費(fèi)完消息后發(fā)送一個(gè)回執(zhí)給RabbitMQ碎税,RabbitMQ收到消息回執(zhí)(Message acknowledgment)后才將該消息從Queue中移除尤慰。
如果一個(gè)Queue沒被任何的Consumer Subscribe(訂閱),當(dāng)有數(shù)據(jù)到達(dá)時(shí)雷蹂,這個(gè)數(shù)據(jù)會(huì)被cache伟端,不會(huì)被丟棄。當(dāng)有Consumer時(shí)匪煌,這個(gè)數(shù)據(jù)會(huì)被立即發(fā)送到這個(gè)Consumer荔泳。這個(gè)數(shù)據(jù)被Consumer正確收到時(shí)蕉饼,這個(gè)數(shù)據(jù)就被從Queue中刪除。
那么什么是正確收到呢玛歌?通過ACK昧港。每個(gè)Message都要被acknowledged(確認(rèn),ACK)支子。我們可以顯示的在程序中去ACK创肥,也可以自動(dòng)的ACK。如果有數(shù)據(jù)沒有被ACK值朋,那么RabbitMQ Server會(huì)把這個(gè)信息發(fā)送到下一個(gè)Consumer叹侄。
消息隊(duì)列的應(yīng)用場(chǎng)景有哪些?
1昨登、異步處理
非核心流程異步化趾代,提高系統(tǒng)響應(yīng)性能
2、應(yīng)用解耦
系統(tǒng)不是強(qiáng)耦合丰辣,消息接受者可以隨意增加撒强,而不需要修改消息發(fā)送者的代碼。消息發(fā)送者的成功不依賴消息接受者(比如有些銀行接口不穩(wěn)定笙什,但調(diào)用方并不需要依賴這些接口)
不強(qiáng)依賴于非本系統(tǒng)的核心流程飘哨,對(duì)于非核心流程,可以放到消息隊(duì)列中讓消息消費(fèi)者去按需消費(fèi)琐凭,而不影響核心主流程
3芽隆、最終一致性
最終一致性不是消息隊(duì)列的必備特性,但確實(shí)可以依靠消息隊(duì)列來做最終一致性的事情统屈。
先寫消息再操作胚吁,確保操作完成后再修改消息狀態(tài)。定時(shí)任務(wù)補(bǔ)償機(jī)制實(shí)現(xiàn)消息可靠發(fā)送接收愁憔、業(yè)務(wù)操作的可靠執(zhí)行囤采,要注意消息重復(fù)與冪等設(shè)計(jì)。
所有不保證100%不丟消息的消息隊(duì)列惩淳,理論上無法實(shí)現(xiàn)最終一致性蕉毯。
4、廣播
只需要關(guān)心消息是否送達(dá)了隊(duì)列思犁,至于誰希望訂閱代虾,是下游的事情。
5激蹲、流量削峰與流控
當(dāng)上下游系統(tǒng)處理能力存在差距的時(shí)候棉磨,利用消息隊(duì)列做一個(gè)通用的“漏斗”。在下游有能力處理的時(shí)候学辱,再進(jìn)行分發(fā)乘瓤。
6环形、日志處理
將消息隊(duì)列用在日志處理中,比如Kafka的應(yīng)用衙傀,解決大量日志傳輸?shù)膯栴}抬吟。
7、消息通訊
消息隊(duì)列一般都內(nèi)置了高效的通信機(jī)制统抬,因此也可以用于單純的消息通訊火本,比如實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)消息隊(duì)列或者聊天室等。
個(gè)人認(rèn)為消息隊(duì)列的主要特點(diǎn)是異步處理聪建,一切不著急馬上響應(yīng)結(jié)果的钙畔、非阻塞性的業(yè)務(wù)邏輯,都可以考慮使用消息隊(duì)列金麸,這樣可以減少用戶請(qǐng)求的響應(yīng)時(shí)間擎析,提高用戶體驗(yàn),同時(shí)也有利于業(yè)務(wù)模塊之間的解耦挥下。
從實(shí)際項(xiàng)目出發(fā)揍魂,整理了如下具體場(chǎng)景:
1、用戶注冊(cè)時(shí):注冊(cè)完后需要發(fā)送注冊(cè)成功郵件见秽,或發(fā)送手機(jī)短信,或推薦一些感興趣的主題或志同道合的人讨盒;
2解取、用戶上傳存證時(shí):需要生成證書、需要生成PDF文檔返顺;
3禀苦、用戶申購金融產(chǎn)品后:需要發(fā)送月報(bào)消息;
4遂鹊、買火車票時(shí):由于并發(fā)量太大振乏,按照火車票當(dāng)天的發(fā)行量,設(shè)置一個(gè)隊(duì)列長度秉扑,隊(duì)列滿則不能再進(jìn)入修補(bǔ)票隊(duì)列慧邮;
5、雙11秒殺商品時(shí):先排隊(duì)扣庫存舟陆,再提醒用戶付款误澳;
擴(kuò)展閱讀
快遞員A需要將一個(gè)快遞給客戶B。
原本:A親手將快遞交給B秦躯。
問題:B現(xiàn)在有很多事要做忆谓,A只能等著B處理完事情,才能把快遞交給他踱承,A就很難受了倡缠,送不了幾個(gè)快遞哨免,沒法賺錢了。
解決:設(shè)置一個(gè)快遞柜昙沦,A把快遞放進(jìn)快遞柜琢唾,就可以送下一個(gè)快遞了,B啥時(shí)候有空了桅滋,去快遞柜拿就行了慧耍。
總結(jié):快遞就是消息,快遞柜就是消息隊(duì)列丐谋∩直蹋快遞柜有很多種,菜鳥号俐,豐巢等泌豆,RabbitMQ就是其中一種快遞柜。
放在程序里舉例:
客戶提交訂單吏饿,支付系統(tǒng)——>訂單系統(tǒng)踪危,支付系統(tǒng)可能很快就執(zhí)行完了,但是訂單系統(tǒng)要很久才能執(zhí)行完猪落,每次支付系統(tǒng)都要等待訂單系統(tǒng)贞远,服務(wù)端速度就會(huì)很慢,現(xiàn)在使用RabbitMQ笨忌,支付系統(tǒng)支付成功后蓝仲,發(fā)送一個(gè)支付成功消息到RabbitMQ,就可以返回前端了官疲,訂單系統(tǒng)在獲取到消息后袱结,慢慢再執(zhí)行訂單修改的程序。(當(dāng)然途凫,要考慮到某個(gè)系統(tǒng)出了異常怎么辦垢夹,這個(gè)入門先不管,只管正常情況维费。)
參考:
https://zhuanlan.zhihu.com/p/37198933
https://www.zhihu.com/question/34243607
https://zhuanlan.zhihu.com/p/63700605
https://www.cnblogs.com/vipstone/p/9275256.html RabbitMQ系列(二)深入了解RabbitMQ工作原理及簡(jiǎn)單使用