自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

分布式鎖詳解:從數(shù)據(jù)庫(kù)實(shí)現(xiàn)到中間件選型

云計(jì)算 分布式
分布式鎖是分布式系統(tǒng)中的一個(gè)基礎(chǔ)組件,選擇合適的實(shí)現(xiàn)方案需要考慮:性能要求;可靠性要求;開(kāi)發(fā)維護(hù)成本;團(tuán)隊(duì)技術(shù)棧。

引言

在分布式系統(tǒng)中,我們經(jīng)常需要對(duì)共享資源進(jìn)行互斥訪問(wèn)。比如:

  • 防止商品超賣(mài)
  • 避免重復(fù)下單
  • 確保任務(wù)只被處理一次
  • 保護(hù)共享資源不被并發(fā)修改

這就需要一個(gè)分布式鎖機(jī)制。與單機(jī)環(huán)境下的線程鎖不同,分布式鎖需要在多個(gè)服務(wù)實(shí)例間生效,這帶來(lái)了新的挑戰(zhàn)。

圖片圖片

分布式鎖的核心要求

一個(gè)可靠的分布式鎖必須滿足以下要求:

  1. 互斥性

在任意時(shí)刻,只能有一個(gè)客戶端持有鎖

不能出現(xiàn)多個(gè)客戶端同時(shí)持有鎖的情況

  1. 可重入性

同一個(gè)客戶端可以多次獲取同一把鎖

需要維護(hù)鎖的重入計(jì)數(shù)

  1. 防死鎖

客戶端崩潰時(shí),鎖必須能自動(dòng)釋放

鎖必須有過(guò)期機(jī)制

  1. 高可用

鎖服務(wù)不能成為系統(tǒng)瓶頸

鎖服務(wù)必須保證高可用

基于數(shù)據(jù)庫(kù)的實(shí)現(xiàn)

圖片圖片

1. 悲觀鎖實(shí)現(xiàn)

最簡(jiǎn)單的方式是利用數(shù)據(jù)庫(kù)的行鎖:

-- 創(chuàng)建鎖表
CREATE TABLE distributed_lock (
    lock_key VARCHAR(50) PRIMARY KEY,
    lock_value VARCHAR(50),
    version INT,
    expire_time TIMESTAMP
);

-- 獲取鎖
SELECT * FROM distributed_lock 
WHERE lock_key = 'order_lock' 
FOR UPDATE;

Java 實(shí)現(xiàn)示例:

@Service
public class DatabaseDistributedLock {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Transactional
    public boolean acquireLock(String lockKey, String lockValue, long expireSeconds) {
        try {
            // 使用 FOR UPDATE 加鎖查詢
            String sql = "SELECT * FROM distributed_lock " +
                        "WHERE lock_key = ? FOR UPDATE";
            
            List<Map<String, Object>> result = jdbcTemplate.queryForList(
                sql, lockKey
            );
            
            if (result.isEmpty()) {
                // 鎖不存在,創(chuàng)建鎖
                jdbcTemplate.update(
                    "INSERT INTO distributed_lock " +
                    "(lock_key, lock_value, version, expire_time) " +
                    "VALUES (?, ?, 1, ?)",
                    lockKey,
                    lockValue,
                    LocalDateTime.now().plusSeconds(expireSeconds)
                );
                return true;
            }
            
            // 檢查鎖是否過(guò)期
            Map<String, Object> lock = result.get(0);
            LocalDateTime expireTime = ((Timestamp) lock.get("expire_time"))
                .toLocalDateTime();
                
            if (expireTime.isBefore(LocalDateTime.now())) {
                // 鎖已過(guò)期,更新鎖
                jdbcTemplate.update(
                    "UPDATE distributed_lock " +
                    "SET lock_value = ?, version = version + 1, expire_time = ? " +
                    "WHERE lock_key = ?",
                    lockValue,
                    LocalDateTime.now().plusSeconds(expireSeconds),
                    lockKey
                );
                return true;
            }
            
            return false;
        } catch (Exception e) {
            return false;
        }
    }
}

2. 樂(lè)觀鎖實(shí)現(xiàn)

使用版本號(hào)實(shí)現(xiàn)樂(lè)觀鎖:

@Service
public class OptimisticLock {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public boolean acquireLock(String lockKey, String lockValue, int version) {
        int updated = jdbcTemplate.update(
            "UPDATE distributed_lock " +
            "SET lock_value = ?, version = version + 1 " +
            "WHERE lock_key = ? AND version = ?",
            lockValue,
            lockKey,
            version
        );
        
        return updated > 0;
    }
}

數(shù)據(jù)庫(kù)實(shí)現(xiàn)的優(yōu)缺點(diǎn):

  • 優(yōu)點(diǎn):

實(shí)現(xiàn)簡(jiǎn)單

容易理解

不需要額外組件

  • 缺點(diǎn):

性能較差

數(shù)據(jù)庫(kù)壓力大

無(wú)法優(yōu)雅處理鎖超時(shí)

基于 Redis 的實(shí)現(xiàn)

1. 單節(jié)點(diǎn)實(shí)現(xiàn)

使用 Redis 的 SETNX 命令:

@Service
public class RedisDistributedLock {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public boolean acquireLock(String lockKey, String lockValue, long expireSeconds) {
        return redisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, expireSeconds, TimeUnit.SECONDS);
    }
    
    public boolean releaseLock(String lockKey, String lockValue) {
        // 使用 Lua 腳本確保原子性
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) " +
            "else " +
            "return 0 " +
            "end";
            
        return redisTemplate.execute(
            new DefaultRedisScript<>(script, Boolean.class),
            Collections.singletonList(lockKey),
            lockValue
        );
    }
}

2. RedLock 算法

在 Redis 集群環(huán)境下,使用 RedLock 算法:

public class RedLock {
    
    private final List<StringRedisTemplate> redisList;
    private final int quorum;  // 大多數(shù)節(jié)點(diǎn)數(shù)
    
    public boolean acquireLock(String lockKey, String lockValue, long expireMillis) {
        int acquiredLocks = 0;
        long startTime = System.currentTimeMillis();
        
        // 嘗試在每個(gè)節(jié)點(diǎn)上獲取鎖
        for (StringRedisTemplate redis : redisList) {
            if (tryAcquireLock(redis, lockKey, lockValue, expireMillis)) {
                acquiredLocks++;
            }
        }
        
        // 計(jì)算獲取鎖消耗的時(shí)間
        long elapsedTime = System.currentTimeMillis() - startTime;
        long remainingTime = expireMillis - elapsedTime;
        
        // 判斷是否獲取到足夠的鎖
        if (acquiredLocks >= quorum && remainingTime > 0) {
            return true;
        } else {
            // 釋放所有獲取的鎖
            releaseLocks(lockKey, lockValue);
            return false;
        }
    }
    
    private boolean tryAcquireLock(
        StringRedisTemplate redis, 
        String lockKey, 
        String lockValue, 
        long expireMillis
    ) {
        try {
            return redis.opsForValue()
                .setIfAbsent(lockKey, lockValue, expireMillis, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            return false;
        }
    }
}

Redis 實(shí)現(xiàn)的優(yōu)缺點(diǎn):

  • 優(yōu)點(diǎn):

性能高

實(shí)現(xiàn)相對(duì)簡(jiǎn)單

支持自動(dòng)過(guò)期

  • 缺點(diǎn):

需要額外維護(hù) Redis 集群

RedLock 算法實(shí)現(xiàn)復(fù)雜

時(shí)鐘依賴問(wèn)題

基于 ZooKeeper 的實(shí)現(xiàn)

圖片圖片

利用 ZooKeeper 的臨時(shí)節(jié)點(diǎn)機(jī)制:

public class ZookeeperDistributedLock {
    
    private final CuratorFramework client;
    private final String lockPath;
    
    public boolean acquireLock(String lockKey) throws Exception {
        // 創(chuàng)建臨時(shí)節(jié)點(diǎn)
        String path = lockPath + "/" + lockKey;
        try {
            client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .forPath(path);
            return true;
        } catch (NodeExistsException e) {
            return false;
        }
    }
    
    public void releaseLock(String lockKey) throws Exception {
        String path = lockPath + "/" + lockKey;
        client.delete().forPath(path);
    }
    
    // 實(shí)現(xiàn)可重入鎖
    public class ReentrantZookeeperLock {
        private final ThreadLocal<Integer> lockCount = new ThreadLocal<>();
        
        public boolean acquire() throws Exception {
            Integer count = lockCount.get();
            if (count != null && count > 0) {
                // 入
                lockCount.set(count + 1);
                return true;
            }
            
            if (acquireLock("lock")) {
                lockCount.set(1);
                return true;
            }
            return false;
        }
        
        public void release() throws Exception {
            Integer count = lockCount.get();
            if (count == null) {
                return;
            }
            
            count--;
            if (count > 0) {
                lockCount.set(count);
            } else {
                lockCount.remove();
                releaseLock("lock");
            }
        }
    }
}

ZooKeeper 實(shí)現(xiàn)的優(yōu)缺點(diǎn):

  • 優(yōu)點(diǎn):

可靠性高

自動(dòng)釋放鎖

支持監(jiān)聽(tīng)機(jī)制

  • 缺點(diǎn):

性能一般

實(shí)現(xiàn)復(fù)雜

需要維護(hù) ZooKeeper 集群

業(yè)務(wù)場(chǎng)景分析

1. 秒殺場(chǎng)景

場(chǎng)景特點(diǎn):

  • 并發(fā)量極高
  • 時(shí)間窗口集中
  • 對(duì)性能要求極高
  • 數(shù)據(jù)一致性要求高

推薦方案: Redis + Lua腳本

@Service
public class SeckillLockService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public boolean trySecKill(String productId, String userId) {
        // Lua腳本保證原子性
        String script = 
            "if redis.call('exists', KEYS[1]) == 0 then " +
            "  redis.call('set', KEYS[1], ARGV[1]) " +
            "  redis.call('decrby', KEYS[2], 1) " +
            "  return 1 " +
            "end " +
            "return 0";
            
        List<String> keys = Arrays.asList(
            "seckill:lock:" + productId + ":" + userId,
            "seckill:stock:" + productId
        );
        
        return redisTemplate.execute(
            new DefaultRedisScript<>(script, Boolean.class),
            keys,
            "1"
        );
    }
}

原因分析:

  • Redis 的高性能滿足并發(fā)要求
  • Lua 腳本保證原子性
  • 內(nèi)存操作速度快
  • 集群方案保證可用性

2. 定時(shí)任務(wù)場(chǎng)景

場(chǎng)景特點(diǎn):

  • 多實(shí)例部署
  • 任務(wù)不能重復(fù)執(zhí)行
  • 故障轉(zhuǎn)移需求
  • 實(shí)時(shí)性要求不高

推薦方案: ZooKeeper

public class ScheduledTaskLock {
    
    private final CuratorFramework client;
    
    public void executeTask() {
        String taskPath = "/scheduled-tasks/daily-report";
        try {
            // 創(chuàng)建臨時(shí)節(jié)點(diǎn)
            client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .forPath(taskPath);
                
            try {
                // 執(zhí)行任務(wù)
                generateDailyReport();
            } finally {
                // 刪除節(jié)點(diǎn)
                client.delete().forPath(taskPath);
            }
        } catch (NodeExistsException e) {
            // 其他實(shí)例正在執(zhí)行
            log.info("Task is running on other instance");
        }
    }
}

原因分析:

  • ZooKeeper 的臨時(shí)節(jié)點(diǎn)特性保證故障時(shí)自動(dòng)釋放鎖
  • 強(qiáng)一致性保證任務(wù)不會(huì)重復(fù)執(zhí)行
  • Watch 機(jī)制便于監(jiān)控任務(wù)執(zhí)行狀態(tài)

3. 訂單支付場(chǎng)景

場(chǎng)景特點(diǎn):

  • 并發(fā)量適中
  • 數(shù)據(jù)一致性要求高
  • 需要事務(wù)支持
  • 有業(yè)務(wù)回滾需求

推薦方案: 數(shù)據(jù)庫(kù)行鎖 + 事務(wù)

@Service
public class PaymentLockService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Transactional
    public boolean processPayment(String orderId, BigDecimal amount) {
        // 使用 FOR UPDATE 鎖定訂單記錄
        String sql = "SELECT * FROM orders WHERE order_id = ? FOR UPDATE";
        Map<String, Object> order = jdbcTemplate.queryForMap(sql, orderId);
        
        // 檢查訂單狀態(tài)
        if (!"PENDING".equals(order.get("status"))) {
            return false;
        }
        
        // 執(zhí)行支付邏輯
        jdbcTemplate.update(
            "UPDATE orders SET status = 'PAID' WHERE order_id = ?",
            orderId
        );
        
        // 記錄支付流水
        jdbcTemplate.update(
            "INSERT INTO payment_log (order_id, amount) VALUES (?, ?)",
            orderId, amount
        );
        
        return true;
    }
}

原因分析:

  • 數(shù)據(jù)庫(kù)事務(wù)保證數(shù)據(jù)一致性
  • 行鎖防止并發(fā)支付
  • 便于與其他業(yè)務(wù)集成
  • 支持事務(wù)回滾

4. 庫(kù)存扣減場(chǎng)景

場(chǎng)景特點(diǎn):

  • 并發(fā)量較高
  • 需要預(yù)占庫(kù)存
  • 需要處理超時(shí)釋放
  • 對(duì)性能要求較高

推薦方案: Redis + 延時(shí)隊(duì)列

@Service
public class InventoryLockService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public boolean lockInventory(String productId, int quantity, String orderId) {
        // 加鎖并預(yù)占庫(kù)存
        String script = 
            "local stock = redis.call('get', KEYS[1]) " +
            "if stock and tonumber(stock) >= tonumber(ARGV[1]) then " +
            "  redis.call('decrby', KEYS[1], ARGV[1]) " +
            "  redis.call('setex', KEYS[2], 1800, ARGV[1]) " +
            "  return 1 " +
            "end " +
            "return 0";
            
        List<String> keys = Arrays.asList(
            "inventory:" + productId,
            "inventory:lock:" + orderId
        );
        
        boolean locked = redisTemplate.execute(
            new DefaultRedisScript<>(script, Boolean.class),
            keys,
            String.valueOf(quantity)
        );
        
        if (locked) {
            // 添加延時(shí)釋放任務(wù)
            redisTemplate.opsForZSet().add(
                "inventory:timeout",
                orderId,
                System.currentTimeMillis() + 1800000
            );
        }
        
        return locked;
    }
}

原因分析:

  • Redis 的高性能滿足并發(fā)要求
  • 延時(shí)隊(duì)列處理超時(shí)釋放
  • 原子操作保證數(shù)據(jù)一致性
  • 便于擴(kuò)展和監(jiān)控

實(shí)現(xiàn)方案對(duì)比

特性

數(shù)據(jù)庫(kù)

Redis

ZooKeeper

性能

可靠性

實(shí)現(xiàn)復(fù)雜度

維護(hù)成本

自動(dòng)釋放

需要額外實(shí)現(xiàn)

支持

支持

可重入性

需要額外實(shí)現(xiàn)

需要額外實(shí)現(xiàn)

需要額外實(shí)現(xiàn)

最佳實(shí)踐

  1. 選擇建議

簡(jiǎn)單場(chǎng)景:使用數(shù)據(jù)庫(kù)實(shí)現(xiàn)

高性能要求:使用 Redis 實(shí)現(xiàn)

高可靠要求:使用 ZooKeeper 實(shí)現(xiàn)

  1. 實(shí)現(xiàn)建議

設(shè)置合理的超時(shí)時(shí)間

實(shí)現(xiàn)可重入機(jī)制

添加監(jiān)控和告警

做好日志記錄

  1. 使用建議

縮小鎖的粒度

減少鎖的持有時(shí)間

避免死鎖

做好異常處理

結(jié)論

分布式鎖是分布式系統(tǒng)中的一個(gè)基礎(chǔ)組件,選擇合適的實(shí)現(xiàn)方案需要考慮:

  • 性能要求
  • 可靠性要求
  • 開(kāi)發(fā)維護(hù)成本
  • 團(tuán)隊(duì)技術(shù)棧

沒(méi)有最好的方案,只有最合適的方案。在實(shí)際應(yīng)用中,要根據(jù)具體場(chǎng)景選擇合適的實(shí)現(xiàn)方式。

正文內(nèi)容從這里開(kāi)始(可直接省略,亦可配圖說(shuō)明)。

責(zé)任編輯:武曉燕 來(lái)源: 架構(gòu)成長(zhǎng)指南
相關(guān)推薦

2021-11-14 16:07:35

中間件阿里Seata

2019-08-12 11:00:59

美團(tuán)網(wǎng)MySQL數(shù)據(jù)庫(kù)

2021-11-26 06:43:19

Java分布式

2012-11-30 10:21:46

移動(dòng)中間件

2025-03-27 11:03:18

2019-08-19 10:24:33

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

2017-12-01 05:04:32

數(shù)據(jù)庫(kù)中間件Atlas

2017-11-27 05:36:16

數(shù)據(jù)庫(kù)中間件TDDL

2017-11-27 05:06:42

數(shù)據(jù)庫(kù)中間件cobar

2024-05-07 07:58:10

數(shù)據(jù)架構(gòu)大數(shù)據(jù)中間件架構(gòu)

2018-02-24 19:37:33

Java8數(shù)據(jù)庫(kù)中間件

2011-08-10 13:03:58

CJDBC數(shù)據(jù)庫(kù)集群

2024-12-06 08:29:29

2015-06-16 10:39:43

NoSQL分布式算法

2022-08-01 18:33:45

關(guān)系型數(shù)據(jù)庫(kù)大數(shù)據(jù)

2017-05-23 18:55:05

mysql-proxy數(shù)據(jù)庫(kù)架構(gòu)

2017-12-04 09:00:00

金融開(kāi)源軟件分布式消息中間件

2017-07-26 09:41:28

MyCATSQLMongoDB

2022-07-21 07:31:41

數(shù)據(jù)庫(kù)分布式

2024-05-06 00:00:00

.NET分布式鎖技術(shù)
點(diǎn)贊
收藏

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