您的位置:首頁(yè) > 軟件教程 > 教程 > 這就叫“面試造火箭,工作擰螺絲!”

這就叫“面試造火箭,工作擰螺絲!”

來(lái)源:好特整理 | 時(shí)間:2024-06-24 15:49:23 | 閱讀:91 |  標(biāo)簽: 擰螺絲 面試 火箭 工作   | 分享到:

你好呀,我是歪歪。 我想再討論一下上次的這篇文章《哎,被這個(gè)叫做at least once的玩意坑麻了》 因?yàn)橛行┡笥芽赐曛笤僭u(píng)論區(qū)給出了自己的思考,也有朋友和我私聊,分享了自己的看法,我覺(jué)得有些想法很好,所以我決定一魚(yú)兩吃,再聊聊這個(gè)問(wèn)題。 假設(shè),我們是一場(chǎng)面試,面試官給你拋出了這樣一個(gè)問(wèn)題:

你好呀,我是歪歪。

我想再討論一下上次的這篇文章 《哎,被這個(gè)叫做at least once的玩意坑麻了》

因?yàn)橛行┡笥芽赐曛笤僭u(píng)論區(qū)給出了自己的思考,也有朋友和我私聊,分享了自己的看法,我覺(jué)得有些想法很好,所以我決定一魚(yú)兩吃,再聊聊這個(gè)問(wèn)題。

假設(shè),我們是一場(chǎng)面試,面試官給你拋出了這樣一個(gè)問(wèn)題:

如果一個(gè)消費(fèi)隊(duì)列由于某些原因,對(duì)于某個(gè)消息發(fā)起了兩次。導(dǎo)致一樣的數(shù)據(jù)落庫(kù)兩條,請(qǐng)問(wèn)你會(huì)怎么處理這個(gè)問(wèn)題?

這題你一拿到手上,應(yīng)該就立馬能分析出是在問(wèn)如何實(shí)現(xiàn)一個(gè)冪等機(jī)制。

想著這玩意我熟啊,張口就能給出方案:

業(yè)務(wù)消息?=?select(業(yè)務(wù)唯一流水號(hào));
if(業(yè)務(wù)消息?==?null){
????save(業(yè)務(wù)消息);
}

面試官一聽(tīng),提示道:你這個(gè)方案在多線程的情況下會(huì)不會(huì)有什么問(wèn)題呢?

于是你的小腦瓜子立刻開(kāi)始轉(zhuǎn)了起來(lái):先查詢,再判斷,最后保存。

如果兩個(gè)線程同時(shí)過(guò)來(lái),都查不到數(shù)據(jù),那么就能都走到保存的邏輯里面去,確實(shí)攔不住。

于是你扣了一下腦殼,想起了你上家公司針對(duì)這個(gè)問(wèn)題,就是在數(shù)據(jù)庫(kù)的表結(jié)構(gòu)里面,對(duì)業(yè)務(wù)唯一流水號(hào)做了唯一索引,所以不會(huì)出現(xiàn)重復(fù)插入的情況。

然后你給出了“加唯一索引”的方案,準(zhǔn)備絕殺這個(gè)問(wèn)題。

沒(méi)想到面試官非常不懂事,還在繼續(xù)追問(wèn):我想盡量不要讓程序拋出異常,還有沒(méi)有其他的方案呢?

Redis

你抱著自己的左手,邊啃指甲邊思考:唯一索引是數(shù)據(jù)庫(kù)幫我們保證的邏輯,現(xiàn)在面試官這個(gè)老登不想讓我用數(shù)據(jù)庫(kù)來(lái)做這件事情。那就必須要控制在并發(fā)的場(chǎng)景下,只有一個(gè)請(qǐng)求能抵達(dá)數(shù)據(jù)庫(kù)。

這就叫“面試造火箭,工作擰螺絲!”

鎖!這不就是鎖干的事兒?jiǎn)幔?

于是你飛快的又想到了一個(gè)方法:

flag?=?redis(業(yè)務(wù)唯一流水號(hào),過(guò)期時(shí)間);
if(flag){
????save(業(yè)務(wù)消息);????
}

可以利用業(yè)務(wù)唯一流水號(hào)結(jié)合 Redis 來(lái)做一個(gè)鎖,加鎖成功的請(qǐng)求才能走到 save 邏輯中。

這樣就能解決并發(fā)場(chǎng)景下,多個(gè)請(qǐng)求穿透到 save 邏輯這一步的問(wèn)題。

面試官聽(tīng)到你這個(gè)方案之后,立馬就啟動(dòng)了追問(wèn)技能:如果放 Redis 成功了,但是還沒(méi)來(lái)得及 save,服務(wù)重啟了。

這個(gè)請(qǐng)求理論上是應(yīng)該能再次發(fā)起的,但是由于 Redis 鎖的存在,導(dǎo)致不會(huì)走到 save 的邏輯去,怎么辦呢?

于是你又扣了一下腦殼,想起你在上家公司的時(shí)候,好像也遇到過(guò)這個(gè)情況。

當(dāng)時(shí)的解決方案就是人工介入,分析了一波數(shù)據(jù),確認(rèn)了這個(gè)消息確實(shí)應(yīng)該被繼續(xù)處理,于是你找 DBA 幫忙刪除了 Redis 對(duì)應(yīng)的 key,流程就通了。

然而這個(gè)回答面試官并不滿意:人工就顯得不優(yōu)雅了,要不再想想?

你又抱著自己的右手,邊啃指甲邊思考:這個(gè)老登考慮的確實(shí)挺多的,感覺(jué)應(yīng)該在一個(gè)很厲害的團(tuán)隊(duì),我得加把勁兒,再想想。

現(xiàn)在要人工介入的原因,是因?yàn)槲覀儼训诙蔚恼?qǐng)求攔截住并丟棄了。

如果不丟棄,那么理論上在“過(guò)期時(shí)間”到了,鎖被釋放后,第二次的請(qǐng)求拿到鎖,就能接著往下走。

所以,這里需要在 Redis 這里加一個(gè)加鎖失敗則等待的邏輯:

flag?=?redis(業(yè)務(wù)唯一流水號(hào),過(guò)期時(shí)間,獲取不到則等待);
if(flag){
????save(業(yè)務(wù)消息);????
}

但是你一看這個(gè)邏輯又不對(duì)了:由于有鎖等待的邏輯,那么如果兩個(gè)請(qǐng)求過(guò)來(lái),還是有可能會(huì)都放入到 Redis 里面,flag 都會(huì)為 true,那么 save 方法還是會(huì)走兩遍。

所以,還得在獲取鎖成功之后加上一個(gè)查詢數(shù)據(jù)庫(kù)的邏輯:

flag?=?redis(業(yè)務(wù)唯一流水號(hào),過(guò)期時(shí)間,獲取不到則等待);
if(flag){
????業(yè)務(wù)消息?=?select(業(yè)務(wù)唯一流水號(hào));
????if(業(yè)務(wù)消息?==?null){
????????save(業(yè)務(wù)消息);????
????}
}else{
????//等待結(jié)束后還是未獲取到鎖,發(fā)送預(yù)警
????monitor(預(yù)警信息);
}

第一層的 Redis 相當(dāng)于讓請(qǐng)求排隊(duì),確保只有一個(gè)請(qǐng)求進(jìn)來(lái)。

第二層的 select 才是真正的防止重復(fù)的業(yè)務(wù)邏輯。

同時(shí),如果等待結(jié)束后還是未獲取到鎖,出現(xiàn)這種低概率情況,就預(yù)警出來(lái),人工兜底嘛,一旦人工介入,那就是能解決任何問(wèn)題。

你心想這波應(yīng)該是穩(wěn)了,應(yīng)該是可以換題了。

然而面試官并不打算在這個(gè)回合上輕易放過(guò)你:這個(gè)方案確實(shí)是可以解決這個(gè)問(wèn)題,但是在技術(shù)實(shí)現(xiàn)上引入了 Redis 框架,如果我不使用 Redis,單純的靠 MySQL 呢?

回到 MySQL

聽(tīng)到這個(gè)問(wèn)題的時(shí)候你覺(jué)得不對(duì)啊,最開(kāi)始的時(shí)候不就是說(shuō)了“加唯一索引”就可以解決這個(gè)問(wèn)題嗎?

于是面試官補(bǔ)充了一下描述:

最開(kāi)始的加唯一索引是基于業(yè)務(wù)表來(lái)做的,如果出現(xiàn)問(wèn)題就讓其拋出主鍵沖突異常,這個(gè)方案確實(shí)是可以實(shí)現(xiàn)需求。但是我現(xiàn)在想讓你給我設(shè)計(jì)一個(gè)通用的技術(shù)組件,不需要基于某個(gè)具體的業(yè)務(wù)場(chǎng)景去設(shè)計(jì)。我想聽(tīng)聽(tīng)你的思路。

拿到新的題目,你開(kāi)始覺(jué)得這是***難,看著面試官求知的眼神,你又開(kāi)始懷疑:這個(gè)老登不會(huì)是來(lái)套方案的吧?

看著自己已經(jīng)被咬禿了的左右大拇指指甲,感覺(jué)自己的靈感和指甲一樣都光禿禿的。

開(kāi)始后悔前面幾個(gè)回合咬得太快了,原以為可以秒殺這個(gè)面試,沒(méi)想到面試官還在纏斗。你動(dòng)了使用必殺技來(lái)結(jié)束戰(zhàn)斗的念想。

于是從帽子的縫隙中插進(jìn)入一根指甲已經(jīng)禿了的手指,在差不多禿了的頭頂,用指腹畫(huà)圈,給自己頭皮按摩,醫(yī)生說(shuō)這樣的有助于毛囊發(fā)育,你想著頭發(fā)還會(huì)長(zhǎng)出來(lái),就思如泉涌,這就是必殺技。

這就叫“面試造火箭,工作擰螺絲!”

你陷入了思考,Redis 在前面的方案中是為了防止有多條數(shù)據(jù)穿透到 save 方法中去,如果不讓用 Redis。MySQL 怎么實(shí)現(xiàn)類(lèi)似的效果呢?

也加鎖嗎?for update?

業(yè)務(wù)消息?=?select(業(yè)務(wù)唯一流水號(hào));//select?***?for?update
if(業(yè)務(wù)消息?==?null){
????save(業(yè)務(wù)消息);????
}

這玩意一看上去就是性能就拉胯了,為了解決這個(gè)偶發(fā)的問(wèn)題,犧牲了接口的性能,這個(gè)路線就走的有點(diǎn)遠(yuǎn)了。

而且這個(gè)上鎖的邏輯隱藏的有點(diǎn)深,容易留下后患,面試官肯定不會(huì)滿意的。

那還有什么辦法,能把 MySQL 當(dāng)作鎖來(lái)用,確保并發(fā)情況下只有一個(gè)請(qǐng)求能穿過(guò)這個(gè)鎖呢?

那還是得靠唯一索引的約束才行。

但是這個(gè)唯一索引面試官不讓用業(yè)務(wù)表的,那就只能直接搞個(gè)“消息消費(fèi)記錄表”,里面有個(gè)“消息唯一標(biāo)識(shí)”的字段,這個(gè)字段是唯一索引。

這張表面試官問(wèn)起來(lái),我就說(shuō)這張表是完全獨(dú)立于業(yè)務(wù)的存在,只是為了解決消息冪等這個(gè)存粹的技術(shù)問(wèn)題而出現(xiàn)的,基于它,我們就可以設(shè)計(jì)出一個(gè)通用的技術(shù)組件,這樣應(yīng)該說(shuō)的過(guò)去。

表有了,技術(shù)方法大概的雛形就有了。

然而你還不能開(kāi)始答題,現(xiàn)在思路還不是特別清晰,你要把方案捋清楚了再?gòu)埧凇?

在不知不覺(jué)間,你的指腹已經(jīng)摩擦的有點(diǎn)麻木了,于是你換了一個(gè)手,穿過(guò)帽子,接著按摩著自己的頭頂。

這個(gè)表我怎么用呢?

if(保存數(shù)據(jù)到消息消費(fèi)記錄表){//出現(xiàn)主鍵沖突就返回false
????save(業(yè)務(wù)消息);????
}

先校驗(yàn),再保存,非原子性,這樣肯定不行啊,

我們想想一個(gè)場(chǎng)景,如果保存數(shù)據(jù)到消息消費(fèi)記錄表成功,還沒(méi)來(lái)得及 save(業(yè)務(wù)消息) ,服務(wù)重啟了,怎么辦?

所以為了保證原子性,我們可以加入事務(wù),把這兩步綁定到一起:

開(kāi)啟事務(wù);
if(保存數(shù)據(jù)到消息消費(fèi)記錄表){//出現(xiàn)主鍵沖突就返回false
????save(業(yè)務(wù)消息);????
}
提交事務(wù);

這樣,如果保存數(shù)據(jù)到消息消費(fèi)記錄表成功,還沒(méi)來(lái)得及 save(扣款信息) ,服務(wù)重啟,事務(wù)回滾,消息消費(fèi)記錄表就不會(huì)真的插入成功。

而 MQ 沒(méi)有收到這個(gè)消息的回執(zhí),也會(huì)再次進(jìn)行投遞。

由于消息消費(fèi)記錄表里沒(méi)有這個(gè)數(shù)據(jù),所以會(huì)再次進(jìn)行消費(fèi)。

現(xiàn)在你覺(jué)得似乎沒(méi)啥問(wèn)題了,剛想給面試官說(shuō)你這個(gè)思路,但是立馬又想到了另外一個(gè)問(wèn)題:通過(guò)引入事務(wù)來(lái)解決了“非原子性”的問(wèn)題,但是事務(wù)這玩意,一般來(lái)說(shuō),大家都是能不使用事務(wù)的地方就盡量不使用事務(wù),通過(guò)最終一致性來(lái)保證數(shù)據(jù)的完整性。

這個(gè)老登肯定會(huì)在這個(gè)地方繼續(xù)窮追猛打的,我先預(yù)判了他,想想這個(gè)問(wèn)題怎么解決。

我們可以在消息消費(fèi)記錄表里面再引入一個(gè)“狀態(tài)”字段,這個(gè)字段有兩個(gè)取值:消費(fèi)中、消費(fèi)完成。

同時(shí)把唯一索引改成“消息唯一標(biāo)識(shí)+狀態(tài)”。

首先,MQ 發(fā)起請(qǐng)求,數(shù)據(jù)往消息消費(fèi)記錄表插的時(shí)候,狀態(tài)直接就是“消費(fèi)中”。

如果插入成功,則說(shuō)明是第一次消費(fèi),進(jìn)入到業(yè)務(wù)邏輯中去。

  • 如果業(yè)務(wù)邏輯執(zhí)行成功,則更新消息消費(fèi)記錄表對(duì)應(yīng)數(shù)據(jù)為“消費(fèi)完成”。
  • 如果業(yè)務(wù)邏輯執(zhí)行失敗,則刪除消息消費(fèi)記錄表對(duì)應(yīng)數(shù)據(jù),把消息仍回 MQ,等待下次重試。

如果插入失敗,則說(shuō)明是重復(fù)消費(fèi),直接扔掉。

畫(huà)成流程圖上大概是這樣的:

這就叫“面試造火箭,工作擰螺絲!”

順便提一嘴,上面這個(gè)流程圖我是用這個(gè)網(wǎng)站直接生成的,我覺(jué)得這個(gè)網(wǎng)站畫(huà)圖還挺舒服的:

https://excalidraw.com/

這就叫“面試造火箭,工作擰螺絲!”
這就叫“面試造火箭,工作擰螺絲!”

你感覺(jué)這波應(yīng)該穩(wěn)了,于是給面試官說(shuō)出了自己的方案,并在白字上畫(huà)了流程圖。

面試官拿著你的流程圖,看了一眼,立馬就看出了一個(gè)問(wèn)題:如果一個(gè)消息插入失敗,你的邏輯是扔掉。那假設(shè)這條消息的狀態(tài)是消費(fèi)中,業(yè)務(wù)邏輯執(zhí)行失敗,是不是應(yīng)該重新消費(fèi)才對(duì)呢?

于是你立馬反映過(guò)來(lái),如果插入失敗,則說(shuō)明是重復(fù)消費(fèi),還需要判斷數(shù)據(jù)的狀態(tài)。

  • 如果狀態(tài)是“消費(fèi)成功”,則說(shuō)明重復(fù)請(qǐng)求,直接返回成功
  • 如果狀態(tài)是“消費(fèi)中”,則說(shuō)明還未處理完成,為了確保成功,需要把請(qǐng)求再次仍回到 MQ。

修改了流程圖:

這就叫“面試造火箭,工作擰螺絲!”

面試官拿著這個(gè)流程圖,微微一笑:

倘若我業(yè)務(wù)執(zhí)行完之后,狀態(tài)更新之前,服務(wù)掛了,閣下又該如何應(yīng)對(duì)?

巧了,這個(gè)問(wèn)題上一篇文章的評(píng)論區(qū)也提到了:

這就叫“面試造火箭,工作擰螺絲!”

所以,還需要針對(duì)長(zhǎng)時(shí)間在“消費(fèi)中”的數(shù)據(jù)進(jìn)行一個(gè)監(jiān)控,人工兜底一下。

此外,為了防止“消費(fèi)完成”的數(shù)據(jù)量過(guò)多,還應(yīng)該對(duì)于這個(gè)狀態(tài)的數(shù)據(jù)做一個(gè)定時(shí)清理的任務(wù)。

終于,你看到了面試官臉上那一閃而過(guò)的滿意表情,在你覺(jué)得面試官應(yīng)該會(huì)放過(guò)你了的時(shí)候,他又提出了另外的問(wèn)題:

你這個(gè)通用組件理論上確實(shí)是可行的。

但是,這張表放在哪個(gè)庫(kù)的哪個(gè)表里呢?

是統(tǒng)一放在一個(gè)庫(kù)里呢還是就放在業(yè)務(wù)服務(wù)的庫(kù)里呢?

統(tǒng)一放一個(gè)庫(kù)的話太大了怎么辦呢是不是要按日期分表?

萬(wàn)一跟業(yè)務(wù)庫(kù)用的數(shù)據(jù)庫(kù)不是一個(gè)數(shù)據(jù)庫(kù)產(chǎn)品那事務(wù)不生效咋辦呢?

放在業(yè)務(wù)庫(kù)里的話萬(wàn)一業(yè)務(wù)服務(wù)連好幾個(gè)庫(kù)那我具體放哪一個(gè)呢?

是不是所有業(yè)務(wù)庫(kù)我都得加這么一張表強(qiáng)制綁架他們的數(shù)據(jù)庫(kù)?

...

這就叫“面試造火箭,工作擰螺絲!”

這一部分問(wèn)題,也來(lái)自上一篇文章評(píng)論區(qū)。

這就叫“面試造火箭,工作擰螺絲!”

聽(tīng)到這些問(wèn)題,你開(kāi)始覺(jué)得這個(gè)面試官是在胡攪蠻纏,一氣之下,準(zhǔn)備拿回簡(jiǎn)歷,結(jié)束面試。

但是手上動(dòng)作稍微大了一點(diǎn),一不小心掀起了自己的帽子,漏出了“資深的發(fā)型”。

面試官也愣住了,看著你“資深的發(fā)型”,當(dāng)即就握住了你的手:你就是我要找的人才。不面了,就你了,明天來(lái)報(bào)道!

這就叫“面試造火箭,工作擰螺絲!”

入職

入職之后你第一件事情就是看看這個(gè)公司的代碼。

當(dāng)你看第一個(gè)接口的時(shí)候,發(fā)現(xiàn)根本沒(méi)有做冪等。

當(dāng)你看第二個(gè)接口的時(shí)候,發(fā)現(xiàn)就是靠業(yè)務(wù)表的唯一索引做的冪等。

當(dāng)你看第三個(gè)接口的時(shí)候,Redis 的方案躍然紙上。

突然一個(gè)哥們氣喘吁吁的跑過(guò)來(lái)找昨天面試你的老登,說(shuō):快,又出問(wèn)題了,幫忙刪除一個(gè) Redis key。

于是,你抽過(guò)去準(zhǔn)備看一下怎么操作。

不經(jīng)意間看到了老登正在寫(xiě)一個(gè)文檔,題目叫做《一種分布式系統(tǒng)中數(shù)據(jù)唯一性的消息冪等保障策略》。

老登看到你過(guò)來(lái)了,說(shuō):正好,你來(lái)寫(xiě)這個(gè)文檔,我已經(jīng)把名字給你想好了,你就按照這個(gè)寫(xiě),把你昨天的思路寫(xiě)清楚,到時(shí)候我去匯報(bào)。

你興奮的問(wèn):匯報(bào)過(guò)了之后我們要按照這個(gè)方案落地嗎?

老登說(shuō):不不不,落地干啥啊,多麻煩啊,方案匯報(bào)嘛,體現(xiàn)一下我們?cè)诩夹g(shù)方面的時(shí)刻,在領(lǐng)導(dǎo)面前去刷個(gè)臉,所以你要多用一些高大上的詞,越晦澀難懂越好。哦,對(duì)了,我順便教教你怎么“刪除 Redis key”,以后就讓他們找你了。這幫老登,大半夜的,老是給我打電話。

這就叫“面試造火箭,工作擰螺絲!”
小編推薦閱讀

好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點(diǎn)或證實(shí)其描述。

相關(guān)視頻攻略

更多

掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

掃二維碼進(jìn)入好特網(wǎng)微信公眾號(hào)!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]

湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2024 haote.com 好特網(wǎng)