java分布式面試接口如何保證冪等及概念理解
引言
穩(wěn)定性設(shè)計(jì)第一篇:這一小節(jié)開(kāi)始講設(shè)計(jì)系統(tǒng)穩(wěn)定性保證的相關(guān)設(shè)計(jì),誰(shuí)都不想自己負(fù)責(zé)的系統(tǒng)三天兩頭就出故障,也不想周六日跟女票葡萄美酒夜光杯的時(shí)候一個(gè)電話call去VPN辦公,那么你就想辦法讓你的系統(tǒng)盡量穩(wěn)定,我們的目標(biāo)是讓系統(tǒng)“無(wú)人值守”。
阿里新零售和阿里媽媽,美團(tuán),過(guò)去我面試這些公司都被問(wèn)過(guò)接口冪等相關(guān)問(wèn)題,接口冪等設(shè)計(jì)在分布式系統(tǒng)開(kāi)發(fā)中非常常見(jiàn)且很重要,后來(lái)我自己做面試官也慢慢意識(shí)到冪等的重要性。
一些初學(xué)者對(duì)冪等這個(gè)概念完全不理解,更不知道如何設(shè)計(jì),這在工作中很容易給自己惹麻煩,所以一定要會(huì)!一定要會(huì)!一定要會(huì)!
1、冪等的概念
面試官:
冪等的概念你了解嗎,你設(shè)計(jì)的系統(tǒng)里有哪些接口使用到了冪等設(shè)計(jì)?
問(wèn)題分析:
冪等的概念首先你肯定理解了,簡(jiǎn)單通俗易懂,就是無(wú)論你是 Http 接口還是 RPC 接口,入?yún)⒉蛔兊那闆r下,無(wú)論請(qǐng)求多少次,結(jié)果都是一樣的,請(qǐng)求結(jié)果不會(huì)因?yàn)檎?qǐng)求次數(shù)不同而改變,沒(méi)有任務(wù)副作用。
答:我參加工作的第一年,在某在線購(gòu)票(電影票)App的一家公司做后臺(tái)系統(tǒng)開(kāi)發(fā),當(dāng)時(shí)我負(fù)責(zé)積分系統(tǒng),工作中接到這樣一個(gè)線上活動(dòng)需求。業(yè)務(wù)場(chǎng)景描述:用戶每天使用 App 點(diǎn)擊簽到按鈕參加活動(dòng),領(lǐng)取相應(yīng)的積分,每個(gè)用戶每天只能參加一次簽到領(lǐng)積分活動(dòng),簽到按鈕在點(diǎn)擊一次后會(huì)自設(shè)置灰色變?yōu)椴豢牲c(diǎn)擊的狀態(tài),這個(gè)領(lǐng)積分的接口由我負(fù)責(zé)開(kāi)發(fā),提供 API 給客戶端同事,上線后出現(xiàn)這樣一個(gè)bug,當(dāng)時(shí)沒(méi)有完善的業(yè)務(wù)監(jiān)控系統(tǒng),功能上線后第二天問(wèn)出于好奇系統(tǒng)里積分最高的人有多少積分,就在后臺(tái)跑了一個(gè)sql,這一好奇,驚奇的發(fā)現(xiàn)有的用戶積分高達(dá)幾萬(wàn)分,因?yàn)榉e分除了簽到領(lǐng)取外,大多都是消費(fèi)累計(jì)積分,一塊錢才能累積一分,我表示懷疑,什么能人看電影能看幾萬(wàn)塊錢?
帶著這個(gè)疑問(wèn),我查詢了他的積分累積記錄,發(fā)現(xiàn)大部分積分都是靠簽到領(lǐng)積分獲得的,按照活動(dòng)規(guī)則,一個(gè)人一天只能參加一次簽到,不可能有這么多積分,而這個(gè)用戶一天簽到幾百次,后來(lái)經(jīng)過(guò)和前端一同檢查bug發(fā)現(xiàn)問(wèn)題所在,原因是簽到按鈕雖然變灰,但是請(qǐng)求的 url 沒(méi)有在前端頁(yè)面隱藏,用戶通過(guò)技術(shù)手段繞過(guò) button 變灰的前端限制重復(fù)刷新了接口,重復(fù)獲得積分。
事后問(wèn)題分析:
這個(gè)bug最大問(wèn)題還在我這里,因?yàn)槲业慕涌跊](méi)有做冪等設(shè)計(jì),正確的邏輯應(yīng)該是根據(jù)系統(tǒng)當(dāng)前日期做冪等,冪等后無(wú)論用戶發(fā)起多少次請(qǐng)求,最后的結(jié)果都是一樣的,積分只累加一次。好在這個(gè)bug沒(méi)有被黑產(chǎn)發(fā)現(xiàn),只有幾個(gè)用戶發(fā)現(xiàn)損失可控。
因?yàn)槲胰鄙僭O(shè)計(jì)經(jīng)驗(yàn),不懂冪等設(shè)計(jì),領(lǐng)導(dǎo)也沒(méi)提醒我,所以出現(xiàn)這種bug,經(jīng)歷更多和錢相關(guān)的系統(tǒng)開(kāi)發(fā)后,我明白一個(gè)道理,任何系統(tǒng)設(shè)計(jì),都要考慮業(yè)務(wù)的安全性,內(nèi)部系統(tǒng)可以為了節(jié)省人力,適當(dāng)簡(jiǎn)化設(shè)計(jì),做到防君子不防小人,假設(shè)你的同事都是君子,對(duì)C端用戶的系統(tǒng),不光要防君子,還要防小人,風(fēng)險(xiǎn)防范不能全指望風(fēng)控系統(tǒng),有時(shí)bug可能會(huì)來(lái)自系統(tǒng)內(nèi)部,比如用戶并沒(méi)有惡意盜刷之意,只是網(wǎng)絡(luò)不好,用戶等了兩秒鐘還沒(méi)加載完就多點(diǎn)了幾次簽到按鈕,我的接口沒(méi)有做冪等設(shè)計(jì),只要收到請(qǐng)求就會(huì)多給用戶加積分,這個(gè)時(shí)候能怪用戶嗎?很顯然是開(kāi)發(fā)者的責(zé)任。
關(guān)于這個(gè)接口的冪等設(shè)計(jì)
我是這樣解決的:
積分接口后臺(tái)根據(jù)用戶手機(jī)號(hào) + userId + 系統(tǒng)當(dāng)前日期拼接后生成唯一流水號(hào),根據(jù)流水號(hào)后保存,如果用戶重復(fù)發(fā)起請(qǐng)求,先根據(jù)唯一流水號(hào)校驗(yàn)在后臺(tái)做校驗(yàn),如果流水號(hào)存在直接返回上一次請(qǐng)求結(jié)果,考慮到并發(fā)的情況下,狀態(tài)判斷使用了鎖處理。
開(kāi)發(fā)業(yè)務(wù)監(jiān)控系統(tǒng),采用定時(shí)任務(wù)每天生成系統(tǒng)里 Top100 積分增長(zhǎng)最多名單,運(yùn)營(yíng)或者術(shù)人員每天觀察有沒(méi)有異常。

經(jīng)過(guò)這次bug反思,學(xué)習(xí)到兩點(diǎn):
理解冪等設(shè)計(jì)的重要性,凡是和錢相關(guān)的功能請(qǐng)謹(jǐn)慎。
監(jiān)控系統(tǒng)的重要性,這里的監(jiān)控說(shuō)的是業(yè)務(wù)類監(jiān)控,如果那天我沒(méi)有好奇系統(tǒng)里誰(shuí)的積分最高,這個(gè)bug會(huì)什么時(shí)候發(fā)現(xiàn)?
面試官: 嚯,有點(diǎn)意思,你還真的是寫了個(gè)大bug,弄懂了吸取教訓(xùn)就好,可別進(jìn)了我的項(xiàng)目組后拿我們的系統(tǒng)寫這bug。
深入分析:
在編程中一個(gè)冪等操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。冪等函數(shù),或冪等方法,是指可以使用相同參數(shù)重復(fù)執(zhí)行,并能獲得相同結(jié)果的函數(shù)。這些函數(shù)不會(huì)影響系統(tǒng)狀態(tài),也不用擔(dān)心重復(fù)執(zhí)行會(huì)對(duì)系統(tǒng)造成改變。例如,“setTrue()”函數(shù)就是一個(gè)冪等函數(shù),無(wú)論多次執(zhí)行,其結(jié)果都是一樣的。更復(fù)雜的操作冪等保證是利用唯一交易號(hào)(流水號(hào))實(shí)現(xiàn)。
—— 百度百科
如果你了解 Restful 風(fēng)格接口,相信你對(duì) GET / POST / DELETE 幾個(gè)動(dòng)詞不陌生,在一次面試錘子科技的過(guò)程中,面試官問(wèn)我是否了解 Rest 接口,我balabala回答了這幾常用的動(dòng)詞,面試官又問(wèn)我:那你除了知道 GET 是從服務(wù)器獲取資源,還有別的理解嗎?當(dāng)時(shí)我沒(méi)答上來(lái),出了公司以后才想起,GET 動(dòng)作的設(shè)計(jì)應(yīng)該是冪等的。同理 DELETE 也是冪等的,如果你設(shè)計(jì)的接口 GET / DELETE 不是冪等的,那么你可能要重新思考一下了。
2、工作中常見(jiàn)的冪等設(shè)計(jì)場(chǎng)景
如果你做的功能和錢相關(guān),或者是能還錢的,那么你就要小心了,每一個(gè)接口都要先考慮下是否需要冪等設(shè)計(jì),下面是兩種常見(jiàn)的需求場(chǎng)景。
發(fā)券/積分接口,通常通過(guò) orderId userId 做冪等校驗(yàn)。
支付/退款接口,我們不希望用戶發(fā)起多次支付都收到用戶的錢,用戶會(huì)投訴,還要把錢退還給用戶,對(duì)系統(tǒng)還是客服人員來(lái)說(shuō)都是無(wú)用功,支付系統(tǒng)非常復(fù)雜,想做好支付系統(tǒng),還有很多東西需要學(xué)習(xí),要考慮網(wǎng)絡(luò)延遲,服務(wù)異常,訂單中心回掉超時(shí)等各種不穩(wěn)定的因素,通常采用前端控制,邏輯層狀態(tài)的控制,數(shù)據(jù)層唯一索引的控制,以及分布式鎖的控制,在冪等篇不過(guò)過(guò)多討論。
3、冪等接口常見(jiàn)設(shè)計(jì)方案
客戶端按鈕提交限制,每次提交一個(gè)請(qǐng)求時(shí),按鈕置為不可用。
后臺(tái)系統(tǒng)邏輯層處理,生成保存唯一ID(流水號(hào)),每次請(qǐng)求先校驗(yàn)流水號(hào)是否已經(jīng)存在,存在則表示重復(fù)操作,直接返回上一次操作結(jié)果。
token校驗(yàn)機(jī)制,客戶端請(qǐng)求前先申請(qǐng)token,同一個(gè)token只處理一次,無(wú)token或者相同token不做處理。
分布式鎖,如引入 Redis 分布式鎖,防止其他請(qǐng)求重復(fù)操作。
請(qǐng)求隊(duì)列,引入 MQ 排隊(duì)的方式讓請(qǐng)求有序處理,關(guān)于異步操作的應(yīng)用會(huì)在后面的章節(jié)講解。
每一種方案都有自己的優(yōu)缺點(diǎn),比如客服端按鈕提交限制,實(shí)現(xiàn)簡(jiǎn)單,但是不能從根本上解決問(wèn)題,后臺(tái)生成唯一ID,判斷存在狀態(tài)必須要保證原子操作,可以采用多種方案組合的方式解決冪等問(wèn)題,我們的目標(biāo)是,用最容易維護(hù)的方法解決問(wèn)題。
總結(jié)
在過(guò)去的工作經(jīng)歷中,我招進(jìn)來(lái)一個(gè)工作三年的同事,場(chǎng)景是開(kāi)發(fā)一個(gè)退款接口,review代碼的時(shí)候,我發(fā)現(xiàn)退款的功能是做完了,錢確實(shí)能退,但是并沒(méi)有做冪等設(shè)計(jì),我倆討論了下,我說(shuō):如果同一個(gè)訂單被請(qǐng)求了兩次退款,那這錢是不是要退兩次,這很危險(xiǎn)呀?當(dāng)時(shí)這個(gè)同事并沒(méi)有意識(shí)到這一點(diǎn),因?yàn)闆](méi)有相關(guān)經(jīng)驗(yàn),連概念都不知道,作為一個(gè)三年經(jīng)驗(yàn)的實(shí)在不應(yīng)該,和錢相關(guān)的功能一定要慎重,做冪等設(shè)計(jì)就是為了系統(tǒng)能防君子,也要防小人。
以上就是java分布式面試接口如何保證冪等及概念的詳細(xì)內(nèi)容,更多關(guān)于java分布式面試接口冪等保證的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解SpringBoot配置devtools實(shí)現(xiàn)熱部署
本篇文章主要介紹了詳解SpringBoot配置devtools實(shí)現(xiàn)熱部署 ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
全網(wǎng)最新springboot整合mybatis-plus的過(guò)程
在本文中,介紹了 MyBatis-Plus 的核心功能和使用方法,包括如何配置分頁(yè)插件、編寫分頁(yè)查詢代碼、使用各種 Wrapper 構(gòu)建復(fù)雜查詢條件等,通過(guò)這些內(nèi)容,相信你已經(jīng)對(duì) MyBatis-Plus 有了更深入的了解,并能夠在實(shí)際項(xiàng)目中靈活應(yīng)用這些功能,感興趣的朋友跟隨小編一起看看吧2025-02-02
SpringBoot實(shí)現(xiàn)國(guó)際化過(guò)程詳解
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)國(guó)際化過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Nacos后臺(tái)頻繁打印get changedGroupKeys:[]的問(wèn)題及解決
這篇文章主要介紹了Nacos后臺(tái)頻繁打印get changedGroupKeys:[]的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Netty開(kāi)發(fā)及粘包實(shí)戰(zhàn)解決分析
這篇文章主要為大家介紹了Netty開(kāi)發(fā)及粘包實(shí)戰(zhàn)解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02
一次"java:程序包org.aspectj.lang不存在"問(wèn)題解決實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了一次"java:程序包org.aspectj.lang不存在"問(wèn)題解決的實(shí)戰(zhàn)過(guò)程,這個(gè)錯(cuò)誤提示意味著你的Java程序中引用了org.aspectj.lang這個(gè)包,但是該包并不存在,文章通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
springboot嵌套子類使用方式—前端與后臺(tái)開(kāi)發(fā)的注意事項(xiàng)
這篇文章主要介紹了springboot嵌套子類使用方式—前端與后臺(tái)開(kāi)發(fā)的注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java讀取InfluxDB數(shù)據(jù)庫(kù)的方法詳解
本文介紹基于Java語(yǔ)言,讀取InfluxDB數(shù)據(jù)庫(kù)的方法,包括讀取InfluxDB的所有數(shù)據(jù)庫(kù),以及指定數(shù)據(jù)庫(kù)中的measurement、field、tag等,感興趣的小伙伴跟著小編一起來(lái)看看吧2025-01-01

