最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot項(xiàng)目中實(shí)現(xiàn)接口冪等的代碼詳解

 更新時(shí)間:2025年09月25日 08:35:34   作者:IT橘子皮  
在 Spring Boot 項(xiàng)目中實(shí)現(xiàn)接口冪等性是確保系統(tǒng)數(shù)據(jù)一致性和可靠性的關(guān)鍵手段,尤其在支付、訂單等核心業(yè)務(wù)場(chǎng)景中,下面我將為你介紹幾種常見(jiàn)的實(shí)現(xiàn)方案、選擇建議以及一些注意事項(xiàng),需要的朋友可以參考下

前言

在 Spring Boot 項(xiàng)目中實(shí)現(xiàn)接口冪等性是確保系統(tǒng)數(shù)據(jù)一致性和可靠性的關(guān)鍵手段,尤其在支付、訂單等核心業(yè)務(wù)場(chǎng)景中。下面我將為你介紹幾種常見(jiàn)的實(shí)現(xiàn)方案、選擇建議以及一些注意事項(xiàng)。

方案核心機(jī)制實(shí)現(xiàn)復(fù)雜度性能影響外部依賴典型適用場(chǎng)景
?Token 令牌?預(yù)生成一次性令牌,使用后即焚Redis用戶下單、支付
?數(shù)據(jù)庫(kù)唯一索引?利用數(shù)據(jù)庫(kù)唯一約束防止重復(fù)數(shù)據(jù)插入數(shù)據(jù)庫(kù)數(shù)據(jù)插入操作,如支付記錄創(chuàng)建
?數(shù)據(jù)庫(kù)樂(lè)觀鎖?通過(guò)版本號(hào)控制更新,避免重復(fù)更新數(shù)據(jù)庫(kù)更新操作,如庫(kù)存扣減
?分布式鎖?加鎖確保同一業(yè)務(wù)標(biāo)識(shí)的請(qǐng)求串行處理Redis/ZooKeeper高并發(fā)場(chǎng)景,如秒殺、搶券
?請(qǐng)求摘要?計(jì)算請(qǐng)求參數(shù)哈希值作為冪等鍵Redis參數(shù)固定的重復(fù)請(qǐng)求

1. Token 令牌機(jī)制

這種方式要求客戶端在發(fā)起業(yè)務(wù)請(qǐng)求前,先獲取一個(gè)服務(wù)器頒發(fā)的唯一令牌(Token),并在后續(xù)請(qǐng)求中攜帶該令牌。

?實(shí)現(xiàn)步驟:

服務(wù)端提供獲取 Token 的接口,生成一個(gè)唯一 Token(如 UUID)并存入 Redis,設(shè)置合理的過(guò)期時(shí)間。

客戶端調(diào)用業(yè)務(wù)接口時(shí),在 HTTP Header(如 Idempotent-Token)中攜帶此 Token。

服務(wù)端攔截請(qǐng)求,檢查 Redis 中是否存在該 Token:

  • 若存在,則刪除該 Token(redis.delete(key))并繼續(xù)執(zhí)行業(yè)務(wù)邏輯。
  • 若不存在,說(shuō)明是重復(fù)請(qǐng)求,直接返回重復(fù)操作結(jié)果。

?優(yōu)點(diǎn)?:對(duì)業(yè)務(wù)代碼侵入性相對(duì)較小,安全性較高,能有效防止重復(fù)提交。

?缺點(diǎn)?:需額外一次獲取 Token 的請(qǐng)求,依賴 Redis 等外部存儲(chǔ)。

?代碼示意 (使用 AOP 簡(jiǎn)化)??:

// 1. 自定義冪等注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    long timeout() default 10; // 過(guò)期時(shí)間,單位分鐘
}

// 2. AOP 實(shí)現(xiàn)
@Aspect
@Component
public class IdempotentAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String token = request.getHeader("Idempotent-Token");
        // ... 校驗(yàn) Token 是否為空...
        String key = "idempotent:token:" + token;
        // 原子性驗(yàn)證并刪除 Token
        Boolean isDeleted = redisTemplate.delete(key);
        if (Boolean.FALSE.equals(isDeleted)) {
            throw new BusinessException("請(qǐng)求已處理,請(qǐng)勿重復(fù)提交");
        }
        return joinPoint.proceed();
    }
}

// 3. 在控制器方法上使用注解
@PostMapping("/order")
@Idempotent(timeout = 10)
public Result createOrder(@RequestBody OrderRequest request) {
    // 業(yè)務(wù)邏輯
}

使用 AOP 可以使業(yè)務(wù)代碼更簡(jiǎn)潔。

2. 數(shù)據(jù)庫(kù)唯一約束

利用數(shù)據(jù)庫(kù)本身的唯一性索引來(lái)保證重復(fù)請(qǐng)求不會(huì)插入多條記錄。

?實(shí)現(xiàn)步驟:

  1. 在數(shù)據(jù)庫(kù)表中為能唯一標(biāo)識(shí)業(yè)務(wù)的字段(如訂單號(hào) order_no、支付流水號(hào) transaction_id)創(chuàng)建唯一索引。
  2. 執(zhí)行業(yè)務(wù)邏輯插入數(shù)據(jù)時(shí),如果重復(fù)插入,數(shù)據(jù)庫(kù)會(huì)拋出 DataIntegrityViolationException等異常。
  3. 捕獲該異常,并根據(jù)業(yè)務(wù)需求處理(如查詢已存在的記錄并返回)。

?優(yōu)點(diǎn)?:實(shí)現(xiàn)簡(jiǎn)單,可靠性高,利用數(shù)據(jù)庫(kù)特性,強(qiáng)一致性。

?缺點(diǎn)?:依賴于數(shù)據(jù)庫(kù),高并發(fā)時(shí)可能產(chǎn)生較多異常,需合理處理。

?代碼示意?:

@Service
public class PaymentService {
    @Autowired
    private PaymentRepository paymentRepository;

    @Transactional
    public PaymentResponse processPayment(PaymentRequest request) {
        try {
            Payment payment = new Payment();
            payment.setOrderNo(request.getOrderNo());
            payment.setTransactionId(request.getTransactionId()); // 此字段有唯一索引
            payment.setAmount(request.getAmount());
            paymentRepository.save(payment);
            // ...其他支付邏輯...
            return new PaymentResponse(true, "支付成功");
        } catch (DataIntegrityViolationException e) {
            // 捕獲唯一約束違反異常
            Payment existingPayment = paymentRepository.findByTransactionId(request.getTransactionId());
            return new PaymentResponse(true, "支付已處理", existingPayment.getId());
        }
    }
}

通常需要結(jié)合 @Transactional注解保證事務(wù)性。

3. 數(shù)據(jù)庫(kù)樂(lè)觀鎖

通過(guò)數(shù)據(jù)庫(kù)的版本號(hào)字段實(shí)現(xiàn),適用于更新操作。

?實(shí)現(xiàn)步驟:

  1. 在數(shù)據(jù)庫(kù)表中增加一個(gè) version字段。
  2. 更新數(shù)據(jù)時(shí),在 SQL 語(yǔ)句中同時(shí)更新版本號(hào)并校驗(yàn)舊版本號(hào):update table set column = new_value, version = version + 1 where id = #{id} and version = #{oldVersion}。
  3. 根據(jù)更新返回的影響行數(shù)判斷是否更新成功。如果影響行數(shù)為 0,說(shuō)明版本號(hào)不符或記錄已被更新,可能是重復(fù)請(qǐng)求或數(shù)據(jù)沖突。

?優(yōu)點(diǎn)?:避免使用悲觀鎖的性能開(kāi)銷,適合讀多寫少的場(chǎng)景。

?缺點(diǎn)?:如果并發(fā)沖突多,失敗次數(shù)會(huì)增加。

?代碼示意 (MyBatis-Plus 示例)??:

// 實(shí)體類
@Data
public class InventoryItemDO {
    private Long id;
    private Integer sellableQuantity;
    @Version // 樂(lè)觀鎖版本號(hào)字段注解
    private Integer version;
}

// Mapper 接口中使用
public interface InventoryItemMapper extends BaseMapper<InventoryItemDO> {
    // MyBatis-Plus 會(huì)自動(dòng)在更新時(shí)帶上版本條件
}

// Service 中調(diào)用 updateById 方法時(shí),MyBatis-Plus 會(huì)自動(dòng)處理版本號(hào)

4. 分布式鎖

在分布式環(huán)境下,使用分布式鎖(如基于 Redis)確保對(duì)同一業(yè)務(wù)鍵的操作是串行的。

?實(shí)現(xiàn)步驟:

  1. 獲取鎖:在執(zhí)行業(yè)務(wù)邏輯前,嘗試獲取一個(gè)分布式鎖,鎖的 Key 通常由業(yè)務(wù)標(biāo)識(shí)生成(如 lock:order:1001)。
  2. 處理業(yè)務(wù):獲取鎖成功后,先檢查是否已處理過(guò)(可選,二次校驗(yàn)),再執(zhí)行業(yè)務(wù)邏輯。
  3. 釋放鎖:最后釋放分布式鎖。

?優(yōu)點(diǎn)?:適用于分布式和高并發(fā)場(chǎng)景,能有效保證強(qiáng)一致性。

?缺點(diǎn)?:實(shí)現(xiàn)相對(duì)復(fù)雜,依賴外部分布式鎖服務(wù)(如 Redis),獲取釋放鎖會(huì)增加響應(yīng)時(shí)間。

?代碼示意 (使用 Redisson)??:

@Service
public class SeckillService {
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private OrderRepository orderRepository;

    public SeckillResponse seckill(SeckillRequest request) {
        String lockKey = "lock:seckill:" + request.getGoodsId();
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { // 嘗試獲取鎖,等待3秒,鎖持有10秒
                // 二次校驗(yàn):查詢是否已下單,防止重復(fù)處理
                if (orderRepository.existsByUserIdAndGoodsId(request.getUserId(), request.getGoodsId())) {
                    return SeckillResponse.alreadyOrdered();
                }
                // 執(zhí)行業(yè)務(wù)邏輯...
                return SeckillResponse.success();
            } else {
                throw new BusinessException("系統(tǒng)繁忙,請(qǐng)稍后再試");
            }
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

分布式鎖常與唯一索引等其他冪等方案結(jié)合使用,形成雙重保障。

5. 請(qǐng)求摘要(內(nèi)容指紋)

將請(qǐng)求參數(shù)(如請(qǐng)求體)通過(guò)哈希算法(如 MD5、SHA-256)生成一個(gè)唯一摘要,以此作為冪等鍵。

?實(shí)現(xiàn)步驟:

  1. 接收到請(qǐng)求后,計(jì)算請(qǐng)求參數(shù)的摘要(如對(duì) JSON 請(qǐng)求體計(jì)算 MD5)。
  2. 以該摘要為 Key,嘗試在 Redis 中執(zhí)行 setIfAbsent(SETNX)操作。
  3. 如果設(shè)置成功,說(shuō)明是首次請(qǐng)求,執(zhí)行業(yè)務(wù)邏輯。
  4. 如果設(shè)置失?。↘ey 已存在),說(shuō)明是重復(fù)請(qǐng)求,直接返回之前的處理結(jié)果或錯(cuò)誤信息。

?優(yōu)點(diǎn)?:客戶端無(wú)需額外操作,對(duì)用戶透明。

?缺點(diǎn)?:計(jì)算摘要有性能損耗,需確保計(jì)算摘要的參數(shù)能唯一標(biāo)識(shí)業(yè)務(wù)操作,否則可能誤判。

?代碼示意?:

@PostMapping("/transfer")
public Result transfer(@RequestBody TransferRequest request) {
    String idempotentKey = generateIdempotentKey(request); // 根據(jù)請(qǐng)求參數(shù)生成摘要
    String redisKey = "idempotent:digest:" + idempotentKey;
    // 原子性地設(shè)置鍵值,僅當(dāng)鍵不存在時(shí)
    Boolean isFirstRequest = redisTemplate.opsForValue().setIfAbsent(redisKey, "processing", 24, TimeUnit.HOURS);
    if (Boolean.FALSE.equals(isFirstRequest)) {
        // 重復(fù)請(qǐng)求,可根據(jù)業(yè)務(wù)查詢之前的結(jié)果或直接返回錯(cuò)誤
        return Result.fail("請(qǐng)勿重復(fù)提交");
    }
    try {
        // 執(zhí)行業(yè)務(wù)邏輯...
        return Result.success();
    } finally {
        // 可選:業(yè)務(wù)成功完成后,可以更新Redis中的狀態(tài);若失敗,可刪除鍵允許重試
        // redisTemplate.delete(redisKey);
    }
}

此方法的關(guān)鍵在于生成冪等鍵的算法要能準(zhǔn)確識(shí)別重復(fù)請(qǐng)求。

如何選擇冪等方案?

選擇哪種方案取決于你的具體業(yè)務(wù)場(chǎng)景、性能要求和系統(tǒng)架構(gòu):

  • ?Token 機(jī)制?:非常適合用戶交互相關(guān)的操作,如防止表單重復(fù)提交、訂單重復(fù)創(chuàng)建。需要客戶端配合。
  • ?數(shù)據(jù)庫(kù)唯一約束/索引?:最適合數(shù)據(jù)插入類的操作,簡(jiǎn)單、可靠、成本低。是許多場(chǎng)景的首選。
  • ?樂(lè)觀鎖?:非常適合數(shù)據(jù)更新類的操作,特別是在并發(fā)更新同一數(shù)據(jù)的場(chǎng)景。
  • ?分布式鎖?:適用于分布式環(huán)境下需要強(qiáng)一致性的復(fù)雜業(yè)務(wù)邏輯,或高并發(fā)秒殺場(chǎng)景。要注意性能開(kāi)銷和死鎖問(wèn)題。
  • ?請(qǐng)求摘要?:適合接口回調(diào)、第三方通知等客戶端不可控,但請(qǐng)求內(nèi)容固定的場(chǎng)景。

注意事項(xiàng)

  1. ?冪等鍵的生成與傳遞?:冪等鍵(Token、業(yè)務(wù)ID等)需要保證全局唯一性。常見(jiàn)的生成方式有 UUID、雪花算法(Snowflake)等。同時(shí),需要和調(diào)用方約定好傳遞方式,如放在 HTTP Header(如 Idempotency-Key)中。
  2. ?異常處理與重試?:設(shè)計(jì)冪等時(shí)要考慮業(yè)務(wù)失敗的情況。例如,在 Token 機(jī)制中,如果業(yè)務(wù)執(zhí)行失敗,可能需要歸還 Token? 允許客戶端重試。在請(qǐng)求摘要模式中,也需考慮失敗后是否刪除 Redis 中的鍵。
  3. ?冪等與并發(fā)的區(qū)別?:冪等主要解決重復(fù)請(qǐng)求問(wèn)題(可能非并發(fā)),而并發(fā)控制解決同時(shí)操作的問(wèn)題。兩者常結(jié)合使用,例如用分布式鎖處理并發(fā),用唯一索引保證最終冪等。
  4. ?HTTP 方法的冪等性?:了解 RESTful API 中不同 HTTP 方法的冪等性語(yǔ)義對(duì)你設(shè)計(jì)接口有幫助(例如,GET、PUT、DELETE 通常是冪等的,而 POST 不是)。

總結(jié)

實(shí)現(xiàn)接口冪等性是構(gòu)建健壯分布式系統(tǒng)的關(guān)鍵。你可以根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景選擇最合適的方案,很多時(shí)候這些方案會(huì)組合使用以達(dá)到最佳效果。

以上就是SpringBoot項(xiàng)目中實(shí)現(xiàn)接口冪等的代碼詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot實(shí)現(xiàn)接口冪等的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring Boot 3 整合 RustFS 實(shí)現(xiàn)分布式文件存儲(chǔ)的常見(jiàn)問(wèn)題及解決方案

    Spring Boot 3 整合 RustFS 實(shí)現(xiàn)分布式文件存儲(chǔ)的常

    本文詳解SpringBoot3與RustFS分布式存儲(chǔ)系統(tǒng)的整合方法,涵蓋環(huán)境部署、配置、核心代碼實(shí)現(xiàn)及高級(jí)功能擴(kuò)展,最終構(gòu)建高性能、可擴(kuò)展的文件存儲(chǔ)服務(wù),強(qiáng)調(diào)其兼容S3協(xié)議、開(kāi)源特性及成本優(yōu)勢(shì),感興趣的朋友跟隨小編一起看看吧
    2025-09-09
  • spring boot 啟動(dòng)過(guò)程步驟詳解

    spring boot 啟動(dòng)過(guò)程步驟詳解

    本文分步驟給大家介紹了spring boot 啟動(dòng)過(guò)程,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2025-11-11
  • java處理日期的工具類DateUtil

    java處理日期的工具類DateUtil

    這篇文章主要為大家詳細(xì)介紹了java處理日期的工具類DateUtil,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-10-10
  • Spring常用注解匯總

    Spring常用注解匯總

    這篇文章主要介紹了Spring常用注解匯總,需要的朋友可以參考下
    2014-08-08
  • Mybatis-plus更新字段update_by失敗問(wèn)題

    Mybatis-plus更新字段update_by失敗問(wèn)題

    在遇到實(shí)體類字段更新不正確的問(wèn)題時(shí),首先復(fù)現(xiàn)問(wèn)題,確定受影響的字段,使用Debug模式查看變量的實(shí)際賦值情況,通過(guò)查看執(zhí)行的SQL語(yǔ)句,確認(rèn)SQL是否正確反映了預(yù)期的更新,如出現(xiàn)問(wèn)題,可以參考Mybatis-plus官網(wǎng)的解決方案
    2024-09-09
  • mybatis教程之增刪改查_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    mybatis教程之增刪改查_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要介紹了mybatis教程之增刪改查,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • 如何配置Eclipse實(shí)現(xiàn)定制登錄界面

    如何配置Eclipse實(shí)現(xiàn)定制登錄界面

    本文介紹了如何配置Eclipse實(shí)現(xiàn)定制登錄界面,每一步的講解都很細(xì)致,感興趣的小伙伴可以閱讀一下
    2015-07-07
  • mybatis-generator-gui根據(jù)需求改動(dòng)示例

    mybatis-generator-gui根據(jù)需求改動(dòng)示例

    這篇文章主要為大家介紹了mybatis-generator-gui根據(jù)需求改動(dòng)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 基于Java class對(duì)象說(shuō)明、Java 靜態(tài)變量聲明和賦值說(shuō)明(詳解)

    基于Java class對(duì)象說(shuō)明、Java 靜態(tài)變量聲明和賦值說(shuō)明(詳解)

    下面小編就為大家?guī)?lái)一篇基于Java class對(duì)象說(shuō)明、Java 靜態(tài)變量聲明和賦值說(shuō)明(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • Java實(shí)現(xiàn)使用Websocket發(fā)送消息詳細(xì)代碼舉例

    Java實(shí)現(xiàn)使用Websocket發(fā)送消息詳細(xì)代碼舉例

    這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)使用Websocket發(fā)送消息的相關(guān)資料,WebSocket是一種協(xié)議,用于在Web應(yīng)用程序和服務(wù)器之間建立實(shí)時(shí)、雙向的通信連接,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-05-05

最新評(píng)論

微山县| 白河县| 抚顺市| 民乐县| 靖安县| 怀来县| 永仁县| 瓦房店市| 龙江县| 离岛区| 新郑市| 鄂尔多斯市| 永康市| 夏邑县| 繁峙县| 阳信县| 商洛市| 岚皋县| 平遥县| 桃园市| 大足县| 利川市| 彝良县| 岑溪市| 阳西县| 昭通市| 长汀县| 交城县| 固安县| 丹寨县| 青阳县| 阿勒泰市| 宣汉县| 石台县| 兴海县| 石泉县| 池州市| 沐川县| 祁阳县| 建昌县| 进贤县|