接口冪等性總結(jié)整理

1、什么是冪等性

冪等彻况,英文Idempotence

冪等這個詞原自數(shù)學(xué)谁尸,冪等性是數(shù)學(xué)中的一個概念,常見于抽象代數(shù)中纽甘,表達(dá)的是N次變換與1次變換的結(jié)果相同良蛮;

簡單來說就是如果方法調(diào)用一次和多次產(chǎn)生的效果是相同的,它就具有冪等性悍赢。

冪等函數(shù)或冪等方法决瞳,是指可以使用相同參數(shù)重復(fù)執(zhí)行,并能獲得相同結(jié)果的函數(shù)泽裳,這些函數(shù)不會影響系統(tǒng)狀態(tài)瞒斩,也不用擔(dān)心重復(fù)執(zhí)行會對系統(tǒng)造成改變。

冪等性(Idempotence)本身是一個數(shù)學(xué)概念涮总,在計(jì)算機(jī)的各個領(lǐng)域都借用了該概念胸囱。

HTTP維度的冪等性

在HTTP/1.1規(guī)范中冪等性的定義是:

Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects?of N > 0 identical requests is the same as for a single request.

從定義上看,HTTP方法的冪等性是指一次和多次請求某一個資源應(yīng)該具有同樣的副作用瀑梗。

HTTP請求常見有?GET烹笔、DELETE、PUT抛丽、POST四種主要方法谤职;

GET方法

HTTP GET方法用于獲取資源,不應(yīng)有副作用亿鲜,所以是冪等的允蜈。

比如:GEThttps://www.wkcto.com/course/100?不會改變資源的狀態(tài),不論調(diào)用一次還是N次都沒有副作用蒿柳。

請注意饶套,這里強(qiáng)調(diào)的是一次和N次具有相同的副作用,而不是每次GET的結(jié)果相同垒探。

GET https://www.wkcto.com/course?這個HTTP請求可能會每次得到不同的結(jié)果妓蛮,但它本身并沒有產(chǎn)生任何副作用,因而是滿足冪等性的圾叼。

DELETE方法

HTTP DELETE方法用于刪除資源蛤克,有副作用捺癞,但它應(yīng)該滿足冪等性。

比如:DELETEhttps://www.wkcto.com/article/detail/10?构挤,調(diào)用一次和N次對系統(tǒng)產(chǎn)生的副作用是相同的髓介,即刪掉id為10的文章,因此調(diào)用者可以多次調(diào)用或刷新頁面而不必?fù)?dān)心引起錯誤儿倒。

POST方法

HTTP POST所對應(yīng)的URI為資源的接收者版保。

比如:

POST https://www.wkcto.com/article?的語義是在https://www.wkcto.com/article下發(fā)表一篇文章,兩次相同的POST請求會在服務(wù)器端創(chuàng)建兩份資源夫否,所以POST方法不具備冪等性彻犁。

PUT方法

HTTP PUT所對應(yīng)的URI是要創(chuàng)建或更新的資源。

比如:PUThttps://www.wkcto.com/article/5231?的語義是創(chuàng)建或更新ID為5231的文章凰慈,對同一URI進(jìn)行多次PUT的副作用和一次PUT是相同的汞幢,因此PUT方法具有冪等性。


以上是主要針對RESTful風(fēng)格的HTTP冪等性討論微谓;

我們知道HTTP協(xié)議是一種面向資源的應(yīng)用層協(xié)議森篷,但對HTTP協(xié)議的應(yīng)用存在兩種不同的方式:

一種是RESTful的,它把HTTP當(dāng)成應(yīng)用層協(xié)議豺型,遵守HTTP協(xié)議的各種規(guī)定仲智;

另一種是在HTTP協(xié)議之上封裝的RPC,沒有完全把HTTP當(dāng)成應(yīng)用層協(xié)議姻氨,而是把HTTP協(xié)議作為了傳輸層協(xié)議钓辆,然后在HTTP之上建立了自己的應(yīng)用層協(xié)議。

那么拋開HTTP協(xié)議的規(guī)范肴焊,冪等性是分布式系統(tǒng)的重要特性前联,所以不論是RESTful的API設(shè)計(jì)還是RPC方式的其他API設(shè)計(jì)都應(yīng)該考慮冪等性;

應(yīng)用維度的冪等性

冪等性衍生到軟件工程中, 它的語義是指函數(shù)/接口可以使用相同的參數(shù)重復(fù)執(zhí)行, 不應(yīng)該影響系統(tǒng)狀態(tài), 也不會對系統(tǒng)造成改變娶眷。

也就是任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行所產(chǎn)生的影響相同似嗤;

如果用戶對同一操作發(fā)起的一次請求或多次請求所產(chǎn)生的影響是一致的,不會因?yàn)槎啻握{(diào)用(點(diǎn)擊)而產(chǎn)生了副作用届宠,那么這就是冪等的烁落;

第一次請求的時候?qū)Y源產(chǎn)生了副作用,但是以后的多次請求都不會再對資源產(chǎn)生副作用豌注。這里的副作用是指不會對結(jié)果產(chǎn)生破壞或者產(chǎn)生不可預(yù)料的結(jié)果伤塌。

即冪等性=多次執(zhí)行無副作用;

2幌羞、產(chǎn)生冪等場景

冪等性問題在我們的開發(fā)中寸谜,分布式竟稳、微服務(wù)架構(gòu)中是隨處可見的:

1属桦、因網(wǎng)絡(luò)波動熊痴,可能會引起重復(fù)請求;

2聂宾、用戶重復(fù)操作果善,用戶在使用產(chǎn)品時可能會無意的觸發(fā)多次下單多次交易,甚至沒有響應(yīng)而有意觸發(fā)多筆交易系谐;

3巾陕、應(yīng)用使用了失敗或超時重試機(jī)制(如Nginx重試、RPC重試或業(yè)務(wù)層重試等)

4纪他、第三方平臺的接口(如:支付成功回調(diào)接口)鄙煤,因?yàn)楫惓?dǎo)致多次異步回調(diào);

5茶袒、中間件/應(yīng)用服務(wù)根據(jù)自身的特性梯刚,也有可能進(jìn)行重試。

6薪寓、用戶雙擊提交按鈕;

7亡资、頁面重復(fù)刷新;

8、使用瀏覽器后退按鈕重復(fù)之前的操作向叉,導(dǎo)致重復(fù)提交表單;

9锥腻、使用瀏覽器歷史記錄重復(fù)提交表單;

10、瀏覽器重復(fù)的HTTP請求母谎;

11瘦黑、定時任務(wù)重復(fù)執(zhí)行;

3销睁、冪等在哪一層實(shí)現(xiàn)

我們現(xiàn)在都是分布式供璧、微服務(wù)的架構(gòu),在哪一層進(jìn)進(jìn)行冪等設(shè)計(jì)冻记,在哪一層解決冪等性問題睡毒?

4、數(shù)據(jù)訪問層的冪等性

讀請求

寫請求

讀請求需要做冪等嗎冗栗?很顯然是不需要的演顾;

寫請求呢?涉及到需要做insert隅居、update钠至、delete數(shù)據(jù)庫操作的,肯定是需要的實(shí)現(xiàn)冪等性的胎源;

那我們可以得出一個結(jié)論棉钧,即不會改變數(shù)據(jù)的操作我們可以不做冪等,會改變數(shù)據(jù)的操作我們就一定要做冪等涕蚤;

那我們逐個討論寫請求:insert宪卿、delete的诵、update操作,首先我假設(shè)我沒有做任何應(yīng)用層面上的冪等操作佑钾。

insert

對于insert操作西疤,當(dāng)我重復(fù)插入數(shù)據(jù)的時候會出現(xiàn)什么情況?這里分兩種情況:

自增主鍵(有冪等性問題)

業(yè)務(wù)主鍵(沒有冪等性問題)

比如:insert into product_info(id, name, type, price, tm)休溶;

假如我的id是自增主鍵會有問題嗎代赁?一定會有冪等性問題,因?yàn)闀a(chǎn)生多條業(yè)務(wù)數(shù)據(jù)相同但主鍵不同的數(shù)據(jù)兽掰。

那如果是業(yè)務(wù)主鍵呢芭碍?即我假設(shè)對name、type孽尽、price建立唯一索引豁跑,這樣就ok了,即使我id相同泻云,數(shù)據(jù)庫也會報錯了艇拍。

delete

對于delete操作,當(dāng)重復(fù)執(zhí)行的時候會出現(xiàn)什么情況宠纯?這里也要分兩種情況:

相對值刪除

絕對值刪除

比如:

delete from product_info where id = 1234; --冪等的

delete top(10) from product_info; --不是冪等

如果是絕對值刪除卸夕,重復(fù)操作兩次是不會出現(xiàn)問題的,但是如果相對值刪除,重復(fù)操作就是重復(fù)刪除多次婆瓜。

update

對于update操作快集,當(dāng)重復(fù)更新數(shù)據(jù)的時候會出現(xiàn)什么情況?這里其實(shí)和刪除操作是一樣廉白,也需要分兩種情況討論:

相對值刪除

絕對值刪除

我們拿一個具體的例子分析:

update product_info set price = 99 where id = 1234; ?--冪等的

update product_info set price = price + 100 where id = 1234; --不是冪等

如果是絕對值修改个初,重復(fù)操作也不會有問題,但是相對值修改猴蹂,一定會有問題院溺,會重復(fù)修改多次。

select

最后是select操作磅轻,其實(shí)這個不用討論珍逸,因?yàn)椴粫?shù)據(jù)發(fā)生改變的操作我們不用做冪等。

狹義與廣義的冪等

以上的所有討論都是基于單庫的聋溜,這是狹義上的冪等處理谆膳,但是在實(shí)際的業(yè)務(wù)場景中,比如分布式系統(tǒng)中撮躁,我們的一次請求可能有多個步驟漱病,這種跨服務(wù)、跨事務(wù)請求的冪等處理怎么辦?也就是廣義上的冪等處理怎么辦呢杨帽?其實(shí)這個就需要分布式事務(wù)來保證冪等性凝果;


所以廣義上的冪等處理通過分布式事務(wù)來解決,狹義上的冪等處理睦尽,對于服務(wù)分層來說只需要在數(shù)據(jù)訪問層做冪等操作,而對于讀寫請求冪等處理型雳,select我們不用處理当凡,insert操作只要要求必須有唯一業(yè)務(wù)主鍵,delete操作在實(shí)際業(yè)務(wù)上一般不會被允許纠俭,update操作只需要把相對值修改轉(zhuǎn)換成絕對值修改即可沿量。

5、保證冪等性的方法

前端冪等的實(shí)現(xiàn)(不是可靠的)

1冤荆、按鈕只可操作一次

一般是提交后把按鈕置灰或loding狀態(tài)朴则,按鈕置灰或loding狀態(tài)可以用一些js組件實(shí)現(xiàn),消除用戶因?yàn)橹貜?fù)點(diǎn)擊而產(chǎn)生的副作用钓简,比如添加操作乌妒,由于點(diǎn)擊兩次而產(chǎn)生兩條記錄。

2外邓、token機(jī)制

產(chǎn)品上允許重復(fù)提交撤蚊,但要保證重復(fù)提交不產(chǎn)生副作用,比如點(diǎn)擊n次只產(chǎn)生一條記錄损话;

具體實(shí)現(xiàn)就是進(jìn)入頁面時申請一個token侦啸,然后后面所有的請求都帶上這個token,根據(jù)token來避免重復(fù)請求丧枪;

3光涂、使用Post/Redirect/Get模式

在提交后執(zhí)行頁面重定向,這就是所謂的Post-Redirect-Get (PRG)模式拧烦。簡言之忘闻,當(dāng)用戶提交了表單后,去執(zhí)行一個客戶端的重定向恋博,轉(zhuǎn)到提交成功信息頁面服赎,這樣避免用戶按F5刷新導(dǎo)致的重復(fù)提交,而其也不會出現(xiàn)瀏覽器表單重復(fù)提交的警告交播,也能消除按瀏覽器前進(jìn)和后退按導(dǎo)致的同樣重復(fù)提交的問題重虑;

4、Session中存放特殊標(biāo)志

在服務(wù)器端秦士,生成一個唯一的標(biāo)識符缺厉,將它存入session,同時將它寫入表單的隱藏中,然后將表單頁面發(fā)給瀏覽器提针,用戶輸入信息后點(diǎn)擊提交命爬,在服務(wù)器端,獲取表單中隱藏字段的值辐脖,與session中的唯一標(biāo)識符比較饲宛,相等說明是首次提交,就處理本次請求嗜价,然后將session中的唯一標(biāo)識符移除艇抠,不相等則表示是重復(fù)提交,不再做處理久锥;

后端冪等性的實(shí)現(xiàn)

1家淤、使用唯一索引防止冪等性問題

此方案可以限制重復(fù)插入數(shù)據(jù),當(dāng)數(shù)據(jù)重復(fù)時瑟由,插入數(shù)據(jù)庫會拋異常絮重,保證不會出現(xiàn)臟數(shù)據(jù),這也是一種簡單粗暴的辦法歹苦;

2青伤、Token+Redis的冪等方案

這種方式分成兩個階段:申請token階段和業(yè)務(wù)操作階段。

以支付為例:

第一階段殴瘦,在進(jìn)入到提交訂單頁面之前潮模,需要訂單系統(tǒng)根據(jù)用戶信息向支付系統(tǒng)發(fā)起一次申請token的請求,支付系統(tǒng)將token保存到Redis緩存中痴施,為第二階段支付使用擎厢。

第二階段,訂單系統(tǒng)拿著申請到的token發(fā)起支付請求辣吃,支付系統(tǒng)會檢查Redis中是否存在該token动遭,如果存在,表示第一次發(fā)起支付請求神得,開始支付邏輯處理厘惦,處理完邏輯后刪除redis中的token;

當(dāng)重復(fù)請求時候哩簿,檢查緩存中token不存在宵蕉,表示非法請求。

該方案的不足之處是需要與系統(tǒng)間交互兩次节榜;

3羡玛、狀態(tài)機(jī)冪等

針對更新操作,比如業(yè)務(wù)上需要修改訂單狀態(tài)宗苍,訂單有待支付稼稿、支付中薄榛、支付成功、支付失敗让歼、訂單超時關(guān)閉等敞恋,在設(shè)計(jì)的時候最好只支持狀態(tài)的單向改變(不可逆),這樣在更新的時候where條件里可以加上status?= 我期望的原來的status谋右,多次調(diào)用的話實(shí)際上也只會執(zhí)行一次硬猫。

Update xx set status=‘支付中’?where status=’待支付’and id=xx;

4改执、樂觀鎖實(shí)現(xiàn)冪等

如果更新已有數(shù)據(jù)啸蜜,可以進(jìn)行加鎖更新,也可以設(shè)計(jì)表結(jié)構(gòu)時使用樂觀鎖天梧,通過version來做樂觀鎖,這樣既能保證執(zhí)行效率霞丧,又能保證冪等呢岗。樂觀鎖的version版本在更新業(yè)務(wù)數(shù)據(jù)要自增。

1蛹尝、查詢數(shù)據(jù)后豫,得到版本號;version=1

2突那、通過版本號去更新挫酿,版本號匹配就更新,版本號不匹配就不能更新愕难;update xxx set money = money - 99, version = version + 1 ?where id = xx and version = 1;

也可以采用update with condition早龟,更新帶條件,實(shí)現(xiàn)樂觀鎖猫缭,通過version或者其他條件來實(shí)現(xiàn)樂觀鎖葱弟;

update table_xxx set quality=quality-#subQuality#,version=version+1 where id=xx and version=#version#


帶條件的樂觀鎖:

update table_xxx set quality=quality-#subQuality# where quality-#subQuality# >= 0

5、防重表實(shí)現(xiàn)冪等性

需要增加一個表猜丹,這個表叫做防重表(防止數(shù)據(jù)重復(fù)的表)

使用唯一主鍵去做防重表的唯一索引芝加,比如使用訂單號orderNo做為防重表的唯一索引,每次請求都根據(jù)訂單號向去重表中插入一條數(shù)據(jù)射窒,第一次請求查詢訂單支付狀態(tài)藏杖,當(dāng)然訂單沒有支付,進(jìn)行支付操作脉顿,支付前先向防重表中插入該支付的訂單號蝌麸,插入成功說明可以支付,無論成功與否艾疟,執(zhí)行完后更新訂單狀態(tài)為成功或其他狀態(tài)祥楣,或者是失敗开财,然后可以刪除防重表中的數(shù)據(jù)。后續(xù)的訂單因?yàn)楸碇形ㄒ凰饕迦胧∥笸剩瑒t返回操作失敗责鳍,直到第一次的請求操作完成(成功或失敗)兽间,可以看出防重表作用是加鎖的功能历葛;

select + insert

該方案就是操作之前先查詢一下,符合要求再插入嘀略,該方案在沒有并發(fā)的系統(tǒng)中可以解決冪等問題恤溶,在單JVM有并發(fā)的時候可以JVM加鎖來保證冪等性,在分布式環(huán)境它是無法保證冪等性帜羊,可以使用分布式鎖來保證咒程。

6、分布式鎖保證冪等性

在進(jìn)入方法時讼育,先去獲取鎖帐姻,假如獲取到鎖,就繼續(xù)后面的流程奶段。假如沒有獲取到鎖饥瓷,就等待鎖的釋放直到獲取到鎖,當(dāng)執(zhí)行完方法時痹籍,釋放鎖呢铆,當(dāng)然,鎖要設(shè)個超時時間蹲缠,防止意外沒有釋放到鎖棺克,它可以用來解決分布式系統(tǒng)的冪等性;

常用的分布式鎖實(shí)現(xiàn)方案是redis 和 zookeeper 等工具线定。

使用分布式鎖類似于防重表逆航,將防重并發(fā)放到了緩存中,較為高效渔肩。思路相同因俐,同一時間只能完成一次支付請求。

7周偎、緩沖隊(duì)列

將請求都快速地接收下來抹剩,放入緩沖隊(duì)列,后續(xù)使用異步任務(wù)處理隊(duì)列中的數(shù)據(jù)蓉坎,過濾掉重復(fù)的請求澳眷,此方案優(yōu)點(diǎn)是同步改為異步處理,高吞吐蛉艾,不足是不能及時地返回請求結(jié)果钳踊,需要后續(xù)輪詢處理結(jié)果衷敌。

8、全局唯一號實(shí)現(xiàn)冪等

比如通過source來源+seq序列號來判斷請求是否重復(fù)拓瞪,在并發(fā)時只能處理一個請求缴罗,其它相同并發(fā)請求要么返回請求重復(fù),要么等待前面請求執(zhí)行完成在執(zhí)行祭埂;

小結(jié)

冪等性雖然復(fù)雜化了業(yè)務(wù)功能和降低了執(zhí)行效率面氓,但為了保證系統(tǒng)的正確性,是必要的蛆橡。保證方法或接口的冪等性是非常有必要的舌界,因?yàn)閿?shù)據(jù)是不能出現(xiàn)任何問題的;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泰演,一起剝皮案震驚了整個濱河市呻拌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌睦焕,老刑警劉巖藐握,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異复亏,居然都是意外死亡趾娃,警方通過查閱死者的電腦和手機(jī)缭嫡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門缔御,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妇蛀,你說我怎么就攤上這事耕突。” “怎么了评架?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵眷茁,是天一觀的道長。 經(jīng)常有香客問我纵诞,道長上祈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任浙芙,我火速辦了婚禮登刺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗡呼。我一直安慰自己纸俭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布南窗。 她就那樣靜靜地躺著揍很,像睡著了一般郎楼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窒悔,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天呜袁,我揣著相機(jī)與錄音,去河邊找鬼蛉迹。 笑死傅寡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的北救。 我是一名探鬼主播荐操,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼珍策!你這毒婦竟也來了托启?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤攘宙,失蹤者是張志新(化名)和其女友劉穎屯耸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹭劈,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疗绣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了铺韧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片多矮。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哈打,靈堂內(nèi)的尸體忽然破棺而出塔逃,到底是詐尸還是另有隱情,我是刑警寧澤料仗,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布湾盗,位于F島的核電站,受9級特大地震影響立轧,放射性物質(zhì)發(fā)生泄漏格粪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一氛改、第九天 我趴在偏房一處隱蔽的房頂上張望帐萎。 院中可真熱鬧,春花似錦平窘、人聲如沸吓肋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春椿疗,著一層夾襖步出監(jiān)牢的瞬間宪拥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓篙顺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親充择。 傳聞我的和親對象是個殘疾皇子德玫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348