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























