分布式面試消息隊(duì)列解決消息重復(fù)保證消息順序
引言
我在《項(xiàng)目中為什么要使用消息隊(duì)列》中列舉了兩個(gè)使用消息隊(duì)列的例子。
(1)收銀系統(tǒng),確認(rèn)收款成功,通過(guò)MQ通知給物流系統(tǒng)發(fā)貨。
(2)消費(fèi)積分,用戶(hù)每消費(fèi)一筆給用戶(hù)增加一定積分,京東豆,信用卡積分,2020年如果還沒(méi)倒閉的電商平臺(tái)中,可以100%的確定訂單系統(tǒng)和積分/獎(jiǎng)勵(lì)系統(tǒng)不是耦合在一起的。
這些都是很典型的使用消息隊(duì)列的場(chǎng)景。
那么問(wèn)題來(lái)了,想象一下,積分系統(tǒng)收到同一個(gè)用戶(hù)同一個(gè)訂單兩條相同的消息會(huì)怎樣?積分會(huì)被加兩遍嗎?針對(duì)這個(gè)問(wèn)題,面試官又開(kāi)始一輪三連問(wèn),你還能扛得過(guò)去嗎?
這不是“面試造火箭,工作擰螺絲”,消息重復(fù),消息積壓這類(lèi)問(wèn)題是你入職后工作中真真切切會(huì)遇到的,不是面試官故意刁難你。
1、面試官:
那你有考慮過(guò)消息重復(fù)問(wèn)題怎么解決嗎?
問(wèn)題分析:還是拿上面的例子分析,積分系統(tǒng)收到同一個(gè)用戶(hù)同一個(gè)訂單兩條相同的消息會(huì)怎樣,先不管因?yàn)槭裁丛蛳l(fā)了兩次,積分會(huì)被加兩遍嗎?
產(chǎn)品經(jīng)理說(shuō): 那肯定不行呀,花100塊給100個(gè)積分,積分沒(méi)有買(mǎi)一送一服務(wù)。
訂單系統(tǒng)RD說(shuō): 我這邊沒(méi)辦法100%保證積分廣播只發(fā)一次,萬(wàn)一出個(gè)bug同一筆消費(fèi)積分,消息可能發(fā)了幾百次也不好說(shuō)。
答:產(chǎn)品說(shuō)不行,訂單RD說(shuō)他不保證消息不重復(fù),Kafka架構(gòu)RD也說(shuō)無(wú)法保證消息不重復(fù),那怎么辦?我是負(fù)責(zé)積分系統(tǒng)的,針對(duì)消息重復(fù)問(wèn)題,我會(huì)針對(duì)積分累計(jì)接口做**“冪等”**設(shè)計(jì),這個(gè)問(wèn)題,首先我們應(yīng)該從上游就做消息去重處理,但是我們不能100%相信上游系統(tǒng)一定可靠,我是消息消費(fèi)端,只有我這邊做了冪等設(shè)計(jì)才能完全避免這種和錢(qián)相關(guān)的bug,畢竟如果依賴(lài)上游,真的出了用戶(hù)消費(fèi)100元最后加了100w積分,這鍋重要還是我們背。
我可以根據(jù)用戶(hù)訂單號(hào)或者流水號(hào)做強(qiáng)冪等,每成功操作一次加積分就記錄下來(lái),即使消息重負(fù)了,我只要判斷同一個(gè)訂單號(hào)已經(jīng)操作加分了,后續(xù)我們就不會(huì)再做任何操作了。
隨手寫(xiě)了一段偽代碼給面試官:
//沒(méi)收到給用戶(hù)消費(fèi)通知,先判斷這個(gè)orderId時(shí)候已經(jīng)有加過(guò)積分的歷史記錄,如果沒(méi)有操作過(guò),則增加。如果已經(jīng)操作過(guò),直接返回不做任何處理。 List<UserPointHistory> lists = userPointDao.queryHistory(orderId); if(CollectionUtils.isNotEmpty(lists)){ //1.加100分。 userPoint.add(pointCount,orderId); //2.保存增加記錄 userPoint.addHistory(orderId); }else{ log.info("該訂單已經(jīng)操作過(guò)積分操作") return null; }
Tip:如果冪等還不明白可以看我寫(xiě)的《談?wù)勗趺蠢斫饨涌趦绲仍O(shè)計(jì),項(xiàng)目中如何保證接口冪等》,上面的代碼加積分和保存增加記錄要保證事務(wù)性,如果你不知道ACID千萬(wàn)別給自己挖坑,被面試官逮住ACID一頓問(wèn)。
面試官:這個(gè)問(wèn)題相對(duì)不難,有解決思路問(wèn)題就不大了。
2、面試官:
在多集群消息架構(gòu)中,如果消費(fèi)端要求接收到的消息是有序的,怎么解決消息順序消費(fèi)問(wèn)題?
問(wèn)題分析:這個(gè)問(wèn)題什么意思呢,比如一個(gè)消息Producer發(fā)送順序是1 2 3,那Consumer接收到的消息也是 1 2 3 ,這就比較為難工程師了,但是還是有辦法的,想要實(shí)現(xiàn)消息有序就要犧牲點(diǎn)什么東西 ---- 性能/可靠性。
答:這個(gè)問(wèn)題從三個(gè)角度考慮:
Producer:讓生產(chǎn)端同步發(fā)送消息,消息1確定發(fā)送成功后再發(fā)送消息2,不能異步,保證消息順序入隊(duì)。
服務(wù)端:Producer -> MQ Server -> Consumer 一對(duì)一關(guān)系,一對(duì)一服務(wù),這肯定能保證消息是按照順序消費(fèi)的,那么問(wèn)題來(lái)了:
- Producer -> MQ Server -> Consumer任意一個(gè)環(huán)節(jié)出現(xiàn)問(wèn)題,那肯定整個(gè)鏈路都阻塞了。
- 單通道模型性能成為瓶頸。
topic不分區(qū):意思就是讓同一個(gè)topic主題都入一個(gè)隊(duì)列,在分布式環(huán)境下如果同一個(gè)topic進(jìn)入多個(gè)分區(qū),那多個(gè)分區(qū)之間肯定無(wú)法保證消息順序了。
Consumer:保證消費(fèi)端是串行消費(fèi),禁止使用多線程。
但是這些方法都會(huì)犧牲掉系統(tǒng)的性能和穩(wěn)定性,順序性問(wèn)題非要使用MQ來(lái)做,那也沒(méi)有太好的辦法了。
3、面試官:
那如何做到topic不分區(qū),能舉例說(shuō)明一下嗎?
問(wèn)題分析:說(shuō)真的,工作中要求消息順序消費(fèi)的業(yè)務(wù)場(chǎng)景真的挺少見(jiàn)的,用到的時(shí)候少,你可以不用深入研究這一塊,知道方法就行,到時(shí)候真的遇到了知道從哪個(gè)方向下手。
答:用當(dāng)前比較流行的RocketMQ和Kafka舉例。
RocketMQ
:RocketMQ提供了MessageQueueSelector隊(duì)列選擇機(jī)制,我們可以把 Topic 用Hash取模法,相同Topic的Hash值肯定是一樣的,讓同一個(gè) Topic 同一個(gè)隊(duì)列中,再使用同步發(fā)送,這樣就能保證消息在一個(gè)分區(qū)有序了。
Kafka
: Kafka可以把 max.in.flight.requests.per.connection 參數(shù)設(shè)置成1,這樣就可以保證同一個(gè)topic在同一個(gè)分區(qū)內(nèi)了。
Tip:Topic就是一個(gè)字符串,給同一類(lèi)消息取個(gè)名字加以區(qū)分,如:topic.com.xxx.order.orderId,大多數(shù)用戶(hù)都可以通過(guò)message key來(lái)定義,因?yàn)橥粋€(gè)key的message可以保證只發(fā)送到同一個(gè)partition,比如說(shuō)key是user id,table row id等等。
總結(jié)
關(guān)于消息重復(fù)和消息順序消費(fèi)問(wèn)題解決思路比較簡(jiǎn)單,都是一些小技巧,雖然內(nèi)容比較枯燥,但是我已經(jīng)盡力說(shuō)得通俗易懂。
如果用兩句話概括這一接的內(nèi)容:
如何保證消息重復(fù)問(wèn)題:消費(fèi)端接口冪等。如何保證消息順序消費(fèi)問(wèn)題:讓同一個(gè)消息不分區(qū),且單線程。
當(dāng)然面試的時(shí)候你可別這么干巴巴兩句話,那顯得你太沒(méi)水平了,面試最理想的效果就是無(wú)論多簡(jiǎn)單的問(wèn)題你都能滔滔不絕,讓面試官無(wú)話可說(shuō)。
以上就是分布式面試消息隊(duì)列解決消息重復(fù)保證消息順序的詳細(xì)內(nèi)容,更多關(guān)于消息隊(duì)列解決消息重復(fù)保證消息順序的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java使用jdbc連接數(shù)據(jù)庫(kù)簡(jiǎn)單實(shí)例
這篇文章主要為大家詳細(xì)介紹了java使用jdbc連接數(shù)據(jù)庫(kù)的簡(jiǎn)單實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07RestTemplate自定義請(qǐng)求失敗異常處理示例解析
這篇文章主要為大家介紹了RestTemplate自定義請(qǐng)求失敗異常處理的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03一篇文章教你用Java使用JVM工具檢測(cè)問(wèn)題
這篇文章主要介紹了深入理解Java使用JVM工具檢測(cè)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-09-09intellij idea修改maven配置時(shí)總是恢復(fù)默認(rèn)配置的解決方法idea版本(2020.2.x)
這篇文章主要介紹了intellij idea修改maven配置時(shí)總是恢復(fù)默認(rèn)配置的解決方法idea版本(2020.2.x),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08SpringBoot實(shí)現(xiàn)接口校驗(yàn)簽名調(diào)用的項(xiàng)目實(shí)踐
在以SpringBoot開(kāi)發(fā)后臺(tái)API接口時(shí),會(huì)存在哪些接口不安全的因素呢?通常如何去解決的呢?本文主要介紹了SpringBoot實(shí)現(xiàn)接口校驗(yàn)簽名調(diào)用的項(xiàng)目實(shí)踐,感興趣的可以了解一下2023-09-09IntelliJ IDEA 2020.2正式發(fā)布,兩點(diǎn)多多總能助你提效
這篇文章主要介紹了IntelliJ IDEA 2020.2正式發(fā)布,諸多亮點(diǎn)總有幾款能助你提效,本文通過(guò)圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-07-07Java類(lèi)庫(kù)BeanUtils組件使用方法及實(shí)例詳解
這篇文章主要介紹了Java類(lèi)庫(kù)BeanUtils組件使用方法級(jí)實(shí)例詳解,需要的朋友可以參考下2020-02-02Java中實(shí)現(xiàn)線程間通信的實(shí)例教程
線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號(hào),另一方面線程通信使線程能夠等待其他線程的信號(hào),這篇文章主要給大家介紹了關(guān)于Java中實(shí)現(xiàn)線程間通信的相關(guān)資料,本文通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09