所有涉及數(shù)據(jù)變更的接口,都一定要做冪等處理嗎?
前言
大家好,我是田螺。
最近面試了一位Java開發(fā)候選人,10年的開發(fā)經(jīng)驗,居然不知道接口冪等如何設(shè)計。。。
我們本文來聊聊,后端設(shè)計的一個重要原則:接口冪等性。以及什么樣的接口才做冪等處理。
是否所有的接口都必須進(jìn)行冪等處理呢?是否所有涉及數(shù)據(jù)變更的接口,都要做冪等處理呢?
顯然不是,我們要綜合考慮業(yè)務(wù)場景、數(shù)據(jù)重要程度、數(shù)據(jù)修復(fù)難易程度等,靈活判斷是否需要冪等設(shè)計,避免過度設(shè)計!
- 什么是接口冪等性?
 - 哪些接口必須做冪等處理
 - 哪些接口可酌情不做冪等處理
 - 通用方案:如何設(shè)計冪等接口?
 - 冪等涉及的分布式唯一ID如何生成
 - 冪等設(shè)計的核心原則
 
1. 什么是接口冪等性?
冪等是一個數(shù)學(xué)與計算機科學(xué)概念。
- 在數(shù)學(xué)中,冪等用函數(shù)表達(dá)式就是:
f(x) = f(f(x))。比如求絕對值的函數(shù),就是冪等的,abs(x) = abs(abs(x))。 - 計算機科學(xué)中,冪等表示一次和多次請求某一個資源應(yīng)該具有同樣的副作用,或者說,多次請求所產(chǎn)生的影響與一次請求執(zhí)行的影響效果相同。
 
比如說,你調(diào)用下游接口做轉(zhuǎn)賬,然后接口網(wǎng)絡(luò)超時了(實際下游轉(zhuǎn)賬成功了),然后你用原來的流水號發(fā)起重試,如果下游接口不做冪等處理,那就會導(dǎo)致重復(fù)支付!
2. 哪些接口必須做冪等處理
判斷接口是否需要冪等處理,核心標(biāo)準(zhǔn)是 “重復(fù)調(diào)用是否會造成業(yè)務(wù)損失或數(shù)據(jù)異?!?/span>。一般資金交易、訂單相關(guān)等接口,都要實現(xiàn)冪等性,比如:
- 支付接口:如用戶下單支付、退款、轉(zhuǎn)賬等接口。若未做冪等,用戶重復(fù)發(fā)起支付請求,可能導(dǎo)致多筆扣款,引發(fā)嚴(yán)重的資金糾紛和用戶信任危機。
 - 訂單創(chuàng)建接口:若用戶因網(wǎng)絡(luò)延遲重復(fù)提交訂單,未做冪等會生成多個重復(fù)訂單,不僅增加庫存管理、物流配送的復(fù)雜度,還可能導(dǎo)致商家錯發(fā)貨物。
 
3. 哪些接口可酌情不做冪等處理
有些接口,業(yè)務(wù)場景簡單、數(shù)據(jù)也相對沒那么重要,重復(fù)調(diào)用概率比較小,且重復(fù)調(diào)用造成的影響也很小,則可考慮不做冪等處理,以降低開發(fā)成本和系統(tǒng)開銷。
典型場景如內(nèi)部OA系統(tǒng)的一些審批
內(nèi)部審批單(如員工報銷審批、部門采購審批)通常具備以下特點,使其無需強制冪等:
- 調(diào)用場景封閉:接口僅在企業(yè)內(nèi)部系統(tǒng)使用,調(diào)用者是內(nèi)部員工或指定系統(tǒng),不存在外部用戶因網(wǎng)絡(luò)問題重復(fù)提交的高頻場景。
 - 按鈕置灰:且內(nèi)部系統(tǒng)通常會有前端按鈕置灰、提交后跳轉(zhuǎn)等邏輯,從源頭減少重復(fù)調(diào)用。
 - 重復(fù)影響極小:即使因異常導(dǎo)致審批單重復(fù)提交,后續(xù)業(yè)務(wù)流程可通過 “人工校驗” 或 “狀態(tài)判斷” 修正,或者駁回流程(或者刪除申請流程)再重新發(fā)起~
 
當(dāng)然,并不是說這類型接口一定不做冪等處理,這個是結(jié)合你實際業(yè)務(wù)場景評估的,有些場景,冪等可以簡單處理的,比如:
一個狀態(tài)同步的接口,如 “將審批單狀態(tài)同步到 OA 系統(tǒng)”,即使重復(fù)同步,目標(biāo)系統(tǒng)可通過 “審批單 ID + 當(dāng)前狀態(tài)” 判斷是否需要更新,重復(fù)調(diào)用不會產(chǎn)生異常狀態(tài)。(這種方案就是狀態(tài)機冪等處理)
4. 通用方案:如何設(shè)計冪等接口?
對于重要接口的冪等處理,比如轉(zhuǎn)賬接口,跟大家分享一種比較通用的冪等方案哈~~
日常開發(fā)中,為了實現(xiàn)交易接口冪等,我是這樣實現(xiàn)的:
交易請求過來,我會先根據(jù)請求的唯一流水號bizSeq字段,先select一下數(shù)據(jù)庫的流水表
- 如果數(shù)據(jù)已經(jīng)存在,就攔截是重復(fù)請求,直接返回成功;
 - 如果數(shù)據(jù)不存在,就執(zhí)行
insert插入,如果insert成功,則直接返回成功,如果insert產(chǎn)生主鍵沖突異常,則捕獲異常,接著直接返回成功。 
流程圖如下:
圖片
偽代碼如下:
/**
 * 冪等處理
 */
Rsp idempotent(Request req){
  Object requestRecord =selectByBizSeq(bizSeq);
if(requestRecord !=null){
    //攔截是重復(fù)請求
     log.info("重復(fù)請求,直接返回成功,流水號:{}",bizSeq);
     return rsp;
  }
  try{
    insert(req);
  }catch(DuplicateKeyException e){
    //攔截是重復(fù)請求,直接返回成功
    log.info("主鍵沖突,是重復(fù)請求,直接返回成功,流水號:{}",bizSeq);
    return rsp;
  }
  //正常處理請求
  dealRequest(req);
return rsp;
}為什么前面已經(jīng)select查詢了,還需要try...catch...捕獲重復(fù)異常呢?
是因為高并發(fā)場景下,兩個請求去
select的時候,可能都沒查到,然后都走到insert的地方啦。
當(dāng)然,一般都是用唯一索引代替數(shù)據(jù)庫主鍵的哈,主要都是全局唯一的ID即可。我們之前的轉(zhuǎn)賬流水,冪等就是基于業(yè)務(wù)流水號作為唯一索引~~
5. 冪等涉及的分布式唯一ID如何生成
我們處理冪等的時候,就需要分布式的全局唯一ID,我們該如何去生成呢?你可以回想下,數(shù)據(jù)庫主鍵Id怎么生成的呢?
是的,我們可以使用UUID,但是UUID的缺點比較明顯,它字符串占用的空間比較大,生成的ID過于隨機,可讀性差,而且沒有遞增。
我們還可以使用雪花算法(Snowflake) 生成唯一性ID。
雪花算法是一種生成分布式全局唯一ID的算法,生成的ID稱為
Snowflake IDs。這種算法由Twitter創(chuàng)建,并用于推文的ID。
一個Snowflake ID有64位。
- 第1位:Java中l(wèi)ong的最高位是符號位代表正負(fù),正數(shù)是0,負(fù)數(shù)是1,一般生成ID都為正數(shù),所以默認(rèn)為0。
 - 接下來前41位是時間戳,表示了自選定的時期以來的毫秒數(shù)。
 - 接下來的10位代表計算機ID,防止沖突。
 - 其余12位代表每臺機器上生成ID的序列號,這允許在同一毫秒內(nèi)創(chuàng)建多個Snowflake ID。
 
雪花算法
當(dāng)然,全局唯一性的ID,還可以使用百度的Uidgenerator,或者美團(tuán)的Leaf。
6. 冪等設(shè)計的核心原則
所以,并不是所有的接口都要做冪等處理。本質(zhì)是 “業(yè)務(wù)影響” 與 “成本開銷” 的權(quán)衡。
- 如果是核心交易、資金相關(guān)接口,必須實現(xiàn)冪等,而且最好是基于流水表這種實現(xiàn),用流水表去跟蹤數(shù)據(jù)、數(shù)據(jù)狀態(tài)的流轉(zhuǎn)變化。
 - 對于內(nèi)部系統(tǒng)的一些簡單接口,如審批單,冪等則可以簡單處理,或者綜合評估可能影響后,不做冪等處理。在 “風(fēng)險控制” 與 “成本” 之間找到平衡。
 
我還是想強調(diào)一句:冪等設(shè)計的最終目標(biāo)不是 “所有接口都實現(xiàn)冪等”,而是 “讓關(guān)鍵接口安全,讓簡單接口高效”,以合理的設(shè)計保障系統(tǒng)穩(wěn)定,同時避免不必要的開銷。















 
 
 






 
 
 
 