對(duì)Java接口進(jìn)行冪等性控制的三種方法
前言
在做分布式系統(tǒng)、支付系統(tǒng)、電商秒殺等實(shí)際項(xiàng)目中,我們經(jīng)常會(huì)遇到接口被重復(fù)調(diào)用的問(wèn)題。比如:
- 用戶(hù)支付時(shí)多次點(diǎn)擊“支付”按鈕;
- 網(wǎng)絡(luò)重試機(jī)制導(dǎo)致接口多次請(qǐng)求;
- 消息隊(duì)列消費(fèi)失敗后自動(dòng)重試。
這些行為如果沒(méi)有控制好冪等性,輕則產(chǎn)生重復(fù)數(shù)據(jù),重則產(chǎn)生資金損失、庫(kù)存混亂等嚴(yán)重問(wèn)題。
今天我們深入聊聊:如何在 Java 中實(shí)現(xiàn)接口的冪等性控制?
什么是冪等性?
**冪等性(Idempotent)**是指一個(gè)接口被調(diào)用多次,結(jié)果與調(diào)用一次的效果相同。
GET /order/123—— 天然冪等。POST /order/create—— 非冪等,需要控制。
冪等性控制的三大核心手段
在我的項(xiàng)目實(shí)戰(zhàn)中,主要使用以下三種方式實(shí)現(xiàn)接口冪等控制:
- 數(shù)據(jù)庫(kù)唯一索引控制
- Redis 防重復(fù)提交
- 冪等 Token 機(jī)制
下面我們逐個(gè)拆解原理與代碼實(shí)現(xiàn)。
1、數(shù)據(jù)庫(kù)唯一索引控制(經(jīng)典可靠)
原理
利用數(shù)據(jù)庫(kù)的唯一約束,防止插入重復(fù)數(shù)據(jù)。
適用場(chǎng)景
- 創(chuàng)建訂單、支付單等“只允許一次成功”的業(yè)務(wù)操作。
- 數(shù)據(jù)庫(kù)操作為最終落地。
實(shí)現(xiàn)
假設(shè)有個(gè)訂單表 order,我們希望一個(gè) clientOrderNo(客戶(hù)端訂單號(hào))只能插入一次。
ALTER TABLE t_order ADD UNIQUE KEY uk_client_order_no (client_order_no);
Java 代碼示例
public void createOrder(String clientOrderNo, OrderDTO dto) {
try {
Order order = new Order();
order.setClientOrderNo(clientOrderNo);
order.setAmount(dto.getAmount());
order.setUserId(dto.getUserId());
orderRepository.insert(order); // 會(huì)觸發(fā)唯一索引約束
} catch (DuplicateKeyException e) {
log.warn("訂單已存在,冪等處理: {}", clientOrderNo);
// 查詢(xún)已有訂單并返回,保持冪等
Order existing = orderRepository.findByClientOrderNo(clientOrderNo);
return existing;
}
}
總結(jié)
優(yōu)點(diǎn):
- 簡(jiǎn)單可靠,數(shù)據(jù)庫(kù)層強(qiáng)力保證。
缺點(diǎn):
- 粒度粗,如果涉及復(fù)雜流程(如多表插入)需結(jié)合事務(wù)控制。
2、Redis 防重復(fù)提交(輕量方案)
原理
利用 Redis 的原子性,通過(guò) SETNX 命令設(shè)置唯一鍵,控制某個(gè)請(qǐng)求只處理一次。
適用場(chǎng)景
- 表單防重復(fù)提交。
- 接口短時(shí)間內(nèi)禁止重復(fù)請(qǐng)求。
Java 實(shí)現(xiàn)
public boolean tryAcquireRequest(String key, long expireSeconds) {
// 原子設(shè)置鍵 + 過(guò)期時(shí)間,表示該請(qǐng)求已處理
return Boolean.TRUE.equals(
redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofSeconds(expireSeconds))
);
}
Controller 示例
@PostMapping("/api/pay")
public ResponseEntity<?> doPay(@RequestBody PayRequest request) {
String redisKey = "pay:" + request.getUserId() + ":" + request.getOrderId();
if (!idempotentService.tryAcquireRequest(redisKey, 30)) {
return ResponseEntity.status(HttpStatus.CONFLICT).body("重復(fù)請(qǐng)求,請(qǐng)稍后再試");
}
// 執(zhí)行支付邏輯
paymentService.pay(request.getOrderId());
return ResponseEntity.ok("支付成功");
}
總結(jié)
優(yōu)點(diǎn):
- 高性能,適合高并發(fā)。
- 不依賴(lài)數(shù)據(jù)庫(kù)操作。
缺點(diǎn):
- Redis異常時(shí)無(wú)法保證冪等。
- 需手動(dòng)構(gòu)造唯一 key。
3、冪等 Token 機(jī)制(前后端協(xié)作)
原理
前端首次請(qǐng)求時(shí)從服務(wù)端獲取一個(gè) token,提交表單時(shí)附帶該 token,服務(wù)端驗(yàn)證 token 是否已被使用。
適用場(chǎng)景
- 表單提交、下單等需要用戶(hù)主動(dòng)確認(rèn)的操作。
- 控制用戶(hù)操作行為。
實(shí)現(xiàn)步驟
1. 生成冪等 token(后端)
@GetMapping("/token")
public String generateToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("token:" + token, "1", Duration.ofMinutes(5));
return token;
}
2. 提交接口驗(yàn)證 token
@PostMapping("/submit")
public ResponseEntity<?> submitForm(@RequestParam String token, @RequestBody FormDTO form) {
String redisKey = "token:" + token;
// Redis 的 delete 操作返回 1 表示成功刪除(即 token 存在)
Boolean success = redisTemplate.delete(redisKey);
if (Boolean.FALSE.equals(success)) {
return ResponseEntity.status(HttpStatus.CONFLICT).body("請(qǐng)勿重復(fù)提交");
}
// 執(zhí)行業(yè)務(wù)邏輯
formService.process(form);
return ResponseEntity.ok("提交成功");
}
總結(jié)
優(yōu)點(diǎn):
- 精準(zhǔn)控制用戶(hù)行為。
- 非常適合前后端協(xié)作系統(tǒng)。
缺點(diǎn):
- 實(shí)現(xiàn)略復(fù)雜,強(qiáng)依賴(lài) Redis。
- 前端需配合使用。
實(shí)戰(zhàn)建議
| 方法 | 場(chǎng)景適用 | 冪等級(jí)別 | 復(fù)雜度 | 推薦備注 |
|---|---|---|---|---|
| 數(shù)據(jù)庫(kù)唯一索引 | 訂單、支付等數(shù)據(jù)落庫(kù) | 高 | 低 | 推薦首選 |
| Redis 防重復(fù)提交 | 高并發(fā)接口、表單提交 | 中 | 中 | 配合使用 |
| Token 機(jī)制 | 用戶(hù)行為防重復(fù) | 中 | 高 | 前后端配合使用 |
總結(jié)
冪等性控制不是一個(gè)「萬(wàn)能解」,而是需要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景選擇合適的方案。作為有多年經(jīng)驗(yàn)的后端工程師,我通常會(huì):
- 數(shù)據(jù)插入場(chǎng)景首選數(shù)據(jù)庫(kù)唯一索引;
- 接口限流或重復(fù)提交保護(hù)使用 Redis;
- 用戶(hù)行為防重復(fù)引入 Token 機(jī)制。
冪等性雖“小”,但不控制好,問(wèn)題很“大”。希望本文對(duì)你理解冪等控制的原理和實(shí)現(xiàn)有所幫助。
以上就是對(duì)Java接口進(jìn)行冪等性控制的三種方法的詳細(xì)內(nèi)容,更多關(guān)于Java接口冪等性控制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IntelliJ IDEA中打開(kāi)拼寫(xiě)檢查與忽略提示曲線的方法
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA中打開(kāi)拼寫(xiě)檢查與忽略提示曲線的方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10
分析 Java Stream 的 peek使用實(shí)踐與副作用處理方案
Stream API的peek操作是中間操作,用于觀察元素但不終止流,其副作用風(fēng)險(xiǎn)包括線程安全、順序混亂及性能問(wèn)題,合理使用場(chǎng)景有限,本文給大家介紹Java Stream的peek使用實(shí)踐與副作用,感興趣的朋友跟隨小編一起看看吧2025-09-09
Java并發(fā)容器之ConcurrentLinkedQueue詳解
這篇文章主要介紹了Java并發(fā)容器之ConcurrentLinkedQueue詳解,加鎖隊(duì)列的實(shí)現(xiàn)較為簡(jiǎn)單,這里就略過(guò),我們來(lái)重點(diǎn)來(lái)解讀一下非阻塞隊(duì)列,2023-12-12
從點(diǎn)到面, 下面我們來(lái)看下非阻塞隊(duì)列經(jīng)典實(shí)現(xiàn)類(lèi)ConcurrentLinkedQueue,需要的朋友可以參考下
SpringBoot詳解整合Spring?Boot?Admin實(shí)現(xiàn)監(jiān)控功能
這篇文章主要介紹了SpringBoot整合Spring?Boot?Admin實(shí)現(xiàn)服務(wù)監(jiān)控,內(nèi)容包括Server端服務(wù)開(kāi)發(fā),Client端服務(wù)開(kāi)發(fā)其中Spring?Boot?Admin還可以對(duì)其監(jiān)控的服務(wù)提供告警功能,如服務(wù)宕機(jī)時(shí),可以及時(shí)以郵件方式通知運(yùn)維人員,感興趣的朋友跟隨小編一起看看吧2022-07-07
java 對(duì)稱(chēng)加密算法實(shí)現(xiàn)詳解
這篇文章主要介紹了java 對(duì)稱(chēng)加密算法實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
15道非常經(jīng)典的Java面試題 附詳細(xì)答案
這篇文章主要為大家推薦了15道非常經(jīng)典的Java面試題,附詳細(xì)答案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10

