金三銀四已經(jīng)快結(jié)束了,有的朋友發(fā)來(lái)喜報(bào),面試上了一線(xiàn)大廠(chǎng),也有沒(méi)有面試上的朋友跑來(lái)跟我說(shuō),被虐慘了,幾天給大家分享下我一個(gè)面試京東的朋友的經(jīng)歷,希望給正在面試的朋友共勉。
面試京東被問(wèn)到的問(wèn)題:
- 如何保證消息不被重復(fù)消費(fèi)?或者說(shuō),如何保證消息消費(fèi)的冪等性?
- 如何保證 redis 的高并發(fā)和高可用?redis 的主從復(fù)制原理能介紹一下么?redis 的哨兵原理能介紹一下么?
- 如何設(shè)計(jì)一個(gè)高并發(fā)系統(tǒng)?
- 分布式服務(wù)接口的冪等性如何設(shè)計(jì)(比如不能重復(fù)扣款)?
記得面試中問(wèn)到的這些面試真題,文中會(huì)幫大家解析這些面試真題,同時(shí)幫大家整理了有spring,mysql,jvm,Redis,java基礎(chǔ),springboot,springMVC等專(zhuān)題的面試真題合集,有需要的朋友可以關(guān)注小編+轉(zhuǎn)發(fā)文章+私信【面試真題】
如何保證消息不被重復(fù)消費(fèi)?或者說(shuō),如何保證消息消費(fèi)的冪等性?
面試官心理分析:
其實(shí)這是很常見(jiàn)的一個(gè)問(wèn)題,這倆問(wèn)題基本可以連起來(lái)問(wèn)。既然是消費(fèi)消息,那肯定要考慮會(huì)不會(huì)重復(fù)消費(fèi)?能不能避免重復(fù)消費(fèi)?或者重復(fù)消費(fèi)了也別造成系統(tǒng)異常可以嗎?這個(gè)是 MQ 領(lǐng)域的基本問(wèn)題,其實(shí)本質(zhì)上還是問(wèn)你使用消息隊(duì)列如何保證冪等性,這個(gè)是你架構(gòu)里要考慮的一個(gè)問(wèn)題。
面試題剖析:
回答這個(gè)問(wèn)題,首先你別聽(tīng)到重復(fù)消息這個(gè)事兒,就一無(wú)所知吧,你先大概說(shuō)一說(shuō)可能會(huì)有哪些重復(fù)消費(fèi)的問(wèn)題。
首先,比如 RabbitMQ、RocketMQ、Kafka,都有可能會(huì)出現(xiàn)消息重復(fù)消費(fèi)的問(wèn)題,正常。因?yàn)檫@問(wèn)題通常不是 MQ 自己保證的,是由我們開(kāi)發(fā)來(lái)保證的。挑一個(gè) Kafka 來(lái)舉個(gè)例子,說(shuō)說(shuō)怎么重復(fù)消費(fèi)吧。
Kafka 實(shí)際上有個(gè) offset 的概念,就是每個(gè)消息寫(xiě)進(jìn)去,都有一個(gè) offset,代表消息的序號(hào),然后consumer 消費(fèi)了數(shù)據(jù)之后,每隔一段時(shí)間(定時(shí)定期),會(huì)把自己消費(fèi)過(guò)的消息的 offset 提交一下,表示“我已經(jīng)消費(fèi)過(guò)了,下次我要是重啟啥的,你就讓我繼續(xù)從上次消費(fèi)到的 offset 來(lái)繼續(xù)消費(fèi)吧”。
但是凡事總有意外,比如我們之前生產(chǎn)經(jīng)常遇到的,就是你有時(shí)候重啟系統(tǒng),看你怎么重啟了,如果碰到點(diǎn)著急的,直接 kill 進(jìn)程了,再重啟。這會(huì)導(dǎo)致 consumer 有些消息處理了,但是沒(méi)來(lái)得及提交 offset,尷尬了。重啟之后,少數(shù)消息會(huì)再次消費(fèi)一次。
舉個(gè)栗子。
有這么個(gè)場(chǎng)景。數(shù)據(jù) 1/2/3 依次進(jìn)入 kafka,kafka 會(huì)給這三條數(shù)據(jù)每條分配一個(gè) offset,代表這條數(shù)據(jù)的序號(hào),我們就假設(shè)分配的 offset 依次是 152/153/154。消費(fèi)者從 kafka 去消費(fèi)的時(shí)候,也是按照這個(gè)順序去消費(fèi)。假如當(dāng)消費(fèi)者消費(fèi)了 offset=153 的這條數(shù)據(jù),剛準(zhǔn)備去提交 offset 到 zookeeper,此時(shí)消費(fèi)者進(jìn)程被重啟了。那么此時(shí)消費(fèi)過(guò)的數(shù)據(jù) 1/2 的 offset 并沒(méi)有提交,kafka 也就不知道你已經(jīng)消費(fèi)了 offset=153 這條數(shù)據(jù)。那么重啟之后,消費(fèi)者會(huì)找 kafka 說(shuō),嘿,哥兒們,你給我接著把上次我消費(fèi)到的那個(gè)地方后面的數(shù)據(jù)繼續(xù)給我傳遞過(guò)來(lái)。由于之前的 offset 沒(méi)有提交成功,那么數(shù)據(jù) 1/2 會(huì)再次傳過(guò)來(lái),如果此時(shí)消費(fèi)者沒(méi)有去重的話(huà),那么就會(huì)導(dǎo)致重復(fù)消費(fèi)。
如果消費(fèi)者干的事兒是拿一條數(shù)據(jù)就往數(shù)據(jù)庫(kù)里寫(xiě)一條,會(huì)導(dǎo)致說(shuō),你可能就把數(shù)據(jù) 1/2 在數(shù)據(jù)庫(kù)里插入了 2 次,那么數(shù)據(jù)就錯(cuò)啦。
其實(shí)重復(fù)消費(fèi)不可怕,可怕的是你沒(méi)考慮到重復(fù)消費(fèi)之后,怎么保證冪等性。
舉個(gè)例子吧。假設(shè)你有個(gè)系統(tǒng),消費(fèi)一條消息就往數(shù)據(jù)庫(kù)里插入一條數(shù)據(jù),要是你一個(gè)消息重復(fù)兩次,你不就插入了兩條,這數(shù)據(jù)不就錯(cuò)了?但是你要是消費(fèi)到第二次的時(shí)候,自己判斷一下是否已經(jīng)消費(fèi)過(guò)了,若是就直接扔了,這樣不就保留了一條數(shù)據(jù),從而保證了數(shù)據(jù)的正確性。
一條數(shù)據(jù)重復(fù)出現(xiàn)兩次,數(shù)據(jù)庫(kù)里就只有一條數(shù)據(jù),這就保證了系統(tǒng)的冪等性。
冪等性,通俗點(diǎn)說(shuō),就一個(gè)數(shù)據(jù),或者一個(gè)請(qǐng)求,給你重復(fù)來(lái)多次,你得確保對(duì)應(yīng)的數(shù)據(jù)是不會(huì)改變的,不能出錯(cuò)。
所以第二個(gè)問(wèn)題來(lái)了,怎么保證消息隊(duì)列消費(fèi)的冪等性?
其實(shí)還是得結(jié)合業(yè)務(wù)來(lái)思考,我這里給幾個(gè)思路:
- 比如你拿個(gè)數(shù)據(jù)要寫(xiě)庫(kù),你先根據(jù)主鍵查一下,如果這數(shù)據(jù)都有了,你就別插入了,update 一下好吧。
- 比如你是寫(xiě) Redis,那沒(méi)問(wèn)題了,反正每次都是 set,天然冪等性。
- 比如你不是上面兩個(gè)場(chǎng)景,那做的稍微復(fù)雜一點(diǎn),你需要讓生產(chǎn)者發(fā)送每條數(shù)據(jù)的時(shí)候,里面加一個(gè)全局唯一的 id,類(lèi)似訂單 id 之類(lèi)的東西,然后你這里消費(fèi)到了之后,先根據(jù)這個(gè) id 去比如 Redis 里查一下,之前消費(fèi)過(guò)嗎?如果沒(méi)有消費(fèi)過(guò),你就處理,然后這個(gè) id 寫(xiě) Redis。如果消費(fèi)過(guò)了,那你就別處理了,保證別重復(fù)處理相同的消息即可。
- 比如基于數(shù)據(jù)庫(kù)的唯一鍵來(lái)保證重復(fù)數(shù)據(jù)不會(huì)重復(fù)插入多條。因?yàn)橛形ㄒ绘I約束了,重復(fù)數(shù)據(jù)插入只會(huì)報(bào)錯(cuò),不會(huì)導(dǎo)致數(shù)據(jù)庫(kù)中出現(xiàn)臟數(shù)據(jù)。
當(dāng)然,如何保證 MQ 的消費(fèi)是冪等性的,需要結(jié)合具體的業(yè)務(wù)來(lái)看。
如何保證 redis 的高并發(fā)和高可用?redis 的主從復(fù)制原理能介紹一下么?redis 的哨兵原理能介紹一下么?
面試官心理分析:
其實(shí)問(wèn)這個(gè)問(wèn)題,主要是考考你,redis 單機(jī)能承載多高并發(fā)?如果單機(jī)扛不住如何擴(kuò)容扛更多的并發(fā)?redis 會(huì)不會(huì)掛?既然 redis 會(huì)掛那怎么保證 redis 是高可用的?
其實(shí)針對(duì)的都是項(xiàng)目中你肯定要考慮的一些問(wèn)題,如果你沒(méi)考慮過(guò),那確實(shí)你對(duì)生產(chǎn)系統(tǒng)中的問(wèn)題思考太少。
面試題剖析:
如果你用 redis 緩存技術(shù)的話(huà),肯定要考慮如何用 redis 來(lái)加多臺(tái)機(jī)器,保證 redis 是高并發(fā)的,還有就是如何讓 redis 保證自己不是掛掉以后就直接死掉了,即 redis 高可用。
由于此節(jié)內(nèi)容較多,因此,會(huì)分為兩個(gè)小節(jié)進(jìn)行講解。 - redis 主從架構(gòu) - redis 基于哨兵實(shí)現(xiàn)高可用
redis 實(shí)現(xiàn)高并發(fā)主要依靠主從架構(gòu),一主多從,一般來(lái)說(shuō),很多項(xiàng)目其實(shí)就足夠了,單主用來(lái)寫(xiě)入數(shù)據(jù),單機(jī)幾萬(wàn) QPS,多從用來(lái)查詢(xún)數(shù)據(jù),多個(gè)從實(shí)例可以提供每秒 10w 的 QPS。
如果想要在實(shí)現(xiàn)高并發(fā)的同時(shí),容納大量的數(shù)據(jù),那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒幾十萬(wàn)的讀寫(xiě)并發(fā)。
redis 高可用,如果是做主從架構(gòu)部署,那么加上哨兵就可以了,就可以實(shí)現(xiàn),任何一個(gè)實(shí)例宕機(jī),可以進(jìn)行主備切換
如何設(shè)計(jì)一個(gè)高并發(fā)系統(tǒng)?
面試官心理分析:
說(shuō)實(shí)話(huà),如果面試官問(wèn)你這個(gè)題目,那么你必須要使出全身吃奶勁了。為啥?因?yàn)槟銢](méi)看到現(xiàn)在很多公司招聘的 JD 里都是說(shuō)啥,有高并發(fā)就經(jīng)驗(yàn)者優(yōu)先。
如果你確實(shí)有真才實(shí)學(xué),在互聯(lián)網(wǎng)公司里干過(guò)高并發(fā)系統(tǒng),那你確實(shí)拿 offer 基本如探囊取物,沒(méi)啥問(wèn)題。面試官也絕對(duì)不會(huì)這樣來(lái)問(wèn)你,否則他就是蠢。
假設(shè)你在某知名電商公司干過(guò)高并發(fā)系統(tǒng),用戶(hù)上億,一天流量幾十億,高峰期并發(fā)量上萬(wàn),甚至是十萬(wàn)。那么人家一定會(huì)仔細(xì)盤(pán)問(wèn)你的系統(tǒng)架構(gòu),你們系統(tǒng)啥架構(gòu)?怎么部署的?部署了多少臺(tái)機(jī)器?緩存咋用的?MQ 咋用的?數(shù)據(jù)庫(kù)咋用的?就是深挖你到底是如何扛住高并發(fā)的。
因?yàn)檎嬲蛇^(guò)高并發(fā)的人一定知道,脫離了業(yè)務(wù)的系統(tǒng)架構(gòu)都是在紙上談兵,真正在復(fù)雜業(yè)務(wù)場(chǎng)景而且還高并發(fā)的時(shí)候,那系統(tǒng)架構(gòu)一定不是那么簡(jiǎn)單的,用個(gè) redis,用 mq 就能搞定?當(dāng)然不是,真實(shí)的系統(tǒng)架構(gòu)搭配上業(yè)務(wù)之后,會(huì)比這種簡(jiǎn)單的所謂“高并發(fā)架構(gòu)”要復(fù)雜很多倍。
如果有面試官問(wèn)你個(gè)問(wèn)題說(shuō),如何設(shè)計(jì)一個(gè)高并發(fā)系統(tǒng)?那么不好意思,一定是因?yàn)槟銓?shí)際上沒(méi)干過(guò)高并發(fā)系統(tǒng)。面試官看你簡(jiǎn)歷就沒(méi)啥出彩的,感覺(jué)就不咋地,所以就會(huì)問(wèn)問(wèn)你,如何設(shè)計(jì)一個(gè)高并發(fā)系統(tǒng)?其實(shí)說(shuō)白了本質(zhì)就是看看你有沒(méi)有自己研究過(guò),有沒(méi)有一定的知識(shí)積累。
系統(tǒng)拆分
將一個(gè)系統(tǒng)拆分為多個(gè)子系統(tǒng),用 dubbo 來(lái)搞。然后每個(gè)系統(tǒng)連一個(gè)數(shù)據(jù)庫(kù),這樣本來(lái)就一個(gè)庫(kù),現(xiàn)在多個(gè)數(shù)據(jù)庫(kù),不也可以扛高并發(fā)么。
緩存
緩存,必須得用緩存。大部分的高并發(fā)場(chǎng)景,都是讀多寫(xiě)少,那你完全可以在數(shù)據(jù)庫(kù)和緩存里都寫(xiě)一份,然后讀的時(shí)候大量走緩存不就得了。畢竟人家 redis 輕輕松松單機(jī)幾萬(wàn)的并發(fā)。所以你可以考慮考慮你的項(xiàng)目里,那些承載主要請(qǐng)求的讀場(chǎng)景,怎么用緩存來(lái)抗高并發(fā)。
MQ
MQ,必須得用 MQ??赡苣氵€是會(huì)出現(xiàn)高并發(fā)寫(xiě)的場(chǎng)景,比如說(shuō)一個(gè)業(yè)務(wù)操作里要頻繁搞數(shù)據(jù)庫(kù)幾十次,增刪改增刪改,瘋了。那高并發(fā)絕對(duì)搞掛你的系統(tǒng),你要是用 redis 來(lái)承載寫(xiě)那肯定不行,人家是緩存,數(shù)據(jù)隨時(shí)就被 LRU 了,數(shù)據(jù)格式還無(wú)比簡(jiǎn)單,沒(méi)有事務(wù)支持。所以該用 mysql 還得用 mysql 啊。那你咋辦?用 MQ 吧,大量的寫(xiě)請(qǐng)求灌入 MQ 里,排隊(duì)慢慢玩兒,后邊系統(tǒng)消費(fèi)后慢慢寫(xiě),控制在 mysql 承載范圍之內(nèi)。所以你得考慮考慮你的項(xiàng)目里,那些承載復(fù)雜寫(xiě)業(yè)務(wù)邏輯的場(chǎng)景里,如何用 MQ 來(lái)異步寫(xiě),提升并發(fā)性。MQ 單機(jī)抗幾萬(wàn)并發(fā)也是 ok 的,這個(gè)之前還特意說(shuō)過(guò)。
分庫(kù)分表
分庫(kù)分表,可能到了最后數(shù)據(jù)庫(kù)層面還是免不了抗高并發(fā)的要求,好吧,那么就將一個(gè)數(shù)據(jù)庫(kù)拆分為多個(gè)庫(kù),多個(gè)庫(kù)來(lái)扛更高的并發(fā);然后將一個(gè)表拆分為多個(gè)表,每個(gè)表的數(shù)據(jù)量保持少一點(diǎn),提高 sql 跑的性能。
讀寫(xiě)分離
讀寫(xiě)分離,這個(gè)就是說(shuō)大部分時(shí)候數(shù)據(jù)庫(kù)可能也是讀多寫(xiě)少,沒(méi)必要所有請(qǐng)求都集中在一個(gè)庫(kù)上吧,可以搞個(gè)主從架構(gòu),主庫(kù)寫(xiě)入,從庫(kù)讀取,搞一個(gè)讀寫(xiě)分離。讀流量太多的時(shí)候,還可以加更多的從庫(kù)。
ElasticSearch
Elasticsearch,簡(jiǎn)稱(chēng) es。es 是分布式的,可以隨便擴(kuò)容,分布式天然就可以支撐高并發(fā),因?yàn)閯?dòng)不動(dòng)就可以擴(kuò)容加機(jī)器來(lái)扛更高的并發(fā)。那么一些比較簡(jiǎn)單的查詢(xún)、統(tǒng)計(jì)類(lèi)的操作,可以考慮用 es 來(lái)承載,還有一些全文搜索類(lèi)的操作,也可以考慮用 es 來(lái)承載。
上面的 6 點(diǎn),基本就是高并發(fā)系統(tǒng)肯定要干的一些事兒,大家可以仔細(xì)結(jié)合之前講過(guò)的知識(shí)考慮一下,到時(shí)候你可以系統(tǒng)的把這塊闡述一下,然后每個(gè)部分要注意哪些問(wèn)題,之前都講過(guò)了,你都可以闡述闡述,表明你對(duì)這塊是有點(diǎn)積累的。
說(shuō)句實(shí)話(huà),畢竟你真正厲害的一點(diǎn),不是在于弄明白一些技術(shù),或者大概知道一個(gè)高并發(fā)系統(tǒng)應(yīng)該長(zhǎng)什么樣?其實(shí)實(shí)際上在真正的復(fù)雜的業(yè)務(wù)系統(tǒng)里,做高并發(fā)要遠(yuǎn)遠(yuǎn)比上面提到的點(diǎn)要復(fù)雜幾十倍到上百倍。你需要考慮:哪些需要分庫(kù)分表,哪些不需要分庫(kù)分表,單庫(kù)單表跟分庫(kù)分表如何 join,哪些數(shù)據(jù)要放到緩存里去,放哪些數(shù)據(jù)才可以扛住高并發(fā)的請(qǐng)求,你需要完成對(duì)一個(gè)復(fù)雜業(yè)務(wù)系統(tǒng)的分析之后,然后逐步逐步的加入高并發(fā)的系統(tǒng)架構(gòu)的改造,這個(gè)過(guò)程是無(wú)比復(fù)雜的,一旦做過(guò)一次,并且做好了,你在這個(gè)市場(chǎng)上就會(huì)非常的吃香。
其實(shí)大部分公司,真正看重的,不是說(shuō)你掌握高并發(fā)相關(guān)的一些基本的架構(gòu)知識(shí),架構(gòu)中的一些技術(shù),RocketMQ、Kafka、Redis、Elasticsearch,高并發(fā)這一塊,你了解了,也只能是次一等的人才。對(duì)一個(gè)有幾十萬(wàn)行代碼的復(fù)雜的分布式系統(tǒng),一步一步架構(gòu)、設(shè)計(jì)以及實(shí)踐過(guò)高并發(fā)架構(gòu)的人,這個(gè)經(jīng)驗(yàn)是難能可貴的。
分布式服務(wù)接口的冪等性如何設(shè)計(jì)(比如不能重復(fù)扣款)?
面試官心理分析:
從這個(gè)問(wèn)題開(kāi)始,面試官就已經(jīng)進(jìn)入了實(shí)際的生產(chǎn)問(wèn)題的面試了。
一個(gè)分布式系統(tǒng)中的某個(gè)接口,該如何保證冪等性?這個(gè)事兒其實(shí)是你做分布式系統(tǒng)的時(shí)候必須要考慮的一個(gè)生產(chǎn)環(huán)境的技術(shù)問(wèn)題。啥意思呢?
你看,假如你有個(gè)服務(wù)提供一些接口供外部調(diào)用,這個(gè)服務(wù)部署在了 5 臺(tái)機(jī)器上,接著有個(gè)接口就是付款接口。然后人家用戶(hù)在前端上操作的時(shí)候,不知道為啥,總之就是一個(gè)訂單不小心發(fā)起了兩次支付請(qǐng)求,然后這倆請(qǐng)求分散在了這個(gè)服務(wù)部署的不同的機(jī)器上,好了,結(jié)果一個(gè)訂單扣款扣兩次。
或者是訂單系統(tǒng)調(diào)用支付系統(tǒng)進(jìn)行支付,結(jié)果不小心因?yàn)?strong>網(wǎng)絡(luò)超時(shí)了,然后訂單系統(tǒng)走了前面我們看到的那個(gè)重試機(jī)制,咔嚓給你重試了一把,好,支付系統(tǒng)收到一個(gè)支付請(qǐng)求兩次,而且因?yàn)樨?fù)載均衡算法落在了不同的機(jī)器上,尷尬了。。。
所以你肯定得知道這事兒,否則你做出來(lái)的分布式系統(tǒng)恐怕容易埋坑。
面試題剖析:
這個(gè)不是技術(shù)問(wèn)題,這個(gè)沒(méi)有通用的一個(gè)方法,這個(gè)應(yīng)該結(jié)合業(yè)務(wù)來(lái)保證冪等性。
所謂冪等性,就是說(shuō)一個(gè)接口,多次發(fā)起同一個(gè)請(qǐng)求,你這個(gè)接口得保證結(jié)果是準(zhǔn)確的,比如不能多扣款、不能多插入一條數(shù)據(jù)、不能將統(tǒng)計(jì)值多加了 1。這就是冪等性。
其實(shí)保證冪等性主要是三點(diǎn): - 對(duì)于每個(gè)請(qǐng)求必須有一個(gè)唯一的標(biāo)識(shí),舉個(gè)栗子:訂單支付請(qǐng)求,肯定得包含訂單 id,一個(gè)訂單 id 最多支付一次,對(duì)吧。 - 每次處理完請(qǐng)求之后,必須有一個(gè)記錄標(biāo)識(shí)這個(gè)請(qǐng)求處理過(guò)了。常見(jiàn)的方案是在 mysql 中記錄個(gè)狀態(tài)啥的,比如支付之前記錄一條這個(gè)訂單的支付流水。 - 每次接收請(qǐng)求需要進(jìn)行判斷,判斷之前是否處理過(guò)。比如說(shuō),如果有一個(gè)訂單已經(jīng)支付了,就已經(jīng)有了一條支付流水,那么如果重復(fù)發(fā)送這個(gè)請(qǐng)求,則此時(shí)先插入支付流水,orderId 已經(jīng)存在了,唯一鍵約束生效,報(bào)錯(cuò)插入不進(jìn)去的。然后你就不用再扣款了。
實(shí)際運(yùn)作過(guò)程中,你要結(jié)合自己的業(yè)務(wù)來(lái),比如說(shuō)利用 redis,用 orderId 作為唯一鍵。只有成功插入這個(gè)支付流水,才可以執(zhí)行實(shí)際的支付扣款。
要求是支付一個(gè)訂單,必須插入一條支付流水,order_id 建一個(gè)唯一鍵 unique key。你在支付一個(gè)訂單之前,先插入一條支付流水,order_id 就已經(jīng)進(jìn)去了。你就可以寫(xiě)一個(gè)標(biāo)識(shí)到 redis 里面去,setorder_id payed,下一次重復(fù)請(qǐng)求過(guò)來(lái)了,先查 redis 的 order_id 對(duì)應(yīng)的 value,如果是 payed 就說(shuō)明已經(jīng)支付過(guò)了,你就別重復(fù)支付了。
總結(jié):
大廠(chǎng)的面試從來(lái)不是一成不變的,需要有扎實(shí)的基礎(chǔ),對(duì)底層原理的深入理解,還有豐富的項(xiàng)目實(shí)戰(zhàn)經(jīng)驗(yàn),所以面試大廠(chǎng)前一定要做好充足的準(zhǔn)備,今天給大家分析的這幾道面試真題,希望能夠給大家一定的幫助,有在面試大廠(chǎng)的朋友,我在這里給大家準(zhǔn)備了有將近2000道的面試真題詳解,包含一線(xiàn)互聯(lián)網(wǎng)絕大部分的知識(shí)點(diǎn),突擊面試有需要的朋友可以關(guān)注小編+轉(zhuǎn)發(fā)文章+私信【面試真題】獲取。