突破Java面試-MQ的數(shù)據(jù)去哪了?
1 面試題
如何保證消息的可靠性傳輸(如何處理消息丟失的問題)?
2 考點分析
這個是肯定的,用mq有個基本原則,就是數(shù)據(jù)不能多一條,也不能少一條,不能多,就是剛才說的重復消費和冪等性問題。不能少,就是說這數(shù)據(jù)別搞丟了。那這個問題你必須得考慮一下。
如果說你這個是用mq來傳遞非常核心的消息,比如說計費,扣費的一些消息,因為我以前設計和研發(fā)過一個公司非常核心的廣告平臺,計費系統(tǒng),計費系統(tǒng)是很重的一個業(yè)務,操作是很耗時的。所以說廣告系統(tǒng)整體的架構里面,實際上是將計費做成異步化的,然后中間就是加了一個MQ。
我們當時為了確保說這個MQ傳遞過程中絕對不會把計費消息給弄丟,花了很多的精力。廣告主投放了一個廣告,明明說好了,用戶點擊一次扣費1塊錢。結果要是用戶動不動點擊了一次,扣費的時候搞的消息丟了,我們公司就會不斷的少幾塊錢,幾塊錢,積少成多,這個就對公司是一個很大的損失。
3 詳解
這個丟數(shù)據(jù),mq一般分為兩種,要么是mq自己弄丟了,要么是我們消費的時候弄丟了
咱們從rabbitmq和kafka分別來分析一下吧
rabbitmq這種mq,一般來說都是承載公司的核心業(yè)務的,數(shù)據(jù)是絕對不能弄丟的
3.1 rabbitmq
3.1.1 生產者丟數(shù)據(jù)
生產者將數(shù)據(jù)發(fā)送到rabbitmq的時候,可能數(shù)據(jù)就在半路給搞丟了,因為網絡啥的問題,都有可能。
此時可以選擇用rabbitmq提供的事務功能,就是生產者發(fā)送數(shù)據(jù)之前開啟rabbitmq事務(channel.txSelect),然后發(fā)送消息
如果消息沒有成功被rabbitmq接收到,那么生產者會收到異常報錯,此時就可以回滾事務(channel.txRollback),然后重試發(fā)送消息;
如果收到了消息,那么可以提交事務(channel.txCommit)
但是問題是,rabbitmq事務機制一搞,基本上吞吐量會下來,因為太耗性能。
所以一般來說,如果你要確保說寫rabbitmq的消息別丟,可以開啟confirm模式,在生產者那里設置開啟confirm模式之后,你每次寫的消息都會分配一個唯一的id,然后如果寫入了rabbitmq中,rabbitmq會給你回傳一個ack消息,告訴你說這個消息ok了。如果rabbitmq沒能處理這個消息,會回調你一個nack接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存里維護每個消息id的狀態(tài),如果超過一定時間還沒接收到這個消息的回調,那么你可以重發(fā)。
事務機制和cnofirm機制最大的不同在于,事務機制是同步的,你提交一個事務之后會阻塞在那兒,但是confirm機制是異步的,你發(fā)送個消息之后就可以發(fā)送下一個消息,然后那個消息rabbitmq接收了之后會異步回調你一個接口通知你這個消息接收到了。
所以一般在生產者這塊避免數(shù)據(jù)丟失,都是用confirm機制的。
3.1.2 rabbitmq丟數(shù)據(jù)
就是rabbitmq自己弄丟了數(shù)據(jù),這個你必須開啟rabbitmq的持久化,就是消息寫入之后會持久化到磁盤,哪怕是rabbitmq自己掛了,恢復之后會自動讀取之前存儲的數(shù)據(jù),一般數(shù)據(jù)不會丟。除非極其罕見的是,rabbitmq還沒持久化,自己就掛了,可能導致少量數(shù)據(jù)會丟失的,但是這個概率較小。
設置持久化有兩個步驟,第一個是創(chuàng)建queue的時候將其設置為持久化的,這樣就可以保證rabbitmq持久化queue的元數(shù)據(jù),但是不會持久化queue里的數(shù)據(jù);第二個是發(fā)送消息的時候將消息的deliveryMode設置為2,就是將消息設置為持久化的,此時rabbitmq就會將消息持久化到磁盤上去。必須要同時設置這兩個持久化才行,rabbitmq哪怕是掛了,再次重啟,也會從磁盤上重啟恢復queue,恢復這個queue里的數(shù)據(jù)。
而且持久化可以跟生產者那邊的confirm機制配合起來,只有消息被持久化到磁盤之后,才會通知生產者ack了,所以哪怕是在持久化到磁盤之前,rabbitmq掛了,數(shù)據(jù)丟了,生產者收不到ack,你也是可以自己重發(fā)的。
哪怕是你給rabbitmq開啟了持久化機制,也有一種可能,就是這個消息寫到了rabbitmq中,但是還沒來得及持久化到磁盤上,結果不巧,此時rabbitmq掛了,就會導致內存里的一點點數(shù)據(jù)會丟失。
3.1.3 消費端弄丟了數(shù)據(jù)
rabbitmq如果丟失了數(shù)據(jù),主要是因為你消費的時候,剛消費到,還沒處理,結果進程掛了,比如重啟了,那么就尷尬了,rabbitmq認為你都消費了,這數(shù)據(jù)就丟了。
這個時候得用rabbitmq提供的ack機制,簡單來說,就是你關閉rabbitmq自動ack,可以通過一個api來調用就行,然后每次你自己代碼里確保處理完的時候,再程序里ack一把。
這樣的話,如果你還沒處理完,不就沒有ack?那rabbitmq就認為你還沒處理完,這個時候rabbitmq會把這個消費分配給別的consumer去處理,消息是不會丟的。
3.2 Kafka
3.2.1 消費端丟數(shù)據(jù)
唯一可能導致消費者弄丟數(shù)據(jù)的情況,你那個消費到了這個消息,然后消費者那邊自動提交了offset,讓kafka以為你已經消費好了這個消息,其實你剛準備處理這個消息,你還沒處理,你自己就掛了,此時這條消息就丟咯。
這不是一樣么,大家都知道kafka會自動提交offset,那么只要關閉自動提交offset,在處理完之后自己手動提交offset,就可以保證數(shù)據(jù)不會丟。但是此時確實還是會重復消費,比如你剛處理完,還沒提交offset,結果自己掛了,此時肯定會重復消費一次,自己保證冪等性就好了。
生產環(huán)境碰到的一個問題,就是說我們的kafka消費者消費到了數(shù)據(jù)之后是寫到一個內存的queue里先緩沖一下,結果有的時候,你剛把消息寫入內存queue,然后消費者會自動提交offset。
然后此時我們重啟了系統(tǒng),就會導致內存queue里還沒來得及處理的數(shù)據(jù)就丟失了
3.2.2 kafka弄丟了數(shù)據(jù)
這塊比較常見的一個場景,就是kafka某個broker宕機,然后重新選舉partiton的leader時。大家想想,要是此時其他的follower剛好還有些數(shù)據(jù)沒有同步,結果此時leader掛了,然后選舉某個follower成leader之后,他不就少了一些數(shù)據(jù)?這就丟了一些數(shù)據(jù)啊。
生產環(huán)境也遇到過,我們也是,之前kafka的leader機器宕機了,將follower切換為leader之后,就會發(fā)現(xiàn)說這個數(shù)據(jù)就丟了
所以此時一般是要求起碼設置如下4個參數(shù):
給這個topic設置replication.factor參數(shù):這個值必須大于1,要求每個partition必須有至少2個副本
在kafka服務端設置min.insync.replicas參數(shù):這個值必須大于1,這個是要求一個leader至少感知到有至少一個follower還跟自己保持聯(lián)系,沒掉隊,這樣才能確保leader掛了還有一個follower吧
在producer端設置acks=all:這個是要求每條數(shù)據(jù),必須是寫入所有replica之后,才能認為是寫成功了
在producer端設置retries=MAX(很大很大很大的一個值,無限次重試的意思):這個是要求一旦寫入失敗,就無限重試,卡在這里了
我們生產環(huán)境就是按照上述要求配置的,這樣配置之后,至少在kafka broker端就可以保證在leader所在broker發(fā)生故障,進行l(wèi)eader切換時,數(shù)據(jù)不會丟失
3.2.3 生產者會不會弄丟數(shù)據(jù)
如果按照上述的思路設置了ack=all,一定不會丟,要求是,你的leader接收到消息,所有的follower都同步到了消息之后,才認為本次寫成功了。如果沒滿足這個條件,生產者會自動不斷的重試,重試無限次。
Java RabbitMQ
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。