接口冪等性設(shè)計(jì)

平手友梨奈

一、什么是冪等踪旷?

看一下維基百科怎么說(shuō)的:

圖片

冪等性:多次調(diào)用方法或者接口不會(huì)改變業(yè)務(wù)狀態(tài)植捎,可以保證重復(fù)調(diào)用的結(jié)果和單次調(diào)用的結(jié)果一致衙解。

二、使用冪等的場(chǎng)景

1焰枢、前端重復(fù)提交

用戶(hù)注冊(cè)蚓峦,用戶(hù)創(chuàng)建商品等操作,前端都會(huì)提交一些數(shù)據(jù)給后臺(tái)服務(wù)济锄,后臺(tái)需要根據(jù)用戶(hù)提交的數(shù)據(jù)在數(shù)據(jù)庫(kù)中創(chuàng)建記錄暑椰。如果用戶(hù)不小心多點(diǎn)了幾次,后端收到了好幾次提交荐绝,這時(shí)就會(huì)在數(shù)據(jù)庫(kù)中重復(fù)創(chuàng)建了多條記錄一汽。這就是接口沒(méi)有冪等性帶來(lái)的 bug。

2低滩、接口超時(shí)重試

對(duì)于給第三方調(diào)用的接口召夹,有可能會(huì)因?yàn)榫W(wǎng)絡(luò)原因而調(diào)用失敗,這時(shí)恕沫,一般在設(shè)計(jì)的時(shí)候會(huì)對(duì)接口調(diào)用加上失敗重試的機(jī)制监憎。如果第一次調(diào)用已經(jīng)執(zhí)行了一半時(shí),發(fā)生了網(wǎng)絡(luò)異常婶溯。這時(shí)再次調(diào)用時(shí)就會(huì)因?yàn)榕K數(shù)據(jù)的存在而出現(xiàn)調(diào)用異常鲸阔。

3偷霉、消息重復(fù)消費(fèi)

在使用消息中間件來(lái)處理消息隊(duì)列,且手動(dòng) ack 確認(rèn)消息被正常消費(fèi)時(shí)隶债。如果消費(fèi)者突然斷開(kāi)連接腾它,那么已經(jīng)執(zhí)行了一半的消息會(huì)重新放回隊(duì)列。

當(dāng)消息被其他消費(fèi)者重新消費(fèi)時(shí)死讹,如果沒(méi)有冪等性瞒滴,就會(huì)導(dǎo)致消息重復(fù)消費(fèi)時(shí)結(jié)果異常,如數(shù)據(jù)庫(kù)重復(fù)數(shù)據(jù)赞警,數(shù)據(jù)庫(kù)數(shù)據(jù)沖突妓忍,資源重復(fù)等。

三愧旦、解決方案

1世剖、token 機(jī)制實(shí)現(xiàn)

通過(guò)token 機(jī)制實(shí)現(xiàn)接口的冪等性,這是一種比較通用性的實(shí)現(xiàn)方法。

示意圖如下:

圖片

具體流程步驟:

  1. 客戶(hù)端會(huì)先發(fā)送一個(gè)請(qǐng)求去獲取 token笤虫,服務(wù)端會(huì)生成一個(gè)全局唯一的 ID 作為 token 保存在 redis 中旁瘫,同時(shí)把這個(gè) ID 返回給客戶(hù)端
  2. 客戶(hù)端第二次調(diào)用業(yè)務(wù)請(qǐng)求的時(shí)候必須攜帶這個(gè) token
  3. 服務(wù)端會(huì)校驗(yàn)這個(gè) token,如果校驗(yàn)成功琼蚯,則執(zhí)行業(yè)務(wù)酬凳,并刪除 redis 中的 token
  4. 如果校驗(yàn)失敗,說(shuō)明 redis 中已經(jīng)沒(méi)有對(duì)應(yīng)的 token遭庶,則表示重復(fù)操作宁仔,直接返回指定的結(jié)果給客戶(hù)端

注意:

  1. 對(duì) redis 中是否存在 token 以及刪除的代碼邏輯建議用 Lua 腳本實(shí)現(xiàn),保證原子性
  2. 全局唯一 ID 可以用百度的 uid-generator峦睡、美團(tuán)的 Leaf 去生成

2翎苫、基于 mysql 實(shí)現(xiàn)

這種實(shí)現(xiàn)方式是利用 mysql 唯一索引的特性。

示意圖如下:

圖片

具體流程步驟:

  1. 建立一張去重表榨了,其中某個(gè)字段需要建立唯一索引
  2. 客戶(hù)端去請(qǐng)求服務(wù)端煎谍,服務(wù)端會(huì)將這次請(qǐng)求的一些信息插入這張去重表中
  3. 因?yàn)楸碇心硞€(gè)字段帶有唯一索引,如果插入成功龙屉,證明表中沒(méi)有這次請(qǐng)求的信息粱快,則執(zhí)行后續(xù)的業(yè)務(wù)邏輯
  4. 如果插入失敗,則代表已經(jīng)執(zhí)行過(guò)當(dāng)前請(qǐng)求叔扼,直接返回

3事哭、基于 redis 實(shí)現(xiàn)

這種實(shí)現(xiàn)方式是基于 SETNX 命令實(shí)現(xiàn)的

SETNX key value:將 key 的值設(shè)為 value ,當(dāng)且僅當(dāng) key 不存在瓜富。若給定的 key 已經(jīng)存在鳍咱,則 SETNX 不做任何動(dòng)作。

該命令在設(shè)置成功時(shí)返回 1与柑,設(shè)置失敗時(shí)返回 0谤辜。

示意圖如下:

圖片

具體流程步驟:

  1. 客戶(hù)端先請(qǐng)求服務(wù)端蓄坏,會(huì)拿到一個(gè)能代表這次請(qǐng)求業(yè)務(wù)的唯一字段
  2. 將該字段以 SETNX 的方式存入 redis 中,并根據(jù)業(yè)務(wù)設(shè)置相應(yīng)的超時(shí)時(shí)間
  3. 如果設(shè)置成功丑念,證明這是第一次請(qǐng)求涡戳,則執(zhí)行后續(xù)的業(yè)務(wù)邏輯
  4. 如果設(shè)置失敗,則代表已經(jīng)執(zhí)行過(guò)當(dāng)前請(qǐng)求脯倚,直接返回

開(kāi)源實(shí)現(xiàn)

idempotent 冪等處理方案

1.原理

1.請(qǐng)求開(kāi)始前渔彰,根據(jù)key查詢(xún) 查到結(jié)果:報(bào)錯(cuò) 未查到結(jié)果:存入key-value-expireTime key=ip+url+args

2.請(qǐng)求結(jié)束后,直接刪除key 不管key是否存在推正,直接刪除 是否刪除恍涂,可配置

3.expireTime過(guò)期時(shí)間,防止一個(gè)請(qǐng)求卡死植榕,會(huì)一直阻塞再沧,超過(guò)過(guò)期時(shí)間,自動(dòng)刪除 過(guò)期時(shí)間要大于業(yè)務(wù)執(zhí)行時(shí)間尊残,需要大概評(píng)估下;

4.此方案直接切的是接口請(qǐng)求層面炒瘸。

5.過(guò)期時(shí)間需要大于業(yè)務(wù)執(zhí)行時(shí)間,否則業(yè)務(wù)請(qǐng)求1進(jìn)來(lái)還在執(zhí)行中寝衫,前端未做遮罩顷扩,或者用戶(hù)跳轉(zhuǎn)頁(yè)面后再回來(lái)做重復(fù)請(qǐng)求2,在業(yè)務(wù)層面上看竞端,結(jié)果依舊是不符合預(yù)期的。

6.建議delKey = false庙睡。即使業(yè)務(wù)執(zhí)行完事富,也不刪除key,強(qiáng)制鎖expireTime的時(shí)間乘陪。預(yù)防5的情況發(fā)生统台。

7.實(shí)現(xiàn)思路:同一個(gè)請(qǐng)求ip和接口,相同參數(shù)的請(qǐng)求啡邑,在expireTime內(nèi)多次請(qǐng)求贱勃,只允許成功一次。

8.頁(yè)面做遮罩谤逼,數(shù)據(jù)庫(kù)層面的唯一索引贵扰,先查詢(xún)?cè)偬砑樱忍幚矸绞綉?yīng)該都處理下流部。

9.此注解只用于冪等戚绕,不用于鎖,100個(gè)并發(fā)這種壓測(cè)枝冀,會(huì)出現(xiàn)問(wèn)題舞丛,在這種場(chǎng)景下也沒(méi)有意義耘子,實(shí)際中用戶(hù)也不會(huì)出現(xiàn)1s或者3s內(nèi)手動(dòng)發(fā)送了50個(gè)或者100個(gè)重復(fù)請(qǐng)求,或者弱網(wǎng)下有100個(gè)重復(fù)請(qǐng)求;

2.使用

    1. 引入依賴(lài)
<dependency>
    <groupId>com.pig4cloud.plugin</groupId>
    <artifactId>idempotent-spring-boot-starter</artifactId>
    <version>0.0.3</version>
</dependency>
    1. 配置 redis 鏈接相關(guān)信息
spring:
  redis:
    host: 127.0.0.1
    port: 6379

理論是支持 redisson-spring-boot-starter 全部配置

    1. 接口設(shè)置注解
@Idempotent(key = "#demo.username", expireTime = 3, info = "請(qǐng)勿重復(fù)查詢(xún)")
@GetMapping("/test")
public String test(Demo demo) {
    return "success";
}

注解 配置詳細(xì)說(shuō)明

    1. 冪等操作的唯一標(biāo)識(shí)球切,使用spring el表達(dá)式 用#來(lái)引用方法參數(shù) 谷誓。 可為空則取 當(dāng)前 url + args 做表示
String key();
    1. 有效期 默認(rèn):1 有效期要大于程序執(zhí)行時(shí)間,否則請(qǐng)求還是可能會(huì)進(jìn)來(lái)
    int expireTime() default 1;
    1. 時(shí)間單位 默認(rèn):s (秒)
TimeUnit timeUnit() default TimeUnit.SECONDS;
    1. 冪等失敗提示信息吨凑,可自定義
String info() default "重復(fù)請(qǐng)求捍歪,請(qǐng)稍后重試";
    1. 是否在業(yè)務(wù)完成后刪除key true:刪除 false:不刪除
boolean delKey() default false;

總結(jié)

這幾種實(shí)現(xiàn)冪等的方式其實(shí)都是大同小異的,類(lèi)似的還有使用狀態(tài)機(jī)怀骤、悲觀鎖费封、樂(lè)觀鎖的方式來(lái)實(shí)現(xiàn),都是比較簡(jiǎn)單的蒋伦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弓摘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子痕届,更是在濱河造成了極大的恐慌韧献,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件研叫,死亡現(xiàn)場(chǎng)離奇詭異锤窑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)嚷炉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)渊啰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人申屹,你說(shuō)我怎么就攤上這事绘证。” “怎么了哗讥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵嚷那,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我杆煞,道長(zhǎng)魏宽,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任决乎,我火速辦了婚禮队询,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘构诚。我一直安慰自己娘摔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布唤反。 她就那樣靜靜地躺著凳寺,像睡著了一般鸭津。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肠缨,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天逆趋,我揣著相機(jī)與錄音,去河邊找鬼晒奕。 笑死闻书,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脑慧。 我是一名探鬼主播魄眉,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼闷袒!你這毒婦竟也來(lái)了坑律?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤囊骤,失蹤者是張志新(化名)和其女友劉穎晃择,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體也物,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宫屠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滑蚯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浪蹂。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖告材,靈堂內(nèi)的尸體忽然破棺而出坤次,到底是詐尸還是另有隱情,我是刑警寧澤创葡,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布浙踢,位于F島的核電站绢慢,受9級(jí)特大地震影響灿渴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胰舆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一骚露、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缚窿,春花似錦棘幸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吨悍。三九已至,卻和暖如春蹋嵌,著一層夾襖步出監(jiān)牢的瞬間育瓜,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工栽烂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躏仇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓腺办,卻偏偏與公主長(zhǎng)得像焰手,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怀喉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 今天感恩節(jié)哎书妻,感謝一直在我身邊的親朋好友。感恩相遇磺送!感恩不離不棄驻子。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,562評(píng)論 0 11
  • 彩排完估灿,天已黑
    劉凱書(shū)法閱讀 4,209評(píng)論 1 3
  • 表情是什么崇呵,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒。表情可以傳達(dá)很多信息馅袁。高興了當(dāng)然就笑了域慷,難過(guò)就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 124,908評(píng)論 2 7