40-分布式锁实现详解

2 阅读11分钟

分布式锁实现详解

一、知识概述

在分布式系统中,多个服务实例可能同时访问共享资源,如数据库、缓存、文件等。为了保证数据一致性,需要一种跨进程、跨机器的互斥机制,这就是分布式锁。分布式锁是实现分布式系统数据一致性的重要手段。

本文将深入讲解分布式锁的实现原理,包括数据库锁、Redis锁、Zookeeper锁等主流方案,分析各种方案的优缺点和适用场景,并提供生产级的实现代码。

二、分布式锁需求分析

2.1 核心特性

/**
 * 分布式锁核心特性
 */
public class DistributedLockRequirements {
    
    /**
     * 1. 互斥性
     * - 任意时刻,只有一个客户端能持有锁
     * - 这是最基本的要求
     */
    
    /**
     * 2. 防死锁
     * - 锁必须有超时机制
     * - 客户端崩溃后锁能自动释放
     * - 不能出现永久死锁
     */
    
    /**
     * 3. 可重入性
     * - 同一线程可多次获取同一把锁
     * - 避免死锁
     */
    
    /**
     * 4. 高可用
     * - 锁服务不能成为系统瓶颈
     * - 需要容错机制
     */
    
    /**
     * 5. 高性能
     * - 加锁/解锁延迟低
     * - 支持高并发
     */
    
    /**
     * 6. 公平性(可选)
     * - 按请求顺序获取锁
     * - 避免饥饿
     */
}

2.2 方案对比

/**
 * 分布式锁方案对比
 */
public class LockSchemesComparison {
    
    /*
    ┌─────────────────────────────────────────────────────────────────────────────────┐
    │                          分布式锁方案对比                                         │
    ├──────────────┬──────────┬──────────┬──────────┬──────────┬─────────────────────┤
    │     方案     │  性能    │  可靠性  │  复杂度  │  可重入  │      适用场景       │
    ├──────────────┼──────────┼──────────┼──────────┼──────────┼─────────────────────┤
    │ 数据库锁     │   低     │   高     │   低     │   支持   │ 低并发、强一致      │
    │ Redis SETNX  │   高     │   中     │   低     │  不支持  │ 高并发、可容忍丢失  │
    │ Redis RedLock│   中     │   高     │   中     │  不支持  │ 高可用要求          │
    │ Zookeeper    │   中     │   高     │   高     │   支持   │ 强一致、低延迟要求低│
    │ Etcd         │   中     │   高     │   中     │   支持   │ 云原生环境          │
    └──────────────┴──────────┴──────────┴──────────┴──────────┴─────────────────────┘
    */
}

三、数据库实现

3.1 基于唯一索引

/**
 * 数据库唯一索引实现分布式锁
 * 
 * 原理:利用数据库唯一索引的互斥性
 */
@Service
public class DatabaseLock {
    
    /**
     * 锁表设计
     */
    /*
    CREATE TABLE `distributed_lock` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `lock_key` varchar(64) NOT NULL COMMENT '锁标识',
        `lock_value` varchar(64) NOT NULL COMMENT '锁持有者标识',
        `expire_time` datetime NOT NULL COMMENT '过期时间',
        `create_time` datetime NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uk_lock_key` (`lock_key`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    */
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 加锁
     */
    public boolean tryLock(String lockKey, String lockValue, 
                           long expireSeconds) {
        try {
            int rows = jdbcTemplate.update(
                "INSERT INTO distributed_lock " +
                "(lock_key, lock_value, expire_time, create_time) " +
                "VALUES (?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND), NOW())",
                lockKey, lockValue, expireSeconds);
            return rows > 0;
        } catch (DuplicateKeyException e) {
            // 锁已被占用
            return false;
        }
    }
    
    /**
     * 解锁
     */
    public boolean unlock(String lockKey, String lockValue) {
        int rows = jdbcTemplate.update(
            "DELETE FROM distributed_lock " +
            "WHERE lock_key = ? AND lock_value = ?",
            lockKey, lockValue);
        return rows > 0;
    }
    
    /**
     * 强制解锁(过期清理)
     */
    public void forceUnlock(String lockKey) {
        jdbcTemplate.update(
            "DELETE FROM distributed_lock " +
            "WHERE lock_key = ? OR expire_time < NOW()",
            lockKey);
    }
    
    /**
     * 使用示例
     */
    public void executeWithLock(String lockKey, Runnable task) {
        String lockValue = UUID.randomUUID().toString();
        
        try {
            // 尝试加锁
            if (tryLock(lockKey, lockValue, 30)) {
                try {
                    // 执行业务逻辑
                    task.run();
                } finally {
                    // 释放锁
                    unlock(lockKey, lockValue);
                }
            } else {
                throw new LockException("获取锁失败: " + lockKey);
            }
        } catch (Exception e) {
            // 处理异常
            throw new LockException("锁操作异常", e);
        }
    }
}

3.2 基于乐观锁

/**
 * 数据库乐观锁实现
 */
@Service
public class OptimisticLock {
    
    /**
     * 带版本号的表
     */
    /*
    CREATE TABLE `resource_lock` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `resource_key` varchar(64) NOT NULL COMMENT '资源标识',
        `version` int(11) NOT NULL DEFAULT 0 COMMENT '版本号',
        `locked` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否锁定',
        `lock_holder` varchar(64) DEFAULT NULL COMMENT '锁持有者',
        `update_time` datetime NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uk_resource_key` (`resource_key`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    */
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 加锁(CAS操作)
     */
    public boolean tryLock(String resourceKey, String lockHolder) {
        // 先查询当前状态
        Map<String, Object> current = jdbcTemplate.queryForMap(
            "SELECT * FROM resource_lock WHERE resource_key = ?",
            resourceKey);
        
        int version = (Integer) current.get("version");
        
        // CAS更新
        int rows = jdbcTemplate.update(
            "UPDATE resource_lock SET locked = 1, lock_holder = ?, " +
            "version = version + 1, update_time = NOW() " +
            "WHERE resource_key = ? AND version = ? AND locked = 0",
            lockHolder, resourceKey, version);
        
        return rows > 0;
    }
    
    /**
     * 解锁
     */
    public boolean unlock(String resourceKey, String lockHolder) {
        int rows = jdbcTemplate.update(
            "UPDATE resource_lock SET locked = 0, lock_holder = NULL, " +
            "version = version + 1, update_time = NOW() " +
            "WHERE resource_key = ? AND lock_holder = ?",
            resourceKey, lockHolder);
        
        return rows > 0;
    }
}

四、Redis 实现

4.1 SETNX 实现

/**
 * Redis SETNX 实现分布式锁
 */
@Service
public class RedisDistributedLock {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String LOCK_PREFIX = "lock:";
    
    /**
     * 简单加锁
     * 问题:SETNX + EXPIRE 不是原子操作
     */
    @Deprecated
    public boolean tryLockSimple(String lockKey, String lockValue, 
                                  long expireTime, TimeUnit unit) {
        // 问题:如果 SETNX 成功但 EXPIRE 失败,会导致死锁
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(LOCK_PREFIX + lockKey, lockValue);
        
        if (Boolean.TRUE.equals(success)) {
            redisTemplate.expire(LOCK_PREFIX + lockKey, expireTime, unit);
            return true;
        }
        
        return false;
    }
    
    /**
     * 加锁(原子操作)
     * 使用 SET key value NX EX seconds
     */
    public boolean tryLock(String lockKey, String lockValue, 
                           long expireTime, TimeUnit unit) {
        String key = LOCK_PREFIX + lockKey;
        
        // 使用 SET NX EX 原子操作
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(key, lockValue, expireTime, unit);
        
        return Boolean.TRUE.equals(success);
    }
    
    /**
     * 加锁(带重试)
     */
    public boolean tryLockWithRetry(String lockKey, String lockValue,
                                     long expireTime, TimeUnit unit,
                                     long waitTime, TimeUnit waitUnit) {
        
        long startTime = System.currentTimeMillis();
        long waitMillis = waitUnit.toMillis(waitTime);
        
        while (true) {
            // 尝试加锁
            if (tryLock(lockKey, lockValue, expireTime, unit)) {
                return true;
            }
            
            // 检查是否超时
            if (System.currentTimeMillis() - startTime > waitMillis) {
                return false;
            }
            
            // 短暂等待后重试
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
    }
    
    /**
     * 解锁(Lua脚本保证原子性)
     * 只有锁的持有者才能解锁
     */
    public boolean unlock(String lockKey, String lockValue) {
        String key = LOCK_PREFIX + lockKey;
        
        // Lua脚本:检查并删除
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";
        
        RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
        Long result = redisTemplate.execute(
            redisScript, Collections.singletonList(key), lockValue);
        
        return result != null && result > 0;
    }
    
    /**
     * 看门狗(自动续期)
     */
    public class LockWatchdog {
        
        private final ScheduledExecutorService scheduler = 
            Executors.newScheduledThreadPool(1);
        
        private final Map<String, ScheduledFuture<?>> tasks = 
            new ConcurrentHashMap<>();
        
        /**
         * 加锁并启动看门狗
         */
        public boolean tryLockWithWatchdog(String lockKey, String lockValue,
                                            long expireTime, TimeUnit unit) {
            if (!tryLock(lockKey, lockValue, expireTime, unit)) {
                return false;
            }
            
            // 启动续期任务
            long expireMillis = unit.toMillis(expireTime);
            long renewInterval = expireMillis / 3;
            
            ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {
                renewLock(lockKey, lockValue, expireTime, unit);
            }, renewInterval, renewInterval, TimeUnit.MILLISECONDS);
            
            tasks.put(lockKey, future);
            
            return true;
        }
        
        /**
         * 解锁并停止看门狗
         */
        public boolean unlockWithWatchdog(String lockKey, String lockValue) {
            // 停止续期任务
            ScheduledFuture<?> future = tasks.remove(lockKey);
            if (future != null) {
                future.cancel(true);
            }
            
            // 解锁
            return unlock(lockKey, lockValue);
        }
        
        /**
         * 续期
         */
        private void renewLock(String lockKey, String lockValue,
                               long expireTime, TimeUnit unit) {
            String key = LOCK_PREFIX + lockKey;
            
            // Lua脚本:检查并续期
            String script = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "    return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "    return 0 " +
                "end";
            
            RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
            redisTemplate.execute(redisScript, 
                Collections.singletonList(key), 
                lockValue, 
                String.valueOf(unit.toSeconds(expireTime)));
        }
    }
}

4.2 Redisson 实现

/**
 * Redisson 分布式锁实现
 * 
 * 特点:
 * - 可重入锁
 * - 看门狗自动续期
 * - 公平锁
 * - 读写锁
 */
@Service
public class RedissonLockService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 可重入锁
     */
    public void reentrantLock(String lockKey, Runnable task) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试加锁,最多等待100秒,锁自动过期时间10秒
            boolean acquired = lock.tryLock(100, 10, TimeUnit.SECONDS);
            
            if (acquired) {
                try {
                    task.run();
                } finally {
                    lock.unlock();
                }
            } else {
                throw new LockException("获取锁超时");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LockException("获取锁被中断", e);
        }
    }
    
    /**
     * 公平锁
     */
    public void fairLock(String lockKey, Runnable task) {
        RLock lock = redissonClient.getFairLock(lockKey);
        
        try {
            if (lock.tryLock(10, TimeUnit.SECONDS)) {
                try {
                    task.run();
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * 读写锁
     */
    public class ReadWriteLockExample {
        
        private final RReadWriteLock rwLock;
        
        public ReadWriteLockExample(String lockKey) {
            this.rwLock = redissonClient.getReadWriteLock(lockKey);
        }
        
        /**
         * 读锁
         */
        public void readLock(Runnable task) {
            RLock lock = rwLock.readLock();
            try {
                lock.lock();
                task.run();
            } finally {
                lock.unlock();
            }
        }
        
        /**
         * 写锁
         */
        public void writeLock(Runnable task) {
            RLock lock = rwLock.writeLock();
            try {
                lock.lock();
                task.run();
            } finally {
                lock.unlock();
            }
        }
    }
    
    /**
     * 联锁(MultiLock)
     * 同时锁定多个资源
     */
    public void multiLock(List<String> lockKeys, Runnable task) {
        RLock[] locks = lockKeys.stream()
            .map(redissonClient::getLock)
            .toArray(RLock[]::new);
        
        RedissonMultiLock multiLock = new RedissonMultiLock(locks);
        
        try {
            if (multiLock.tryLock(10, 30, TimeUnit.SECONDS)) {
                try {
                    task.run();
                } finally {
                    multiLock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * 红锁(RedLock)
     * 在多个 Redis 实例上同时加锁
     */
    public void redLock(List<String> lockKeys, Runnable task) {
        RLock[] locks = lockKeys.stream()
            .map(redissonClient::getLock)
            .toArray(RLock[]::new);
        
        RedissonRedLock redLock = new RedissonRedLock(locks);
        
        try {
            if (redLock.tryLock(10, 30, TimeUnit.SECONDS)) {
                try {
                    task.run();
                } finally {
                    redLock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * 信号量
     */
    public void semaphore(String semaphoreKey, int permits, Runnable task) {
        RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
        
        try {
            // 初始化信号量
            semaphore.trySetPermits(permits);
            
            // 获取许可
            semaphore.acquire();
            
            try {
                task.run();
            } finally {
                semaphore.release();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

4.3 RedLock 算法

/**
 * RedLock 算法实现
 * 
 * 由 Redis 作者 Antirez 提出
 * 解决 Redis 主从切换导致的锁丢失问题
 */
@Service
public class RedLockService {
    
    private final List<RedisTemplate<String, String>> redisTemplates;
    
    public RedLockService(List<RedisTemplate<String, String>> redisTemplates) {
        this.redisTemplates = redisTemplates;
    }
    
    /**
     * RedLock 加锁流程
     * 
     * 1. 获取当前时间戳
     * 2. 依次向所有 Redis 实例尝试加锁
     * 3. 计算加锁成功数和耗时
     * 4. 成功数超过半数且耗时小于锁过期时间,则加锁成功
     * 5. 否则解锁所有实例
     */
    public boolean tryLock(String lockKey, String lockValue,
                           long expireTime, TimeUnit unit) {
        
        long startTime = System.currentTimeMillis();
        long expireMillis = unit.toMillis(expireTime);
        
        int successCount = 0;
        
        // 依次向所有 Redis 实例加锁
        for (RedisTemplate<String, String> redisTemplate : redisTemplates) {
            try {
                Boolean success = redisTemplate.opsForValue()
                    .setIfAbsent(lockKey, lockValue, expireTime, unit);
                
                if (Boolean.TRUE.equals(success)) {
                    successCount++;
                }
            } catch (Exception e) {
                // 忽略单个实例的错误
            }
        }
        
        // 计算耗时
        long elapsed = System.currentTimeMillis() - startTime;
        
        // 判断是否成功
        // 1. 成功数超过半数
        // 2. 耗时小于锁过期时间
        if (successCount > redisTemplates.size() / 2 && elapsed < expireMillis) {
            return true;
        }
        
        // 加锁失败,解锁所有实例
        unlock(lockKey, lockValue);
        return false;
    }
    
    /**
     * 解锁所有实例
     */
    public void unlock(String lockKey, String lockValue) {
        for (RedisTemplate<String, String> redisTemplate : redisTemplates) {
            try {
                String script = 
                    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "    return redis.call('del', KEYS[1]) " +
                    "else " +
                    "    return 0 " +
                    "end";
                
                RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
                redisTemplate.execute(redisScript, 
                    Collections.singletonList(lockKey), lockValue);
            } catch (Exception e) {
                // 忽略解锁失败
            }
        }
    }
}

五、Zookeeper 实现

5.1 临时节点实现

/**
 * Zookeeper 临时节点实现分布式锁
 * 
 * 原理:
 * - 创建临时节点,成功则获取锁
 * - 删除节点则释放锁
 * - 会话断开自动删除临时节点
 */
@Service
public class ZkDistributedLock {
    
    private final CuratorFramework zkClient;
    private final String lockPath;
    
    public ZkDistributedLock(CuratorFramework zkClient, String lockPath) {
        this.zkClient = zkClient;
        this.lockPath = lockPath;
    }
    
    /**
     * 简单加锁
     */
    public boolean tryLock(String lockKey) {
        try {
            zkClient.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .forPath(lockPath + "/" + lockKey);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    /**
     * 解锁
     */
    public void unlock(String lockKey) {
        try {
            zkClient.delete().forPath(lockPath + "/" + lockKey);
        } catch (Exception e) {
            // 忽略
        }
    }
}

5.2 临时顺序节点实现

/**
 * Zookeeper 临时顺序节点实现公平锁
 * 
 * 原理:
 * 1. 在锁节点下创建临时顺序节点
 * 2. 获取所有子节点,判断自己是否是最小序号
 * 3. 如果是,获取锁成功
 * 4. 如果不是,监听前一个节点
 * 5. 前一个节点删除时,被唤醒
 */
@Service
public class ZkFairLock {
    
    private final CuratorFramework zkClient;
    private final String lockPath;
    private final ThreadLocal<String> currentPath = new ThreadLocal<>();
    
    public ZkFairLock(CuratorFramework zkClient, String lockPath) {
        this.zkClient = zkClient;
        this.lockPath = lockPath;
    }
    
    /**
     * 加锁
     */
    public void lock() throws Exception {
        // 1. 创建临时顺序节点
        String path = zkClient.create()
            .creatingParentsIfNeeded()
            .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
            .forPath(lockPath + "/lock-");
        
        currentPath.set(path);
        
        while (true) {
            // 2. 获取所有子节点
            List<String> children = zkClient.getChildren().forPath(lockPath);
            Collections.sort(children);
            
            // 3. 获取当前节点的序号
            String currentNode = path.substring(path.lastIndexOf('/') + 1);
            int currentIndex = children.indexOf(currentNode);
            
            // 4. 如果是最小序号,获取锁成功
            if (currentIndex == 0) {
                return;
            }
            
            // 5. 否则监听前一个节点
            String prevNode = children.get(currentIndex - 1);
            String prevPath = lockPath + "/" + prevNode;
            
            // 使用 CountDownLatch 等待
            CountDownLatch latch = new CountDownLatch(1);
            
            // 监听前一个节点
            Watcher watcher = event -> {
                if (event.getType() == Watcher.Event.EventType.NodeDeleted) {
                    latch.countDown();
                }
            };
            
            // 检查前一个节点是否存在
            Stat stat = zkClient.checkExists()
                .usingWatcher(watcher)
                .forPath(prevPath);
            
            if (stat == null) {
                // 前一个节点已不存在,重试
                continue;
            }
            
            // 等待前一个节点删除
            latch.await();
        }
    }
    
    /**
     * 解锁
     */
    public void unlock() {
        try {
            String path = currentPath.get();
            if (path != null) {
                zkClient.delete().forPath(path);
                currentPath.remove();
            }
        } catch (Exception e) {
            // 忽略
        }
    }
}

5.3 Curator InterProcessMutex

/**
 * Curator 框架提供的分布式锁实现
 */
@Service
public class CuratorLockService {
    
    private final CuratorFramework zkClient;
    
    public CuratorLockService(CuratorFramework zkClient) {
        this.zkClient = zkClient;
    }
    
    /**
     * 可重入锁
     */
    public void reentrantLock(String lockPath, Runnable task) {
        InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath);
        
        try {
            // 尝试加锁,最多等待10秒
            if (lock.acquire(10, TimeUnit.SECONDS)) {
                try {
                    task.run();
                } finally {
                    lock.release();
                }
            } else {
                throw new LockException("获取锁超时");
            }
        } catch (Exception e) {
            throw new LockException("锁操作异常", e);
        }
    }
    
    /**
     * 不可重入锁
     */
    public void semaphoreLock(String lockPath, Runnable task) {
        InterProcessSemaphoreMutex lock = 
            new InterProcessSemaphoreMutex(zkClient, lockPath);
        
        try {
            if (lock.acquire(10, TimeUnit.SECONDS)) {
                try {
                    task.run();
                } finally {
                    lock.release();
                }
            }
        } catch (Exception e) {
            throw new LockException("锁操作异常", e);
        }
    }
    
    /**
     * 读写锁
     */
    public class ZkReadWriteLock {
        
        private final InterProcessReadWriteLock rwLock;
        
        public ZkReadWriteLock(String lockPath) {
            this.rwLock = new InterProcessReadWriteLock(zkClient, lockPath);
        }
        
        public void readLock(Runnable task) {
            InterProcessMutex lock = rwLock.readLock();
            try {
                lock.acquire();
                try {
                    task.run();
                } finally {
                    lock.release();
                }
            } catch (Exception e) {
                throw new LockException("读锁操作异常", e);
            }
        }
        
        public void writeLock(Runnable task) {
            InterProcessMutex lock = rwLock.writeLock();
            try {
                lock.acquire();
                try {
                    task.run();
                } finally {
                    lock.release();
                }
            } catch (Exception e) {
                throw new LockException("写锁操作异常", e);
            }
        }
    }
    
    /**
     * 联锁(多锁)
     */
    public void multiLock(List<String> lockPaths, Runnable task) {
        List<InterProcessMutex> locks = lockPaths.stream()
            .map(path -> new InterProcessMutex(zkClient, path))
            .collect(Collectors.toList());
        
        InterProcessMultiLock multiLock = new InterProcessMultiLock(locks);
        
        try {
            if (multiLock.acquire(10, TimeUnit.SECONDS)) {
                try {
                    task.run();
                } finally {
                    multiLock.release();
                }
            }
        } catch (Exception e) {
            throw new LockException("联锁操作异常", e);
        }
    }
}

六、最佳实践

6.1 锁使用模板

/**
 * 分布式锁使用最佳实践
 */
@Service
public class LockBestPractices {
    
    @Autowired
    private RedissonLockService redissonLockService;
    
    /**
     * 标准锁使用模式
     */
    public <T> T executeWithLock(String lockKey, long waitTime, 
                                  long leaseTime, TimeUnit unit,
                                  Supplier<T> supplier) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 1. 尝试获取锁
            if (!lock.tryLock(waitTime, leaseTime, unit)) {
                throw new LockException("获取锁失败: " + lockKey);
            }
            
            try {
                // 2. 执行业务逻辑
                return supplier.get();
            } finally {
                // 3. 确保释放锁
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LockException("获取锁被中断", e);
        }
    }
    
    /**
     * 带降级的锁使用
     */
    public <T> T executeWithLockFallback(String lockKey, 
                                          Supplier<T> supplier,
                                          Supplier<T> fallback) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
                try {
                    return supplier.get();
                } finally {
                    lock.unlock();
                }
            } else {
                // 获取锁失败,执行降级逻辑
                return fallback.get();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return fallback.get();
        }
    }
    
    /**
     * 防止锁失效
     */
    public void safeLockUsage(String lockKey, Runnable task) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 使用看门狗自动续期
            lock.lock(30, TimeUnit.SECONDS);
            
            try {
                task.run();
            } finally {
                // 检查锁是否仍由当前线程持有
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        } catch (Exception e) {
            // 异常处理
            log.error("锁操作异常", e);
        }
    }
}

6.2 注意事项

/**
 * 分布式锁注意事项
 */
public class LockPrecautions {
    
    /**
     * 1. 锁粒度
     */
    // 锁粒度越细越好
    // 锁范围越小越好
    
    // 好的做法:锁具体资源
    String lockKey = "order:" + orderId;
    
    // 不好的做法:锁范围太大
    String lockKey = "order";
    
    /**
     * 2. 锁超时
     */
    // 必须设置合理的超时时间
    // 过短:业务未执行完锁就释放
    // 过长:异常时锁长时间不释放
    
    /**
     * 3. 异常处理
     */
    // 加锁失败的处理
    // 锁续期失败的处理
    // 解锁失败的处理
    
    /**
     * 4. 死锁预防
     */
    // 设置超时时间
    // 使用可重入锁
    // 按固定顺序加锁
    
    /**
     * 5. 性能优化
     */
    // 减少锁持有时间
    // 使用分段锁
    // 考虑读写分离
    
    /**
     * 6. 监控告警
     */
    // 锁等待时间监控
    // 锁持有时间监控
    // 锁获取失败率监控
}

六、思考与练习

思考题

  1. 基础题:Redis的SETNX + EXPIRE为什么不是原子操作?使用SET key value NX EX命令解决了什么问题?

  2. 进阶题:RedLock算法的核心思想是什么?为什么需要在多个Redis实例上同时加锁?它有什么争议?

  3. 实战题:在Redis主从架构下,主节点加锁成功但未同步到从节点就宕机,会发生什么?如何避免这种情况?

编程练习

练习:使用Redisson实现一个分布式锁工具类,要求:(1) 支持可重入锁;(2) 实现看门狗自动续期;(3) 提供加锁失败时的降级策略;(4) 完善的异常处理和日志记录。

章节关联

  • 前置章节:分布式ID生成详解
  • 后续章节:分布式事务详解
  • 扩展阅读:Redis RedLock文档、Martin Kleppmann对RedLock的批评文章

📝 下一章预告

分布式锁解决了资源互斥访问问题,但分布式系统中的事务一致性更为复杂。下一章将深入讲解分布式事务的理论与实现,包括2PC/3PC、TCC、Saga等主流方案。


本章完


参考资料: