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

Java冪等性的4種解決方案實(shí)戰(zhàn)講解(附通俗案例)

 更新時(shí)間:2025年07月29日 10:11:15   作者:要阿爾卑斯嗎.  
冪等性應(yīng)該是合格程序員的一個(gè)基因,在設(shè)計(jì)系統(tǒng)時(shí),是首要考慮的問題,尤其是在像支付寶,銀行,互聯(lián)網(wǎng)金融公司等涉及的都是錢的系統(tǒng),這篇文章主要介紹了Java冪等性的4種解決方案的相關(guān)資料,需要的朋友可以參考下

1. 什么是冪等?

冪等性(Idempotence)
在分布式、高并發(fā)場景中,同一操作無論執(zhí)行一次還是執(zhí)行多次,其對(duì)系統(tǒng)的最終影響是一樣的。

通俗解釋:

你點(diǎn)了個(gè)外賣,點(diǎn)了之后系統(tǒng)扣了你賬戶的錢。網(wǎng)絡(luò)延遲了,你又點(diǎn)了一次。

如果系統(tǒng)不控制冪等,平臺(tái)就會(huì)給你扣兩次錢,要求商家送兩份外賣。

所以,后端系統(tǒng)要判斷:

這是不是重復(fù)請(qǐng)求?是不是已經(jīng)處理過了?

如果是,直接返回成功;如果不是,才繼續(xù)處理。

Tips:“冪等性”和 “我想點(diǎn)多份外賣”到底有什么區(qū)別?

這兩個(gè)看起來都可能發(fā)送多個(gè)請(qǐng)求,但業(yè)務(wù)語義和系統(tǒng)處理方式完全不同。

一句話區(qū)別:

概念是重復(fù)請(qǐng)求?用戶想干嘛?后端該處理幾次?
冪等性是(用戶誤操作或系統(tǒng)重試)用戶只想下一個(gè)訂單只處理一次
我要點(diǎn)多份外賣否(用戶有意多次下單)用戶想下多個(gè)訂單處理多次

場景對(duì)比解釋

冪等性場景(系統(tǒng)要"去重")

點(diǎn)了一次外賣按鈕,但由于網(wǎng)絡(luò)慢,或者手抖點(diǎn)了兩下,系統(tǒng)收到了兩個(gè)一模一樣的下單請(qǐng)求。

但你心里是想買一份,不是兩份。

系統(tǒng)這時(shí)必須判斷:“這些請(qǐng)求是不是重復(fù)的?是不是我們已經(jīng)處理過了?”
如果是重復(fù)的,就不能再扣一次錢,不能再發(fā)一份外賣。
這就是冪等性要解決的核心問題:重復(fù)請(qǐng)求,只處理一次。

用戶主動(dòng)下多個(gè)訂單(系統(tǒng)不能去重)

你今天太餓了,就是想點(diǎn)兩份外賣:一份給自己吃,一份留著晚上吃。

所以你在 APP 上點(diǎn)了一次 → 下了一單;又點(diǎn)了一次 → 又下一單。

這兩次請(qǐng)求雖然是一樣的商品、一樣的地址,但它們是你主動(dòng)發(fā)出的兩個(gè)下單請(qǐng)求。

系統(tǒng)這時(shí)不能把這兩次請(qǐng)求當(dāng)成“重復(fù)”來過濾掉。
每次都要扣一次錢,生成一個(gè)訂單,通知商家送餐。

再簡單點(diǎn)說

比喻冪等性多份外賣
行為動(dòng)機(jī)“我點(diǎn)了一次,但系統(tǒng)誤以為我點(diǎn)了多次”“我自己就點(diǎn)了兩次”
你心里的目標(biāo)只想買一份就是要買兩份
系統(tǒng)正確做法識(shí)別重復(fù)請(qǐng)求,只處理一次每次都要處理,不能去重

一句話總結(jié)

冪等性是系統(tǒng)幫你“防止你不小心多下單”;
而“我要點(diǎn)多份外賣”是你故意多下單,系統(tǒng)必須每次都處理。

2. 實(shí)際例子:外賣平臺(tái)“確認(rèn)收貨”

我們來用“外賣平臺(tái)確認(rèn)收貨按鈕”這個(gè)更容易理解的例子:

業(yè)務(wù)背景:

用戶下單外賣,騎手送到之后,點(diǎn)擊“確認(rèn)收貨”按鈕。

后端的處理邏輯大致如下:

1. 更新訂單狀態(tài)為“已完成”
2. 給騎手發(fā)放配送提成
3. 給商家結(jié)算費(fèi)用
4. 給用戶發(fā)放優(yōu)惠券

問題來了:

用戶點(diǎn)了多次“確認(rèn)收貨”怎么辦?或者說用戶網(wǎng)絡(luò)不好點(diǎn)了2次?甚至 App 自動(dòng)重發(fā)了請(qǐng)求怎么辦?

如果沒處理冪等,就會(huì)導(dǎo)致:

  • 訂單狀態(tài)多次更新
  • 騎手提成發(fā)多次
  • 商家收到多筆錢
  • 用戶領(lǐng)好幾張券

→ 系統(tǒng)直接崩了,賬全亂了。

3. 數(shù)據(jù)庫設(shè)計(jì)

我們先準(zhǔn)備兩張表模擬場景:

-- 用戶表
CREATE TABLE t_user (
    id VARCHAR(50) PRIMARY KEY,
    name VARCHAR(50),
    coupon_count INT DEFAULT 0
);

-- 訂單表
CREATE TABLE t_order (
    id VARCHAR(50) PRIMARY KEY,
    user_id VARCHAR(50),
    status VARCHAR(20), -- "待收貨"、"已完成"
    version BIGINT DEFAULT 0
);

初始化數(shù)據(jù):

-- 用戶張三,沒有優(yōu)惠券
INSERT INTO t_user VALUES ('u1', '張三', 0);

-- 一筆待收貨訂單
INSERT INTO t_order VALUES ('o1', 'u1', '待收貨', 0);

4. 冪等的四種解決方案實(shí)戰(zhàn)講解

方案一:UPDATE帶條件字段控制(最常用)

原理:

利用數(shù)據(jù)庫行級(jí)鎖 + WHERE status = '待收貨' 來保證只有一次能成功更新狀態(tài)。

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

@Transactional
public String confirmOrder(String orderId) {
    // 查詢訂單狀態(tài)
    Order order = orderMapper.selectById(orderId);
    if ("已完成".equals(order.getStatus())) {
        return "SUCCESS";
    }

    // 核心:只更新待收貨的訂單
    int rows = orderMapper.updateStatusIfUnfinished(orderId, "已完成", "待收貨");

    if (rows == 1) {
        // 發(fā)優(yōu)惠券
        userMapper.addCoupon(order.getUserId(), 1);
        return "SUCCESS";
    } else {
        throw new RuntimeException("系統(tǒng)繁忙,請(qǐng)稍后重試");
    }
}

并發(fā)測試:

  • 100 并發(fā)同時(shí)確認(rèn)訂單

  • 最終結(jié)果:

    • 訂單狀態(tài):已完成
    • 用戶優(yōu)惠券數(shù)量:1(不會(huì)重復(fù)發(fā))

方案二:樂觀鎖version控制(適用于高并發(fā))

原理:

每次操作都要帶上 version 字段,只有匹配時(shí)才更新。

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

@Transactional(rollbackFor = Exception.class)
public String handleRecharge(String rechargeId) {
    // 根據(jù)rechargeId去找充值記錄,如果已處理過,則直接返回成功
    RechargePO rechargePo = rechargeMapper.selectById(rechargeId);
    if (rechargePo == null) {
        throw new IllegalArgumentException("充值記錄不存在");
    }

    // 充值記錄已處理過,直接返回成功
    if (rechargePo.getStatus() == 1) {
        return "SUCCESS";
    }

    // 開啟Spring事務(wù)(由 @Transactional 控制)

    // 在where后面要加 status = 0 這個(gè)條件;count表示影響行數(shù)
    int count = rechargeMapper.updateStatusToProcessed(rechargeId, 0, 1);

    // count = 1,表示上面sql執(zhí)行成功
    if (count != 1) {
        // 走到這里,說明有并發(fā),直接拋出異常
        throw new RuntimeException("系統(tǒng)繁忙,請(qǐng)重試");
    } else {
        // 給賬戶加錢
        accountMapper.increaseBalance(rechargePo.getAccountId(), rechargePo.getPrice());
    }

    // 提交Spring事務(wù)(由 @Transactional 控制)

    return "SUCCESS";
}

并發(fā)測試:

  • 最終結(jié)果:

    • 狀態(tài):已完成
    • 優(yōu)惠券:1

方案三:唯一約束表控制冪等(通用方案)

原理:

在操作前插入一條帶有唯一索引的冪等 key,插入失敗說明是重復(fù)請(qǐng)求。

新增輔助表:

CREATE TABLE t_idempotent (
    id VARCHAR(50) PRIMARY KEY,
    idempotent_key VARCHAR(100) NOT NULL,
    UNIQUE KEY uq_idempotent_key (idempotent_key)
);

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

@Transactional
public String confirmOrderWithUniqueKey(String orderId) {
    String idempotentKey = "order_confirm:" + orderId;

    try {
        idempotentMapper.insert(idempotentKey);
    } catch (DuplicateKeyException e) {
        return "SUCCESS";
    }

    Order order = orderMapper.selectById(orderId);
    if ("已完成".equals(order.getStatus())) return "SUCCESS";

    orderMapper.updateStatus(orderId, "已完成");
    userMapper.addCoupon(order.getUserId(), 1);
    return "SUCCESS";
}

并發(fā)測試:

  • 最終結(jié)果:

    • 插入 t_idempotent 成功 1 次
    • 優(yōu)惠券發(fā)放一次

方案四:分布式鎖(適合跨服務(wù)或非數(shù)據(jù)庫操作)

原理:

利用 Redis 鎖來控制并發(fā),只允許一個(gè)線程進(jìn)入。

實(shí)現(xiàn)步驟(偽代碼):

public String confirmOrderWithRedisLock(String orderId) {
    String lockKey = "lock:order:confirm:" + orderId;

    if (!redisLock.tryLock(lockKey, 5秒)) {
        throw new RuntimeException("系統(tǒng)繁忙,請(qǐng)稍后重試");
    }

    try {
        // 冪等處理邏輯
        ...
    } finally {
        redisLock.release(lockKey);
    }
}

并發(fā)測試:

  • Redis 鎖只允許 1 個(gè)線程進(jìn)入
  • 其余請(qǐng)求返回“系統(tǒng)繁忙”,防止重復(fù)處理

5. 四種方案對(duì)比總結(jié)

方案是否通用可靠性成本
方案1:UPDATE + 條件僅 DB 操作
方案2:樂觀鎖 version僅 DB 操作
方案3:唯一約束表通用
方案4:Redis 鎖非 DB 操作場景較高

6. 實(shí)戰(zhàn)建議 & 常見套路

  • 接口冪等性建議統(tǒng)一封裝,如方案3可以做成注解 + 攔截器模式
  • 支付回調(diào)、確認(rèn)訂單、接口重試場景、MQ消費(fèi) 等業(yè)務(wù)必須加冪等控制
  • 數(shù)據(jù)庫層冪等優(yōu)先,跨服務(wù)冪等使用分布式鎖或唯一冪等表

總結(jié) 

到此這篇關(guān)于Java冪等性的4種解決方案的文章就介紹到這了,更多相關(guān)Java冪等性解決方案內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot 2 實(shí)戰(zhàn):自定義啟動(dòng)運(yùn)行邏輯實(shí)例詳解

    Spring Boot 2 實(shí)戰(zhàn):自定義啟動(dòng)運(yùn)行邏輯實(shí)例詳解

    這篇文章主要介紹了Spring Boot 2 實(shí)戰(zhàn):自定義啟動(dòng)運(yùn)行邏輯,結(jié)合實(shí)例形式詳細(xì)分析了Spring Boot 2自定義啟動(dòng)運(yùn)行邏輯詳細(xì)操作技巧與注意事項(xiàng),需要的朋友可以參考下
    2020-05-05
  • SpringBoot結(jié)合Redis實(shí)現(xiàn)緩存

    SpringBoot結(jié)合Redis實(shí)現(xiàn)緩存

    本文主要介紹了SpringBoot結(jié)合Redis實(shí)現(xiàn)緩存,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • SpringBoot加載配置6種方式分析

    SpringBoot加載配置6種方式分析

    這篇文章主要介紹了SpringBoot加載配置6種方式分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Java實(shí)現(xiàn)圖片對(duì)比功能

    Java實(shí)現(xiàn)圖片對(duì)比功能

    個(gè)人從來沒有研究過圖像學(xué),也沒看過什么論文或者相關(guān)文檔,寫這個(gè)完全是靠google和百度,自己寫了個(gè)實(shí)驗(yàn)了下,測試用例也少,估計(jì)有大BUG的存在,所以看的人權(quán)當(dāng)學(xué)習(xí)交流,切勿生產(chǎn)使用。
    2014-09-09
  • 深入理解hibernate的三種狀態(tài)

    深入理解hibernate的三種狀態(tài)

    本篇文章主要介紹了深入理解hibernate的三種狀態(tài) ,主要包括了transient(瞬時(shí)狀態(tài)),persistent(持久化狀態(tài))以及detached(離線狀態(tài)),有興趣的同學(xué)可以了解一下
    2017-05-05
  • java實(shí)現(xiàn)excel導(dǎo)出合并單元格的步驟詳解

    java實(shí)現(xiàn)excel導(dǎo)出合并單元格的步驟詳解

    這篇文章主要介紹了java實(shí)現(xiàn)excel導(dǎo)出合并單元格,通過使用Apache POI庫,我們可以方便地創(chuàng)建Excel文件、填充數(shù)據(jù)、合并單元格和導(dǎo)出Excel文件,需要的朋友可以參考下
    2023-04-04
  • 一文搞懂Java頂層類之Object類的使用

    一文搞懂Java頂層類之Object類的使用

    java.lang.Object類是Java語言中的根類,即所有類的父類。它中描述的所有方法子類都可以使用。本文主要介紹了Object類中toString和equals方法的使用,感興趣的小伙伴可以了解一下
    2022-11-11
  • java網(wǎng)絡(luò)編程之socket網(wǎng)絡(luò)編程示例(服務(wù)器端/客戶端)

    java網(wǎng)絡(luò)編程之socket網(wǎng)絡(luò)編程示例(服務(wù)器端/客戶端)

    這篇文章主要介紹了java socket網(wǎng)絡(luò)編程的示例,分為服務(wù)器端和客戶端,大家參考使用吧
    2014-01-01
  • 如何基于java語言實(shí)現(xiàn)八皇后問題

    如何基于java語言實(shí)現(xiàn)八皇后問題

    這篇文章主要介紹了如何基于java語言實(shí)現(xiàn)八皇后問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Java多線程中的ThreadPoolExecutor使用解析

    Java多線程中的ThreadPoolExecutor使用解析

    這篇文章主要介紹了Java多線程中的ThreadPoolExecutor使用解析,作為線程池的緩沖,當(dāng)新增線程超過maximumPoolSize時(shí),會(huì)將新增線程暫時(shí)存放到該隊(duì)列中,需要的朋友可以參考下
    2023-12-12

最新評(píng)論

连平县| 海南省| 开封市| 合水县| 永宁县| 津市市| 大理市| 龙泉市| 眉山市| 长海县| 普兰县| 玉门市| 峡江县| 黄冈市| 泽普县| 甘谷县| 烟台市| 津市市| 长泰县| 舟山市| 辽宁省| 皮山县| 西乌珠穆沁旗| 怀远县| 昌都县| 平江县| 金华市| 依安县| 南郑县| 浦江县| 会宁县| 大同市| 绵阳市| 沂源县| 皮山县| 公安县| 平邑县| 金寨县| 张掖市| 和龙市| 仁寿县|