偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

分布式鎖+事務(wù)=災(zāi)難?不要把鎖加在事務(wù)內(nèi)?。。?/h1>

數(shù)據(jù)庫
分布式鎖和事務(wù)就像一對(duì)歡喜冤家,既相互依賴,又相互排斥。鎖可以保證數(shù)據(jù)的一致性,但如果用錯(cuò)了地方,就會(huì)成為性能瓶頸。

兄弟們,最近有個(gè)電商朋友跟我哭訴,他們搞了個(gè)茅臺(tái)搶購活動(dòng),結(jié)果系統(tǒng)直接炸鍋了。用戶瘋狂下單,庫存直接被扣成了負(fù)數(shù),客服電話被打爆,技術(shù)團(tuán)隊(duì)連夜搶修。我問他用了什么防超賣方案,他說:"我用了分布式鎖?。≡诳蹘齑娴姆椒ㄉ霞恿?@Transactional 注解,然后用 Redisson 的 RLock 鎖住商品 ID,應(yīng)該萬無一失啊!"

我心里咯噔一下,這場(chǎng)景我太熟悉了。就像你在超市推了個(gè)購物車去排隊(duì)結(jié)賬,結(jié)果購物車太大卡在通道里,后面的人都走不動(dòng)。鎖加在事務(wù)里,就像把購物車(鎖)和結(jié)賬流程(事務(wù))綁在一起,一旦事務(wù)執(zhí)行時(shí)間長(zhǎng),鎖就成了性能瓶頸。

一、鎖在事務(wù)里:穿著棉襖游泳的痛苦

1. 鎖的持有時(shí)間過長(zhǎng)

假設(shè)你的事務(wù)里有三個(gè)操作:查庫存、扣庫存、發(fā)消息。每個(gè)操作都需要 100ms,事務(wù)總時(shí)長(zhǎng) 300ms。而鎖的超時(shí)時(shí)間設(shè)置為 500ms,看起來沒問題。但如果數(shù)據(jù)庫突然慢了,事務(wù)執(zhí)行了 800ms,鎖就會(huì)自動(dòng)釋放。這時(shí)候另一個(gè)線程拿到鎖,繼續(xù)扣庫存,就會(huì)導(dǎo)致超賣。

這就像你租了個(gè)充電寶,租期 2 小時(shí),但你用了 3 小時(shí)才還。中間第二個(gè)小時(shí)的時(shí)候,充電寶被別人借走了,你還的時(shí)候發(fā)現(xiàn)已經(jīng)被別人還了,結(jié)果你被扣了雙倍租金。

2. 事務(wù)回滾導(dǎo)致鎖無法釋放

如果事務(wù)執(zhí)行過程中拋出異?;貪L,鎖會(huì)被釋放嗎?答案是不一定。比如你用 Redis 的 SETNX 加鎖,沒有設(shè)置過期時(shí)間,事務(wù)回滾時(shí)忘記手動(dòng)釋放鎖,這個(gè)鎖就會(huì)一直存在,導(dǎo)致其他線程永遠(yuǎn)無法獲取鎖。

這就像你在酒店退房時(shí),把房卡忘在房間里,后面的客人就無法入住了。

3. 數(shù)據(jù)庫隔離級(jí)別的坑

如果你用的是 MySQL 的可重復(fù)讀隔離級(jí)別,在事務(wù)內(nèi)查詢庫存時(shí),其他事務(wù)的修改是不可見的。但如果鎖在事務(wù)內(nèi),其他事務(wù)可能在鎖釋放后修改庫存,導(dǎo)致數(shù)據(jù)不一致。

這就像你在餐廳吃飯,點(diǎn)了一份牛排,結(jié)果服務(wù)員告訴你已經(jīng)賣完了。你剛要走,另一個(gè)服務(wù)員又端來一份牛排,說剛才查錯(cuò)了。

二、正確的姿勢(shì):鎖在事務(wù)外,事務(wù)在鎖內(nèi)

1. 先鎖后事務(wù)

正確的做法是先獲取鎖,再開啟事務(wù)。這樣鎖的持有時(shí)間只包括事務(wù)內(nèi)的操作,而不是整個(gè)方法的執(zhí)行時(shí)間。

RLock lock = redisson.getLock("product_123");
try {
    lock.lock(); // 先獲取鎖
    // 開啟事務(wù)
    Product product = productRepository.findById(123).orElseThrow();
    if (product.getStock() > 0) {
        product.setStock(product.getStock() - 1);
        productRepository.save(product);
    }
} finally {
    lock.unlock(); // 釋放鎖
}

這樣,即使事務(wù)執(zhí)行時(shí)間長(zhǎng),鎖也會(huì)在事務(wù)結(jié)束后立即釋放,不會(huì)影響其他線程。

2. 鎖的粒度要細(xì)

不要鎖整個(gè)商品 ID,而是鎖具體的庫存項(xiàng)。比如按庫存批次加鎖,或者按 SKU 加鎖。這樣可以提高并發(fā)度,減少鎖競(jìng)爭(zhēng)。

這就像你去銀行取錢,不是鎖整個(gè)銀行,而是鎖具體的 ATM 機(jī)。

3. 數(shù)據(jù)庫層面加唯一索引

為庫存表的商品 ID 加唯一索引,防止重復(fù)扣庫存。即使鎖被釋放,數(shù)據(jù)庫也能保證數(shù)據(jù)一致性。

ALTER TABLE product_stock ADD UNIQUE INDEX uk_product_id (product_id);

這樣,當(dāng)多個(gè)線程同時(shí)扣庫存時(shí),只有一個(gè)線程能成功插入或更新記錄,其他線程會(huì)收到唯一約束沖突的錯(cuò)誤。

三、分布式鎖的選型:別用錘子釘釘子

1. Redis 分布式鎖:性能王者

Redis 的 SETNX+EXPIRE 命令可以實(shí)現(xiàn)分布式鎖,性能高,適合高并發(fā)場(chǎng)景。但要注意以下幾點(diǎn):

  • 使用 Lua 腳本保證加鎖和設(shè)置超時(shí)時(shí)間的原子性
  • 鎖的 value 要設(shè)置為唯一標(biāo)識(shí),防止誤釋放
  • 集群模式下使用 Redlock 算法,避免腦裂問題
String luaScript = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
        "redis.call('expire', KEYS[1], ARGV[2]) return 1 else return 0 end";
List<String> keys = Collections.singletonList("lock_key");
List<String> argv = Arrays.asList("unique_value", "30");
Long result = jedis.eval(luaScript, keys, argv);

2. ZooKeeper 分布式鎖:可靠性之選

ZooKeeper 的臨時(shí)順序節(jié)點(diǎn)可以實(shí)現(xiàn)公平鎖,適合對(duì)可靠性要求高的場(chǎng)景。但性能較低,適合中低并發(fā)。

InterProcessMutex lock = new InterProcessMutex(client, "/locks/lock1");
try {
    lock.acquire();
    // 執(zhí)行業(yè)務(wù)邏輯
} finally {
    lock.release();
}

ZooKeeper 會(huì)自動(dòng)處理節(jié)點(diǎn)的創(chuàng)建和刪除,即使客戶端宕機(jī),臨時(shí)節(jié)點(diǎn)也會(huì)自動(dòng)消失,避免死鎖。

3. 數(shù)據(jù)庫分布式鎖:簡(jiǎn)單但不推薦

通過數(shù)據(jù)庫的唯一索引和行鎖實(shí)現(xiàn)分布式鎖,簡(jiǎn)單易懂,但性能差,適合小型系統(tǒng)。

INSERT INTO lock_table (resource_id, lock_time) VALUES ('product_123', NOW()) 
ON DUPLICATE KEY UPDATE lock_time = NOW();

這種方法在高并發(fā)下會(huì)導(dǎo)致大量的鎖競(jìng)爭(zhēng),數(shù)據(jù)庫壓力大,不建議在生產(chǎn)環(huán)境中使用。

四、分布式事務(wù)的正確打開方式:別把鎖當(dāng)萬能鑰匙

1. 避免分布式事務(wù)

能不用分布式事務(wù)就不用,盡量通過本地事務(wù)和消息隊(duì)列實(shí)現(xiàn)最終一致性。比如訂單服務(wù)扣庫存后,發(fā)送一條消息給庫存服務(wù),庫存服務(wù)異步更新庫存。

這就像你在淘寶下單后,支付寶異步通知商家發(fā)貨。

2. 使用 TCC 事務(wù)

TCC(Try-Confirm-Cancel)事務(wù)模型適合長(zhǎng)事務(wù)場(chǎng)景。比如支付服務(wù)先凍結(jié)資金(Try),訂單服務(wù)扣庫存(Try),然后支付服務(wù)確認(rèn)支付(Confirm),訂單服務(wù)確認(rèn)發(fā)貨(Confirm)。如果任何一步失敗,都需要回滾(Cancel)。

// 支付服務(wù)
public void tryPay(String orderId, BigDecimal amount) {
    // 凍結(jié)資金
}
public void confirmPay(String orderId) {
    // 扣除資金
}
public void cancelPay(String orderId) {
    // 解凍資金
}
// 訂單服務(wù)
public void tryDeductStock(String orderId, String productId, int quantity) {
    // 鎖定庫存
}
public void confirmDeductStock(String orderId) {
    // 扣減庫存
}
public void cancelDeductStock(String orderId) {
    // 釋放庫存
}

3. 結(jié)合 Saga 模式

Saga 模式將長(zhǎng)事務(wù)拆分為多個(gè)短事務(wù),每個(gè)短事務(wù)都有對(duì)應(yīng)的補(bǔ)償操作。如果某個(gè)短事務(wù)失敗,回滾之前的所有短事務(wù)。

比如用戶注冊(cè)流程:發(fā)送驗(yàn)證碼(短事務(wù) 1)→ 創(chuàng)建用戶(短事務(wù) 2)→ 發(fā)送歡迎郵件(短事務(wù) 3)。如果創(chuàng)建用戶失敗,回滾發(fā)送驗(yàn)證碼的操作。

五、常見誤區(qū):這些坑你踩過嗎?

1. 鎖的超時(shí)時(shí)間設(shè)置不合理

超時(shí)時(shí)間太短,會(huì)導(dǎo)致鎖頻繁釋放,增加重試次數(shù);太長(zhǎng),會(huì)影響并發(fā)度。應(yīng)該根據(jù)業(yè)務(wù)邏輯的平均執(zhí)行時(shí)間來設(shè)置,比如平均執(zhí)行時(shí)間的 1.5 倍。

這就像你設(shè)置自動(dòng)關(guān)機(jī)時(shí)間,太短會(huì)導(dǎo)致工作沒保存,太長(zhǎng)會(huì)浪費(fèi)電。

2. 鎖的可重入性問題

如果同一個(gè)線程多次獲取同一把鎖,會(huì)導(dǎo)致死鎖。使用支持可重入的鎖,如 Redisson 的 RLock,或者在數(shù)據(jù)庫鎖表中記錄線程 ID。

// Redisson可重入鎖
RLock lock = redisson.getLock("product_123");
lock.lock();
try {
    // 執(zhí)行業(yè)務(wù)邏輯
    lock.lock(); // 可重入
    try {
        // 嵌套業(yè)務(wù)邏輯
    } finally {
        lock.unlock();
    }
} finally {
    lock.unlock();
}

3. 忽略網(wǎng)絡(luò)延遲的影響

在分布式系統(tǒng)中,網(wǎng)絡(luò)延遲是不可避免的。鎖的獲取和釋放可能會(huì)因?yàn)榫W(wǎng)絡(luò)問題失敗,需要設(shè)置重試機(jī)制。

int retryCount = 3;
int retryInterval = 1000;
for (int i = 0; i < retryCount; i++) {
    try {
        if (lock.tryLock(10, TimeUnit.SECONDS)) {
            // 執(zhí)行業(yè)務(wù)邏輯
            break;
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        break;
    }
    if (i < retryCount - 1) {
        try {
            Thread.sleep(retryInterval);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
}

六、性能優(yōu)化:讓系統(tǒng)飛起來

1. 異步處理非核心邏輯

將發(fā)消息、寫日志等非核心操作放到鎖外面異步執(zhí)行,減少鎖的持有時(shí)間。

RLock lock = redisson.getLock("product_123");
try {
    lock.lock();
    // 核心業(yè)務(wù)邏輯
} finally {
    lock.unlock();
}
// 異步發(fā)送消息
CompletableFuture.runAsync(() -> messageService.send("庫存已扣減"));

2. 使用本地緩存

將高頻訪問的庫存數(shù)據(jù)緩存到本地,減少對(duì)數(shù)據(jù)庫的訪問次數(shù)。比如使用 Caffeine 或 Guava Cache。

LoadingCache<Long, Integer> stockCache = Caffeine.newBuilder()
        .expireAfterWrite(1, TimeUnit.MINUTES)
        .build(key -> productRepository.findStockByProductId(key));

int stock = stockCache.get(123);

3. 限流和熔斷

在高并發(fā)場(chǎng)景下,使用限流組件(如 Sentinel)限制請(qǐng)求流量,避免系統(tǒng)過載。同時(shí),使用熔斷機(jī)制(如 Hystrix)在服務(wù)不可用時(shí)快速失敗,防止級(jí)聯(lián)故障。

// Sentinel限流
@SentinelResource(value = "deductStock", blockHandler = "handleBlock")
public void deductStock(Long productId, Integer quantity) {
    // 扣庫存邏輯
}

public void handleBlock(Long productId, Integer quantity, BlockException e) {
    // 限流處理
    throw new RuntimeException("系統(tǒng)繁忙,請(qǐng)稍后再試");
}

七、總結(jié):鎖與事務(wù)的正確關(guān)系

分布式鎖和事務(wù)就像一對(duì)歡喜冤家,既相互依賴,又相互排斥。鎖可以保證數(shù)據(jù)的一致性,但如果用錯(cuò)了地方,就會(huì)成為性能瓶頸。正確的做法是:

  • 鎖在事務(wù)外,事務(wù)在鎖內(nèi)
  • 鎖的粒度要細(xì),避免大鎖
  • 結(jié)合數(shù)據(jù)庫唯一索引和重試機(jī)制
  • 選擇合適的分布式鎖方案
  • 避免分布式事務(wù),使用最終一致性

鎖不是萬能的,不要把所有問題都?xì)w咎于鎖。在設(shè)計(jì)系統(tǒng)時(shí),要從架構(gòu)層面考慮性能和可用性,而不是依賴鎖來解決所有問題。

責(zé)任編輯:姜華 來源: 石杉的架構(gòu)筆記
相關(guān)推薦

2022-06-27 08:21:05

Seata分布式事務(wù)微服務(wù)

2017-07-26 15:08:05

大數(shù)據(jù)分布式事務(wù)

2022-06-21 08:27:22

Seata分布式事務(wù)

2018-11-27 16:17:13

分布式Tomcat

2021-11-26 06:43:19

Java分布式

2009-06-19 15:28:31

JDBC分布式事務(wù)

2025-04-29 04:00:00

分布式事務(wù)事務(wù)消息

2009-09-18 15:10:13

分布式事務(wù)LINQ TO SQL

2021-09-29 09:07:37

分布式架構(gòu)系統(tǒng)

2019-06-19 15:40:06

分布式鎖RedisJava

2021-07-06 08:37:29

Redisson分布式

2022-08-04 08:45:50

Redisson分布式鎖工具

2021-07-16 07:57:34

ZooKeeperCurator源碼

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2025-06-10 08:02:15

2019-06-26 09:41:44

分布式事務(wù)微服務(wù)

2022-03-24 07:51:27

seata分布式事務(wù)Java

2025-05-15 08:05:00

2021-07-02 08:51:09

Redisson分布式鎖公平鎖

2019-10-10 09:16:34

Zookeeper架構(gòu)分布式
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)