爲何使用?其實就是在實際業務中,有個具體的場景,若是不使用MQ,可能會有不少麻煩,用了MQ以後帶給咱們不少好處。場景其實有不少,常見的有三個:1.解耦、2.異步、3.削峯數據庫
A系統要發送一條數據到BCD三個系統,接口調用發送,若是新增個E系統,也須要這條數據呢?若是C系統如今不須要這條數據了呢?若是A系統又要發送第二種數據了呢?並且A系統要時刻關注BCD系統的狀態,BCD掛了怎麼辦?要不要重發?要不要把數據存起來?api
若是系統中存在相似狀況,你能夠考慮這個調用是否是必需要同步調用?若是用MQ來解耦,會省去不少麻煩。緩存
A系統接收一個請求,須要在ABCD四個數據庫進行寫庫操做,A本地寫庫須要30ms,B庫須要100ms,C庫須要200ms,C庫須要200ms,則一共須要30+100+200+200ms。若是用MQ來進行異步操做,則只須要30ms後便可返回,BCD異步寫入便可,A不用考慮。網絡
天天0~11點,A系統風平浪靜,每秒併發100,12點時,併發暴增到10000,系統每秒只能處理1000個請求,怎麼辦?這時能夠用MQ進行流量削峯。併發
優勢已經說了,解耦,異步,削峯,接下來講消息隊列的缺點:異步
原本只須要考慮系統自己可用性,如今引入了MQ,若是MQ掛了怎麼辦?性能
MQ加進來了,消息丟失怎麼辦?重複投遞怎麼辦?重複消費怎麼辦?消息的順序性怎麼保證?日誌
A處理完返回成功了,調用者覺得請求成功了,但是BC成功,D失敗了怎麼辦,數據就不一致了。orm
因此,消息隊列其實結構很是複雜,引入它會帶來不少好處,可是同時須要規避不少問題。blog
消息的丟失可能會出如今三個地方:
(1)生產者弄丟數據
生產者將數據發送到RabbitMQ的時候,可能數據就在半路給搞丟了,由於網絡啥的問題,都有可能。怎麼解決?
①事務:生產者發送數據以前開啓RabbitMQ事務(channel.txSelect),而後發送消息,若是消息沒有成功被RabbitMQ接收到,那麼生產者會收到異常報錯,此時就能夠回滾事務(channel.txRollback),而後重試發送消息;若是收到了消息,能夠提交事務(channel.txCommit)。可是問題是,RabbitMQ事務機制一搞,基本上吞吐量會下來,由於太耗性能。
②confirm模式:在生產者那裏設置開啓confirm模式以後,你每次寫的消息都會分配一個惟一的id,而後若是寫入了RabbitMQ中,RabbitMQ會給你回傳一個ack消息,告訴你說這個消息ok了。若是RabbitMQ沒能處理這個消息,會回調你一個nack接口,告訴你這個消息接收失敗,你能夠重試。並且你能夠結合這個機制本身在內存裏維護每一個消息id的狀態,若是超過必定時間還沒接收到這個消息的回調,那麼你能夠重發。
因此通常在生產者這塊避免數據丟失,都是用confirm機制的。
(2)Mq弄丟數據
就是RabbitMQ本身弄丟了數據,這個你必須開啓RabbitMQ的持久化,就是消息寫入以後會持久化到磁盤,哪怕是RabbitMQ本身掛了,恢復以後會自動讀取以前存儲的數據,通常數據不會丟。
設置持久化有兩個步驟:
①第一個是建立queue和交換器的時候將其設置爲持久化,這樣就能夠保證RabbitMQ持久化相關的元數據,可是不會持久化queue裏的數據;
②第二個是發送消息的時候將消息的deliveryMode設置爲2,就是將消息設置爲持久化的,此時RabbitMQ就會將消息持久化到磁盤上去
必需要同時設置這兩個持久化才行
持久化能夠和生產者的confirm結合,當持久化成功後,再ack生產者。若是持久化以前RabbitMQ掛了,生產者沒收到ack,會重發。
(3)消費者弄丟數據
RabbitMQ若是丟失了數據,主要是由於你消費的時候,剛消費到,還沒處理,結果進程掛了,好比重啓了,那麼就尷尬了,RabbitMQ認爲你都消費了,這數據就丟了。
這個時候得用RabbitMQ提供的ack機制,簡單來講,就是你關閉RabbitMQ自動ack,能夠經過一個api來調用就行,而後每次你本身代碼裏確保處理完的時候,再程序裏ack一把。這樣的話,若是你還沒處理完,不就沒有ack?那RabbitMQ就認爲你還沒處理完,這個時候RabbitMQ會把這個消費分配給別的consumer去處理,消息是不會丟的。
從根本上說,異步消息是不該該有順序依賴的。在MQ上估計是無法解決。要實現嚴格的順序消息,簡單且可行的辦法就是:保證生產者- MQServer -消費者是一對一對一的關係。
若是有順序依賴的消息,要保證消息有一個hashKey,相似於數據庫表分區的的分區key列。保證對同一個key的消息發送到相同的隊列。A用戶產生的消息(包括建立消息和刪除消息)都按A的hashKey分發到同一個隊列。只須要把強相關的兩條消息基於相同的路由就好了,也就是說通過m1和m2的在路由表裏的路由是同樣的,那天然m1會優先於m2去投遞。並且一個queue只對應一個consumer
分爲兩大類狀況:一、生產者消息重複發送; 2.MQ向消費者投遞時重複投遞
終極解決辦法:冪等性
1. MVCC:
多版本併發控制,樂觀鎖的一種實現,在生產者發送消息時進行數據更新時須要帶上數據的版本號,消費者去更新時須要去比較持有數據的版本號,版本號不一致的操做沒法成功。例如博客點贊次數自動+1的接口: public boolean addCount(Long id, Long version); update blogTable set count= count+1,version=version+1 where id=321 and version=123
每個version只有一次執行成功的機會,一旦失敗了生產者必須從新獲取數據的最新版本號再次發起更新。
2. 去重表:
利用數據庫表單的特性來實現冪等,經常使用的一個思路是在表上構建惟一性索引,保證某一類數據一旦執行完畢,後續一樣的請求再也不重複處理了(利用一張日誌表來記錄已經處理成功的消息的ID,若是新到的消息ID已經在日誌表中,那麼就再也不處理這條消息。)
以電商平臺爲例子,電商平臺上的訂單id就是最適合的token。當用戶下單時,會經歷多個環節,好比生成訂單,減庫存,減優惠券等等。每個環節執行時都先檢測一下該訂單id是否已經執行過這一步驟,對未執行的請求,執行操做並緩存結果,而對已經執行過的id,則直接返回以前的執行結果,不作任何操做。這樣能夠在最大程度上避免操做的重複執行問題,緩存起來的執行結果也能用於事務的控制等。