一踩验、為什么使用消息隊列?
為什么使用商玫?其實就是在實際業(yè)務(wù)中箕憾,有個具體的場景,如果不使用MQ拳昌,可能會有很多麻煩袭异,用了MQ之后帶給我們很多好處。場景其實有很多炬藤,常見的有三個:1.解耦御铃、2.異步、3.削峰
1.解耦
A系統(tǒng)要發(fā)送一條數(shù)據(jù)到BCD三個系統(tǒng)沈矿,接口調(diào)用發(fā)送上真,如果新增個E系統(tǒng),也需要這條數(shù)據(jù)呢羹膳?如果C系統(tǒng)現(xiàn)在不需要這條數(shù)據(jù)了呢睡互?如果A系統(tǒng)又要發(fā)送第二種數(shù)據(jù)了呢?而且A系統(tǒng)要時刻關(guān)注BCD系統(tǒng)的狀態(tài)陵像,BCD掛了怎么辦就珠?要不要重發(fā)?要不要把數(shù)據(jù)存起來醒颖?
如果系統(tǒng)中存在類似情況妻怎,你可以考慮這個調(diào)用是不是必須要同步調(diào)用?如果用MQ來解耦泞歉,會省去很多麻煩蹂季。
2.異步
A系統(tǒng)接收一個請求冕广,需要在ABCD四個數(shù)據(jù)庫進(jìn)行寫庫操作,A本地寫庫需要30ms偿洁,B庫需要100ms撒汉,C庫需要200ms,C庫需要200ms涕滋,則一共需要30+100+200+200ms睬辐。如果用MQ來進(jìn)行異步操作,則只需要30ms后即可返回宾肺,BCD異步寫入即可溯饵,A不用考慮。
3.削峰
每天0~11點锨用,A系統(tǒng)風(fēng)平浪靜腌且,每秒并發(fā)100元践,12點時,并發(fā)暴增到10000,系統(tǒng)每秒只能處理1000個請求顷级,怎么辦曹阔?這時可以用MQ進(jìn)行流量削峰嗡髓。
二瀑梗、消息隊列的優(yōu)缺點
優(yōu)點已經(jīng)說了,解耦猾封,異步澄耍,削峰,接下來說消息隊列的缺點:
1.系統(tǒng)可用性降低
本來只需要考慮系統(tǒng)本身可用性晌缘,現(xiàn)在引入了MQ齐莲,如果MQ掛了怎么辦?
2.系統(tǒng)復(fù)雜性提高
MQ加進(jìn)來了磷箕,消息丟失怎么辦选酗?重復(fù)投遞怎么辦?重復(fù)消費怎么辦搀捷?消息的順序性怎么保證星掰?
3.一致性問題
A處理完返回成功了,調(diào)用者以為請求成功了嫩舟,可是BC成功氢烘,D失敗了怎么辦,數(shù)據(jù)就不一致了家厌。
所以播玖,消息隊列其實結(jié)構(gòu)非常復(fù)雜,引入它會帶來很多好處饭于,但是同時需要規(guī)避很多問題蜀踏。
三维蒙、應(yīng)對MQ缺點的辦法
1.消息丟失怎么辦?(消息的可靠性傳輸)
消息的丟失可能會出現(xiàn)在三個地方:
(1)生產(chǎn)者弄丟數(shù)據(jù)
生產(chǎn)者將數(shù)據(jù)發(fā)送到RabbitMQ的時候果覆,可能數(shù)據(jù)就在半路給搞丟了颅痊,因為網(wǎng)絡(luò)啥的問題,都有可能局待。怎么解決?
①事務(wù):生產(chǎn)者發(fā)送數(shù)據(jù)之前開啟RabbitMQ事務(wù)(channel.txSelect),然后發(fā)送消息斑响,如果消息沒有成功被RabbitMQ接收到,那么生產(chǎn)者會收到異常報錯钳榨,此時就可以回滾事務(wù)(channel.txRollback),然后重試發(fā)送消息舰罚;如果收到了消息,可以提交事務(wù)(channel.txCommit)薛耻。但是問題是营罢,RabbitMQ事務(wù)機(jī)制一搞,基本上吞吐量會下來饼齿,因為太耗性能饲漾。
②confirm模式:在生產(chǎn)者那里設(shè)置開啟confirm模式之后,你每次寫的消息都會分配一個唯一的id候醒,然后如果寫入了RabbitMQ中能颁,RabbitMQ會給你回傳一個ack消息杂瘸,告訴你說這個消息ok了倒淫。如果RabbitMQ沒能處理這個消息,會回調(diào)你一個nack接口败玉,告訴你這個消息接收失敗敌土,你可以重試。而且你可以結(jié)合這個機(jī)制自己在內(nèi)存里維護(hù)每個消息id的狀態(tài)运翼,如果超過一定時間還沒接收到這個消息的回調(diào)返干,那么你可以重發(fā)。
所以一般在生產(chǎn)者這塊避免數(shù)據(jù)丟失血淌,都是用confirm機(jī)制的矩欠。
(2)Mq弄丟數(shù)據(jù)
就是RabbitMQ自己弄丟了數(shù)據(jù),這個你必須開啟RabbitMQ的持久化悠夯,就是消息寫入之后會持久化到磁盤癌淮,哪怕是RabbitMQ自己掛了,恢復(fù)之后會自動讀取之前存儲的數(shù)據(jù)沦补,一般數(shù)據(jù)不會丟乳蓄。
設(shè)置持久化有兩個步驟:
①第一個是創(chuàng)建queue和交換器的時候?qū)⑵湓O(shè)置為持久化,這樣就可以保證RabbitMQ持久化相關(guān)的元數(shù)據(jù)夕膀,但是不會持久化queue里的數(shù)據(jù)虚倒;
②第二個是發(fā)送消息的時候?qū)⑾⒌膁eliveryMode設(shè)置為2美侦,就是將消息設(shè)置為持久化的,此時RabbitMQ就會將消息持久化到磁盤上去
必須要同時設(shè)置這兩個持久化才行
持久化可以和生產(chǎn)者的confirm結(jié)合魂奥,當(dāng)持久化成功后菠剩,再ack生產(chǎn)者。如果持久化之前RabbitMQ掛了耻煤,生產(chǎn)者沒收到ack赠叼,會重發(fā)。
(3)消費者弄丟數(shù)據(jù)
RabbitMQ如果丟失了數(shù)據(jù)违霞,主要是因為你消費的時候嘴办,剛消費到,還沒處理买鸽,結(jié)果進(jìn)程掛了涧郊,比如重啟了,那么就尷尬了眼五,RabbitMQ認(rèn)為你都消費了妆艘,這數(shù)據(jù)就丟了。
這個時候得用RabbitMQ提供的ack機(jī)制看幼,簡單來說批旺,就是你關(guān)閉RabbitMQ自動ack,可以通過一個api來調(diào)用就行诵姜,然后每次你自己代碼里確保處理完的時候汽煮,再程序里ack一把。這樣的話棚唆,如果你還沒處理完暇赤,不就沒有ack?那RabbitMQ就認(rèn)為你還沒處理完宵凌,這個時候RabbitMQ會把這個消費分配給別的consumer去處理鞋囊,消息是不會丟的。
2.消費者順序消費
從根本上說瞎惫,異步消息是不應(yīng)該有順序依賴的溜腐。在MQ上估計是沒法解決。要實現(xiàn)嚴(yán)格的順序消息瓜喇,簡單且可行的辦法就是:保證生產(chǎn)者- MQServer -消費者是一對一對一的關(guān)系挺益。
如果有順序依賴的消息,要保證消息有一個hashKey欠橘,類似于數(shù)據(jù)庫表分區(qū)的的分區(qū)key列矩肩。保證對同一個key的消息發(fā)送到相同的隊列。A用戶產(chǎn)生的消息(包括創(chuàng)建消息和刪除消息)都按A的hashKey分發(fā)到同一個隊列。只需要把強(qiáng)相關(guān)的兩條消息基于相同的路由就行了黍檩,也就是說經(jīng)過m1和m2的在路由表里的路由是一樣的叉袍,那自然m1會優(yōu)先于m2去投遞。而且一個queue只對應(yīng)一個consumer
3.消息的重復(fù)
分為兩大類情況:1刽酱、生產(chǎn)者消息重復(fù)發(fā)送喳逛; 2.MQ向消費者投遞時重復(fù)投遞
終極解決辦法:冪等性? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
1. MVCC:
多版本并發(fā)控制,樂觀鎖的一種實現(xiàn)棵里,在生產(chǎn)者發(fā)送消息時進(jìn)行數(shù)據(jù)更新時需要帶上數(shù)據(jù)的版本號润文,消費者去更新時需要去比較持有數(shù)據(jù)的版本號,版本號不一致的操作無法成功殿怜。例如博客點贊次數(shù)自動+1的接口:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? public boolean addCount(Long id, Long version);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? update blogTable set count= count+1,version=version+1 where id=321 and version=123
每一個version只有一次執(zhí)行成功的機(jī)會典蝌,一旦失敗了生產(chǎn)者必須重新獲取數(shù)據(jù)的最新版本號再次發(fā)起更新。
2. 去重表:
利用數(shù)據(jù)庫表單的特性來實現(xiàn)冪等头谜,常用的一個思路是在表上構(gòu)建唯一性索引骏掀,保證某一類數(shù)據(jù)一旦執(zhí)行完畢,后續(xù)同樣的請求不再重復(fù)處理了(利用一張日志表來記錄已經(jīng)處理成功的消息的ID柱告,如果新到的消息ID已經(jīng)在日志表中截驮,那么就不再處理這條消息。)
以電商平臺為例子际度,電商平臺上的訂單id就是最適合的token葵袭。當(dāng)用戶下單時,會經(jīng)歷多個環(huán)節(jié)乖菱,比如生成訂單坡锡,減庫存,減優(yōu)惠券等等块请。每一個環(huán)節(jié)執(zhí)行時都先檢測一下該訂單id是否已經(jīng)執(zhí)行過這一步驟娜氏,對未執(zhí)行的請求拳缠,執(zhí)行操作并緩存結(jié)果墩新,而對已經(jīng)執(zhí)行過的id,則直接返回之前的執(zhí)行結(jié)果窟坐,不做任何操作海渊。這樣可以在最大程度上避免操作的重復(fù)執(zhí)行問題,緩存起來的執(zhí)行結(jié)果也能用于事務(wù)的控制等哲鸳。