分布式锁实现方式深度详解

目录

  1. 分布式锁核心概念
  2. 分布式锁实现方式对比
  3. Redis 分布式锁实现
  4. ZooKeeper 分布式锁实现
  5. 数据库分布式锁实现
  6. Redisson 分布式锁实现
  7. 常见问题与解决方案
  8. 最佳实践
  9. 常见疑问与解答

一、分布式锁核心概念

1.1 什么是分布式锁?

分布式锁是在分布式系统中,用于控制多个进程/线程对共享资源进行互斥访问的机制。

单机锁 vs 分布式锁:

┌────────────────────────────────────────────────────┐
│                   单机锁(JVM内)                   │
├────────────────────────────────────────────────────┤
│  Thread-1 ──┐                                      │
│             ├──> [synchronized / ReentrantLock]    │
│  Thread-2 ──┘                                      │
│                                                    │
│  特点: 只在一个JVM内有效                             │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│                 分布式锁(跨JVM)                     │
├────────────────────────────────────────────────────┤
│  JVM-1 ──┐                                         │
│          ├──> [分布式锁服务] ──┐                    │
│  JVM-2 ──┤                    ├──> 共享资源         │
│          │                    │                    │
│  JVM-3 ──┘                    ┘                    │
│                                                    │
│  特点: 跨多个JVM/服务器有效                          │
└────────────────────────────────────────────────────┘

1.2 分布式锁的核心特性

分布式锁必须满足的特性:

1. 互斥性(Mutual Exclusion)
   ✅ 同一时刻只有一个进程/线程能持有锁

2. 可重入性(Reentrant)
   ✅ 同一个进程/线程可以多次获取同一把锁

3. 锁超时(Timeout)
   ✅ 防止死锁,锁必须有过期时间

4. 高可用性(High Availability)
   ✅ 锁服务本身必须高可用,不能单点故障

5. 非阻塞性(Non-blocking)
   ✅ 获取锁失败时,应该快速失败,而不是无限等待

6. 公平性(Fairness)
   ✅ (可选)按照请求顺序获取锁

1.3 分布式锁的应用场景

典型应用场景:

1. 防止重复操作
   - 用户重复提交订单
   - 定时任务重复执行
   - 消息重复消费

2. 资源访问控制
   - 库存扣减
   - 账户余额修改
   - 文件上传处理

3. 分布式任务调度
   - 定时任务只在一个节点执行
   - 分布式计算任务分配

4. 缓存更新控制
   - 防止缓存击穿(缓存重建)
   - 防止缓存雪崩

二、分布式锁实现方式对比

2.1 实现方式概览

分布式锁实现方式对比表:

实现方式性能可靠性复杂度推荐度
Redis⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Redisson⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
ZooKeeper⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
数据库⭐⭐⭐⭐⭐⭐⭐
etcd⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

2.2 详细对比

特性RedisRedissonZooKeeper数据库etcd
性能极高(10万+ QPS)极高(10万+ QPS)中等(1万+ QPS)低(1000 QPS)高(5万+ QPS)
可靠性中等(主从复制)高(RedLock)极高(ZAB协议)高(ACID)极高(Raft)
实现复杂度简单简单中等简单中等
锁超时支持支持支持支持支持
可重入需实现原生支持需实现需实现需实现
公平锁需实现支持支持不支持需实现
Watch机制需实现支持原生支持不支持原生支持
适用场景高并发、低一致性要求高并发、中等一致性要求高一致性要求低并发、高一致性高一致性要求

三、Redis 分布式锁实现

3.1 基础实现(SET NX EX)

3.1.1 简单实现
/**
 * Redis 分布式锁(基础版)
 * 问题: 存在死锁风险(进程崩溃无法释放锁)
 */
@Service
@Slf4j
public class SimpleRedisLock {
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 获取锁
     * @param key 锁的key
     * @param value 锁的值(用于释放锁时验证)
     * @param expireTime 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String key, String value, long expireTime) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);
    }
    
    /**
     * 释放锁
     * @param key 锁的key
     * @param value 锁的值(必须匹配才能释放)
     */
    public void unlock(String key, String value) {
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";
        
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(key),
            value
        );
    }
}
3.1.2 问题分析
基础实现的问题:
1. ❌ 死锁风险
   场景: 进程获取锁后崩溃,锁无法释放
   解决: 必须设置过期时间
2. ❌ 误释放锁
   场景: 进程A获取锁(30s),执行50s,进程B获取锁,进程A释放了B的锁
   解决: 释放锁时验证value是否匹配
3. ❌ 锁续期问题
   场景: 业务执行时间超过锁过期时间
   解决: 使用看门狗机制自动续期

3.2 改进实现(带看门狗)

/**
 * Redis 分布式锁(改进版 - 带看门狗)
 */
@Service
@Slf4j
public class ImprovedRedisLock {
    @Autowired
    private StringRedisTemplate redisTemplate;
    // 锁续期线程池
    private final ScheduledExecutorService watchDogExecutor = 
        Executors.newScheduledThreadPool(10);
    // 存储锁的续期任务
    private final Map<String, ScheduledFuture<?>> watchDogTasks = new ConcurrentHashMap<>();
    
    /**
     * 获取锁(带看门狗)
     * @param key 锁的key
     * @param expireTime 过期时间(秒)
     * @param watchDogInterval 看门狗续期间隔(秒)
     * @return 锁的value(用于释放锁)
     */
    public String tryLock(String key, long expireTime, long watchDogInterval) {
        String value = UUID.randomUUID().toString();
        // 1. 尝试获取锁
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(result)) {
            // 2. 启动看门狗(自动续期)
            startWatchDog(key, value, expireTime, watchDogInterval);
            return value;
        }
        return null;
    }
    
    /**
     * 启动看门狗(自动续期)
     */
    private void startWatchDog(String key, String value, long expireTime, long interval) {
        ScheduledFuture<?> future = watchDogExecutor.scheduleAtFixedRate(() -> {
            try {
                // 检查锁是否还存在且value匹配
                String script = 
                    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "    return redis.call('expire', KEYS[1], ARGV[2]) " +
                    "else " +
                    "    return 0 " +
                    "end";
                
                Long result = redisTemplate.execute(
                    new DefaultRedisScript<>(script, Long.class),
                    Collections.singletonList(key),
                    value,
                    String.valueOf(expireTime)
                );
                
                if (result == null || result == 0) {
                    // 锁已不存在,停止续期
                    stopWatchDog(key);
                }
            } catch (Exception e) {
                log.error("Watch dog error for key: {}", key, e);
                stopWatchDog(key);
            }
        }, interval, interval, TimeUnit.SECONDS);  
        watchDogTasks.put(key, future);
    }
    
    /**
     * 停止看门狗
     */
    private void stopWatchDog(String key) {
        ScheduledFuture<?> future = watchDogTasks.remove(key);
        if (future != null) {
            future.cancel(false);
        }
    }
    
    /**
     * 释放锁
     */
    public void unlock(String key, String value) {
        // 1. 停止看门狗
        stopWatchDog(key);
        // 2. 释放锁(Lua脚本保证原子性)
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";
        
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(key),
            value
        );
    }
}

3.3 可重入锁实现

/**
 * Redis 可重入分布式锁
 */
@Service
@Slf4j
public class ReentrantRedisLock {
    @Autowired
    private StringRedisTemplate redisTemplate;
    // ThreadLocal存储当前线程的锁信息
    private final ThreadLocal<Map<String, Integer>> lockCount = ThreadLocal.withInitial(HashMap::new);
    /**
     * 获取可重入锁
     */
    public boolean tryLock(String key, String value, long expireTime) {
        Map<String, Integer> counts = lockCount.get();
        Integer count = counts.get(key);
        
        if (count != null && count > 0) {
            // 重入:增加计数
            counts.put(key, count + 1);
            return true;
        }
        // 首次获取锁
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(result)) {
            counts.put(key, 1);
            return true;
        }
        return false;
    }
    
    /**
     * 释放可重入锁
     */
    public void unlock(String key, String value) {
        Map<String, Integer> counts = lockCount.get();
        Integer count = counts.get(key);
        if (count == null || count <= 0) {
            throw new IllegalStateException("Lock not held by current thread");
        }
        if (count == 1) {
            // 最后一次释放,删除锁
            String script = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "    return redis.call('del', KEYS[1]) " +
                "else " +
                "    return 0 " +
                "end";
            
            redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Collections.singletonList(key),
                value
            );
            counts.remove(key);
        } else {
            // 减少计数
            counts.put(key, count - 1);
        }
    }
}

3.4 公平锁实现(基于List + Set)

/**
 * Redis 公平分布式锁(FIFO)
 * 
 * 实现原理:
 *  1. 使用 List 维护等待队列(FIFO)
 *  2. 使用 Set 存储已获取锁的线程
 *  3. 使用 String 存储当前持有锁的线程
 */
@Service
@Slf4j
public class FairRedisLock {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    private static final String LOCK_KEY_PREFIX = "fair:lock:";
    private static final String QUEUE_KEY_PREFIX = "fair:queue:";
    private static final String SET_KEY_PREFIX = "fair:set:";
    /**
     * 获取公平锁
     */
    public boolean tryLock(String key, String value, long expireTime, long waitTime) {
        String lockKey = LOCK_KEY_PREFIX + key;
        String queueKey = QUEUE_KEY_PREFIX + key;
        String setKey = SET_KEY_PREFIX + key;
        long startTime = System.currentTimeMillis();
        while (true) {
            // 1. 尝试获取锁
            Boolean result = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
            
            if (Boolean.TRUE.equals(result)) {
                // 2. 从等待队列中移除
                redisTemplate.opsForList().remove(queueKey, 1, value);
                redisTemplate.opsForSet().remove(setKey, value);
                return true;
            }
            // 3. 检查是否超时
            if (System.currentTimeMillis() - startTime > waitTime) {
                // 超时,从等待队列中移除
                redisTemplate.opsForList().remove(queueKey, 1, value);
                redisTemplate.opsForSet().remove(setKey, value);
                return false;
            }
            // 4. 加入等待队列(如果不在队列中)
            Boolean inQueue = redisTemplate.opsForSet().isMember(setKey, value);
            if (Boolean.FALSE.equals(inQueue)) {
                redisTemplate.opsForList().rightPush(queueKey, value);
                redisTemplate.opsForSet().add(setKey, value);
            }
            // 5. 检查是否轮到当前线程
            String first = redisTemplate.opsForList().index(queueKey, 0);
            if (value.equals(first)) {
                // 轮到我了,再次尝试获取锁
                continue;
            }
            // 6. 等待一小段时间后重试
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
    }
    
    /**
     * 释放公平锁
     */
    public void unlock(String key, String value) {
        String lockKey = LOCK_KEY_PREFIX + key;
        String queueKey = QUEUE_KEY_PREFIX + key;
        String setKey = SET_KEY_PREFIX + key;
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    redis.call('del', KEYS[1]) " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";
        
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Arrays.asList(lockKey),
            value
        );
    }
}

四、ZooKeeper 分布式锁实现

4.1 ZooKeeper 锁原理

ZooKeeper 分布式锁原理:

1. 临时顺序节点(Ephemeral Sequential Node)
   /lock/resource_0000000001
   /lock/resource_0000000002
   /lock/resource_0000000003

2. 最小序号获取锁
   - 节点序号最小的获得锁
   - 其他节点监听前一个节点的删除事件

3. 锁释放
   - 节点删除(临时节点,会话断开自动删除)
   - 下一个节点被通知,尝试获取锁

4.2 基础实现

/**
 * ZooKeeper 分布式锁
 */
@Service
@Slf4j
public class ZooKeeperLock {
    private final CuratorFramework client;
    private static final String LOCK_PATH = "/locks";
    public ZooKeeperLock(CuratorFramework client) {
        this.client = client;
    }
    
    /**
     * 获取锁
     */
    public InterProcessMutex tryLock(String resource) throws Exception {
        String lockPath = LOCK_PATH + "/" + resource;
        InterProcessMutex lock = new InterProcessMutex(client, lockPath);
        // 尝试获取锁(最多等待10秒)
        boolean acquired = lock.acquire(10, TimeUnit.SECONDS);
        if (acquired) {
            return lock;
        }
        return null;
    }
    
    /**
     * 释放锁
     */
    public void unlock(InterProcessMutex lock) {
        try {
            if (lock != null && lock.isAcquiredInThisProcess()) {
                lock.release();
            }
        } catch (Exception e) {
            log.error("Failed to release lock", e);
        }
    }
}

4.3 可重入锁实现

/**
 * ZooKeeper 可重入分布式锁
 */
@Service
@Slf4j
public class ReentrantZooKeeperLock {
    
    private final CuratorFramework client;
    private static final String LOCK_PATH = "/locks";
    
    // ThreadLocal存储当前线程的锁信息
    private final ThreadLocal<Map<String, InterProcessMutex>> locks = 
        ThreadLocal.withInitial(HashMap::new);
    
    public ReentrantZooKeeperLock(CuratorFramework client) {
        this.client = client;
    }
    
    /**
     * 获取可重入锁
     */
    public boolean tryLock(String resource) throws Exception {
        Map<String, InterProcessMutex> lockMap = locks.get();
        InterProcessMutex lock = lockMap.get(resource);
        
        if (lock != null) {
            // 重入:直接返回true(Curator的InterProcessMutex本身支持可重入)
            return true;
        }
        
        // 首次获取锁
        String lockPath = LOCK_PATH + "/" + resource;
        lock = new InterProcessMutex(client, lockPath);
        
        boolean acquired = lock.acquire(10, TimeUnit.SECONDS);
        if (acquired) {
            lockMap.put(resource, lock);
            return true;
        }
        
        return false;
    }
    
    /**
     * 释放可重入锁
     */
    public void unlock(String resource) {
        Map<String, InterProcessMutex> lockMap = locks.get();
        InterProcessMutex lock = lockMap.get(resource);
        
        if (lock != null) {
            try {
                lock.release();
                lockMap.remove(resource);
            } catch (Exception e) {
                log.error("Failed to release lock: {}", resource, e);
            }
        }
    }
}

4.4 ZooKeeper 配置

/**
 * ZooKeeper 配置
 */
@Configuration
public class ZooKeeperConfig {
    
    @Value("${zookeeper.connect-string:localhost:2181}")
    private String connectString;
    
    @Value("${zookeeper.session-timeout:30000}")
    private int sessionTimeout;
    
    @Bean
    public CuratorFramework curatorFramework() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        
        CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString(connectString)
            .sessionTimeoutMs(sessionTimeout)
            .retryPolicy(retryPolicy)
            .build();
        
        client.start();
        return client;
    }
}

五、数据库分布式锁实现

5.1 基于唯一索引实现

/**
 * 数据库分布式锁(基于唯一索引)
 */
@Service
@Slf4j
public class DatabaseLock {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 获取锁
     */
    public boolean tryLock(String lockName, String owner, long expireTime) {
        String sql = "INSERT INTO distributed_lock (lock_name, owner, expire_time, create_time) " +
                     "VALUES (?, ?, ?, NOW()) " +
                     "ON DUPLICATE KEY UPDATE " +
                     "  owner = IF(expire_time < NOW(), VALUES(owner), owner), " +
                     "  expire_time = IF(expire_time < NOW(), VALUES(expire_time), expire_time)";
        
        try {
            int rows = jdbcTemplate.update(sql, lockName, owner, 
                new Timestamp(System.currentTimeMillis() + expireTime));
            return rows > 0;
        } catch (DuplicateKeyException e) {
            // 锁已被其他进程持有
            return false;
        }
    }
    
    /**
     * 释放锁
     */
    public void unlock(String lockName, String owner) {
        String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND owner = ?";
        jdbcTemplate.update(sql, lockName, owner);
    }
    
    /**
     * 锁续期
     */
    public boolean renewLock(String lockName, String owner, long expireTime) {
        String sql = "UPDATE distributed_lock " +
                     "SET expire_time = DATE_ADD(NOW(), INTERVAL ? SECOND) " +
                     "WHERE lock_name = ? AND owner = ? AND expire_time > NOW()";
        
        int rows = jdbcTemplate.update(sql, expireTime / 1000, lockName, owner);
        return rows > 0;
    }
}

5.2 数据库表结构

CREATE TABLE distributed_lock (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    lock_name VARCHAR(128) NOT NULL UNIQUE COMMENT '锁名称',
    owner VARCHAR(128) NOT NULL COMMENT '锁持有者',
    expire_time TIMESTAMP NOT NULL COMMENT '过期时间',
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    INDEX idx_expire_time (expire_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁表';

5.3 定时清理过期锁

/**
 * 定时清理过期锁
 */
@Component
@Slf4j
public class LockCleanupTask {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Scheduled(fixedRate = 60000) // 每分钟执行一次
    public void cleanupExpiredLocks() {
        String sql = "DELETE FROM distributed_lock WHERE expire_time < NOW()";
        int deleted = jdbcTemplate.update(sql);
        if (deleted > 0) {
            log.info("Cleaned up {} expired locks", deleted);
        }
    }
}

六、Redisson 分布式锁实现

6.1 Redisson 简介

Redisson 是 Redis 的 Java 客户端,提供了丰富的分布式对象和服务,包括分布式锁。

Redisson 分布式锁特性:

✅ 可重入锁(ReentrantLock)
✅ 公平锁(FairLock)
✅ 读写锁(ReadWriteLock)
✅ 信号量(Semaphore)
✅ 闭锁(CountDownLatch)
✅ 看门狗机制(自动续期)
✅ 异步支持

6.2 Redisson 配置

/**
 * Redisson 配置
 */
@Configuration
public class RedissonConfig {
    
    @Value("${redis.host:localhost}")
    private String host;
    
    @Value("${redis.port:6379}")
    private int port;
    
    @Value("${redis.password:}")
    private String password;
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        
        // 单节点模式
        config.useSingleServer()
            .setAddress("redis://" + host + ":" + port)
            .setPassword(password)
            .setConnectionPoolSize(10)
            .setConnectionMinimumIdleSize(5);
        
        // 集群模式(可选)
        // config.useClusterServers()
        //     .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001");
        
        return Redisson.create(config);
    }
}

6.3 可重入锁使用

/**
 * Redisson 可重入锁使用示例
 */
@Service
@Slf4j
public class RedissonLockService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 可重入锁示例
     */
    public void doWithLock(String resource) {
        RLock lock = redissonClient.getLock("lock:" + resource);
        
        try {
            // 尝试获取锁,最多等待10秒,锁定后30秒自动解锁
            boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
            
            if (acquired) {
                // 业务逻辑
                doBusinessLogic(resource);
            } else {
                log.warn("Failed to acquire lock: {}", resource);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("Interrupted while acquiring lock", e);
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 公平锁示例
     */
    public void doWithFairLock(String resource) {
        RLock lock = redissonClient.getFairLock("fair:lock:" + resource);
        
        try {
            boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (acquired) {
                doBusinessLogic(resource);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 读写锁示例
     */
    public void doWithReadWriteLock(String resource) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw:lock:" + resource);
        RLock readLock = readWriteLock.readLock();
        RLock writeLock = readWriteLock.writeLock();
        
        // 读锁(共享)
        readLock.lock();
        try {
            // 读操作
            readData(resource);
        } finally {
            readLock.unlock();
        }
        
        // 写锁(独占)
        writeLock.lock();
        try {
            // 写操作
            writeData(resource);
        } finally {
            writeLock.unlock();
        }
    }
    
    /**
     * 信号量示例(限流)
     */
    public void doWithSemaphore(String resource, int permits) {
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore:" + resource);
        
        try {
            // 尝试获取许可
            boolean acquired = semaphore.tryAcquire(permits, 10, TimeUnit.SECONDS);
            if (acquired) {
                // 执行业务逻辑
                doBusinessLogic(resource);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 释放许可
            semaphore.release(permits);
        }
    }
    
    private void doBusinessLogic(String resource) {
        // 业务逻辑
    }
    
    private void readData(String resource) {
        // 读操作
    }
    
    private void writeData(String resource) {
        // 写操作
    }
}

6.4 RedLock 实现(多Redis实例)

/**
 * RedLock 实现(多Redis实例,提高可靠性)
 */
@Service
@Slf4j
public class RedLockService {
    
    @Autowired
    private RedissonClient redissonClient1;
    
    @Autowired
    private RedissonClient redissonClient2;
    
    @Autowired
    private RedissonClient redissonClient3;
    
    /**
     * RedLock 获取锁(需要大多数节点成功)
     */
    public boolean tryRedLock(String resource, long waitTime, long leaseTime) {
        RLock lock1 = redissonClient1.getLock("lock:" + resource);
        RLock lock2 = redissonClient2.getLock("lock:" + resource);
        RLock lock3 = redissonClient3.getLock("lock:" + resource);
        
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        
        try {
            boolean acquired = redLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (acquired) {
                // 业务逻辑
                doBusinessLogic(resource);
                return true;
            }
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            if (redLock.isHeldByCurrentThread()) {
                redLock.unlock();
            }
        }
    }
    
    private void doBusinessLogic(String resource) {
        // 业务逻辑
    }
}

七、常见问题与解决方案

7.1 死锁问题

问题: 进程获取锁后崩溃,锁无法释放

解决方案:
  1. 设置锁过期时间(TTL)
  2. 使用看门狗机制自动续期
  3. 使用临时节点(ZooKeeper)

7.2 锁误释放问题

问题: 进程A的锁过期,进程B获取锁,进程A释放了B的锁

解决方案:
  1. 释放锁时验证value是否匹配(Redis)
  2. 使用唯一标识(UUID)作为锁的值
  3. 使用Lua脚本保证原子性

7.3 锁续期问题

问题: 业务执行时间超过锁过期时间

解决方案:
  1. 使用看门狗机制(Redisson)
  2. 合理设置锁过期时间
  3. 异步续期任务

7.4 时钟漂移问题

问题: 不同服务器时钟不同步,导致锁过期时间不准确

解决方案:
  1. 使用NTP同步时钟
  2. 使用相对时间而非绝对时间
  3. 增加锁过期时间的缓冲时间

7.5 网络分区问题

问题: 网络分区导致锁状态不一致

解决方案:
  1. 使用RedLock(多Redis实例)
  2. 使用ZooKeeper(ZAB协议保证一致性)
  3. 使用etcd(Raft协议保证一致性)

八、最佳实践

8.1 锁命名规范

/**
 * 锁命名规范
 */
public class LockNaming {
    
    /**
     * 格式: {业务模块}:{资源类型}:{资源ID}
     * 
     * 示例:
     *   order:create:user123
     *   inventory:deduct:product456
     *   payment:process:order789
     */
    public static String generateLockKey(String module, String resourceType, String resourceId) {
        return String.format("%s:%s:%s", module, resourceType, resourceId);
    }
}

8.2 锁超时时间设置

/**
 * 锁超时时间设置建议
 */
public class LockTimeout {
    
    /**
     * 根据业务场景设置合理的超时时间
     * 
     * 快速操作: 5-10秒
     * 一般操作: 30-60秒
     * 慢速操作: 5-10分钟
     */
    public static long getTimeout(String operationType) {
        switch (operationType) {
            case "QUICK": return 10;      // 10秒
            case "NORMAL": return 60;     // 60秒
            case "SLOW": return 600;      // 10分钟
            default: return 30;           // 默认30秒
        }
    }
}

8.3 锁使用模板

/**
 * 分布式锁使用模板
 */
@Component
@Slf4j
public class LockTemplate {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 执行带锁的业务逻辑
     */
    public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime, 
                                 Supplier<T> supplier) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            boolean acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (!acquired) {
                throw new LockAcquisitionException("Failed to acquire lock: " + lockKey);
            }
            
            return supplier.get();
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LockAcquisitionException("Interrupted while acquiring lock", e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 执行带锁的业务逻辑(无返回值)
     */
    public void executeWithLock(String lockKey, long waitTime, long leaseTime, 
                                Runnable runnable) {
        executeWithLock(lockKey, waitTime, leaseTime, () -> {
            runnable.run();
            return null;
        });
    }
}

8.4 使用示例

/**
 * 分布式锁使用示例
 */
@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private LockTemplate lockTemplate;
    
    /**
     * 创建订单(防止重复提交)
     */
    public Order createOrder(CreateOrderRequest request) {
        String lockKey = "order:create:" + request.getUserId();
        
        return lockTemplate.executeWithLock(
            lockKey,
            5,      // 等待5秒
            30,     // 锁定30秒
            () -> {
                // 检查是否重复提交
                Order existingOrder = orderMapper.selectByRequestId(request.getRequestId());
                if (existingOrder != null) {
                    return existingOrder;
                }
                
                // 创建订单
                Order order = new Order();
                // ... 设置订单信息
                orderMapper.insert(order);
                
                return order;
            }
        );
    }
    
    /**
     * 扣减库存(防止超卖)
     */
    public void deductInventory(String productId, int quantity) {
        String lockKey = "inventory:deduct:" + productId;
        
        lockTemplate.executeWithLock(
            lockKey,
            10,     // 等待10秒
            60,     // 锁定60秒
            () -> {
                // 查询库存
                Inventory inventory = inventoryMapper.selectByProductId(productId);
                
                // 检查库存是否充足
                if (inventory.getStock() < quantity) {
                    throw new InsufficientStockException("库存不足");
                }
                
                // 扣减库存
                inventory.setStock(inventory.getStock() - quantity);
                inventoryMapper.updateById(inventory);
            }
        );
    }
}

8.5 监控和告警

/**
 * 分布式锁监控
 */
@Component
@Slf4j
public class LockMonitor {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    private final Counter lockAcquireCounter;
    private final Counter lockReleaseCounter;
    private final Counter lockTimeoutCounter;
    private final Timer lockHoldTimer;
    
    public LockMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        this.lockAcquireCounter = Counter.builder("distributed.lock.acquire.total")
            .description("分布式锁获取总数")
            .register(meterRegistry);
        
        this.lockReleaseCounter = Counter.builder("distributed.lock.release.total")
            .description("分布式锁释放总数")
            .register(meterRegistry);
        
        this.lockTimeoutCounter = Counter.builder("distributed.lock.timeout.total")
            .description("分布式锁超时总数")
            .register(meterRegistry);
        
        this.lockHoldTimer = Timer.builder("distributed.lock.hold.duration")
            .description("分布式锁持有时间")
            .register(meterRegistry);
    }
    
    /**
     * 记录锁获取
     */
    public void recordLockAcquire(String lockKey, boolean success) {
        lockAcquireCounter.increment(Tags.of("lock", lockKey, "success", String.valueOf(success)));
        if (!success) {
            lockTimeoutCounter.increment(Tags.of("lock", lockKey));
        }
    }
    
    /**
     * 记录锁释放
     */
    public void recordLockRelease(String lockKey) {
        lockReleaseCounter.increment(Tags.of("lock", lockKey));
    }
    
    /**
     * 记录锁持有时间
     */
    public void recordLockHoldDuration(String lockKey, long durationMs) {
        lockHoldTimer.record(durationMs, TimeUnit.MILLISECONDS, Tags.of("lock", lockKey));
    }
}

九、常见疑问与解答

9.1 基础概念疑问

Q1: 为什么单机锁(synchronized/ReentrantLock)在分布式环境下不够用?

疑问: 既然单机锁已经能保证线程安全,为什么还需要分布式锁?

解答:

单机锁的局限性:

场景示例:
  ┌──────────────┐      ┌─────────────┐      ┌─────────────┐
  │  服务器A     │      │  服务器B     │      │  服务器C     │
  │  JVM-1       │      │  JVM-2      │      │  JVM-3      │
  │              │      │              │      │              │
  │ synchronized │      │ synchronized │      │ synchronized │
  │  锁A         │      │  锁B         │      │  锁C         │
  └──────────────┘      └─────────────┘      └─────────────┘
        │                     │                     │
        └─────────────────────┴─────────────────────┘
                              │
                    ┌─────────▼─────────┐
                    │   共享数据库       │
                    │   库存: 100        │
                    └──────────────────┘

问题:
  1. 每个JVM的锁是独立的,无法感知其他JVM的锁
  2. 三个服务器同时扣减库存,可能导致超卖
  3. 需要跨JVM的协调机制

解决方案:
  使用分布式锁,所有服务器共享同一个锁状态

代码示例:

// ❌ 错误:单机锁无法防止分布式环境下的并发问题
public class InventoryService {
    private int stock = 100;
    private final Object lock = new Object();
    
    public void deductStock(int quantity) {
        synchronized (lock) {  // 只在当前JVM有效
            if (stock >= quantity) {
                stock -= quantity;
            }
        }
    }
}

// ✅ 正确:使用分布式锁
public class InventoryService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void deductStock(int quantity) {
        RLock lock = redissonClient.getLock("inventory:product:123");
        try {
            lock.lock();
            // 从数据库查询库存
            Inventory inventory = inventoryMapper.selectById(123);
            if (inventory.getStock() >= quantity) {
                inventory.setStock(inventory.getStock() - quantity);
                inventoryMapper.updateById(inventory);
            }
        } finally {
            lock.unlock();
        }
    }
}

Q2: 分布式锁和数据库事务有什么区别?什么时候用哪个?

疑问: 数据库事务已经能保证ACID,为什么还需要分布式锁?

解答:

分布式锁 vs 数据库事务:

┌─────────────────────────────────────────────────────────┐
│                   数据库事务                            │
├─────────────────────────────────────────────────────────┤
│  作用范围: 单个数据库连接内                              │
│  保证: ACID(原子性、一致性、隔离性、持久性)            │
│  粒度: 数据库操作级别                                    │
│  场景: 单个请求内的多个数据库操作                        │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                   分布式锁                              │
├─────────────────────────────────────────────────────────┤
│  作用范围: 跨进程、跨服务器                              │
│  保证: 互斥访问                                          │
│  粒度: 业务逻辑级别                                      │
│  场景: 跨请求的业务逻辑互斥                              │
└─────────────────────────────────────────────────────────┘

使用场景对比:

// 场景1: 数据库事务适用(单个请求内的操作)
@Transactional
public void transferMoney(Long fromAccount, Long toAccount, BigDecimal amount) {
    // 这些操作在同一个事务中,要么全部成功,要么全部回滚
    accountMapper.deduct(fromAccount, amount);
    accountMapper.add(toAccount, amount);
}

// 场景2: 分布式锁适用(跨请求的业务逻辑)
public void createOrder(CreateOrderRequest request) {
    String lockKey = "order:create:" + request.getUserId();
    RLock lock = redissonClient.getLock(lockKey);
    
    try {
        lock.lock();
        // 防止用户重复提交订单(跨请求)
        Order existingOrder = orderMapper.selectByRequestId(request.getRequestId());
        if (existingOrder != null) {
            return existingOrder;
        }
        
        // 创建订单(内部可能包含事务)
        return createOrderInternal(request);
    } finally {
        lock.unlock();
    }
}

选择原则:

  • 数据库事务: 单个请求内的多个数据库操作需要原子性
  • 分布式锁: 跨请求的业务逻辑需要互斥访问

9.2 实现细节疑问

Q3: Redis分布式锁为什么要用Lua脚本?直接用Redis命令不行吗?

疑问: 为什么释放锁要用Lua脚本,直接用DEL命令不行吗?

解答:

问题场景:

时间线:
  T1: 进程A获取锁(过期时间30秒)
  T2: 进程A执行业务逻辑(耗时40秒)
  T3: 锁过期,进程B获取锁
  T4: 进程A执行完成,释放锁(误释放了B的锁)
  T5: 进程C获取锁(此时B和C都认为持有锁)

❌ 错误实现:
public void unlock(String key) {
    redisTemplate.delete(key);  // 可能删除其他进程的锁
}

✅ 正确实现(Lua脚本保证原子性):
public void unlock(String key, String value) {
    String script = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "    return redis.call('del', KEYS[1]) " +
        "else " +
        "    return 0 " +
        "end";
    
    redisTemplate.execute(
        new DefaultRedisScript<>(script, Long.class),
        Collections.singletonList(key),
        value
    );
}

为什么需要Lua脚本:

非原子操作的问题:

步骤1: GET key → 返回 "value-A"
步骤2: 判断 value == "value-A" → true
步骤3: DEL key → 删除锁

问题:
  在步骤2和步骤3之间,如果锁被其他进程修改,步骤3可能删除错误的锁

Lua脚本的优势:
  ✅ 原子性:整个脚本作为一个原子操作执行
  ✅ 一致性:执行过程中不会被其他命令打断
  ✅ 性能:减少网络往返次数

Q4: 看门狗机制是怎么工作的?为什么需要它?

疑问: 既然设置了锁过期时间,为什么还需要看门狗自动续期?

解答:

看门狗机制的必要性:

场景1: 锁过期时间设置过长
  ❌ 问题: 如果进程崩溃,锁要等很久才能自动释放
  ✅ 解决: 设置较短的过期时间(如30秒)

场景2: 锁过期时间设置过短
  ❌ 问题: 业务逻辑执行时间超过锁过期时间,锁被提前释放
  ✅ 解决: 使用看门狗自动续期

看门狗工作原理:

时间线:
  T0: 获取锁,设置过期时间30秒,启动看门狗(每10秒续期一次)
  T10: 看门狗检查锁是否还存在,如果存在则续期30秒
  T20: 看门狗再次续期30秒
  T30: 业务逻辑执行完成,释放锁,停止看门狗

代码示例:

/**
 * 看门狗机制实现
 */
public class WatchDogLock {
    
    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    private ScheduledFuture<?> watchDogTask;
    
    public void lock(String key, String value, long expireTime) {
        // 1. 获取锁
        redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        
        // 2. 启动看门狗(在过期时间的1/3时续期)
        long renewInterval = expireTime / 3;
        watchDogTask = executor.scheduleAtFixedRate(() -> {
            String script = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "    return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "    return 0 " +
                "end";
            
            redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Collections.singletonList(key),
                value,
                String.valueOf(expireTime)
            );
        }, renewInterval, renewInterval, TimeUnit.SECONDS);
    }
    
    public void unlock(String key, String value) {
        // 停止看门狗
        if (watchDogTask != null) {
            watchDogTask.cancel(false);
        }
        
        // 释放锁
        // ...
    }
}

看门狗的优势:

  • ✅ 自动续期,避免锁提前过期
  • ✅ 进程崩溃时,看门狗停止,锁自动过期
  • ✅ 无需手动估算业务执行时间

Q5: RedLock算法真的能解决所有问题吗?有什么局限性?

疑问: RedLock号称能解决Redis单点故障问题,它真的完美吗?

解答:

RedLock算法原理:

1. 客户端向N个Redis实例发送加锁请求
2. 如果大多数(N/2+1)实例加锁成功,则认为获取锁成功
3. 释放锁时,向所有实例发送释放请求

示例(5个Redis实例):
  Redis-1: ✅ 加锁成功
  Redis-2: ✅ 加锁成功
  Redis-3: ✅ 加锁成功
  Redis-4: ❌ 加锁失败
  Redis-5: ❌ 加锁失败
  
  结果: 3/5 > 50%,获取锁成功

RedLock的局限性:

1. 时钟同步问题
   问题: 如果不同Redis服务器时钟不同步,可能导致锁提前过期
   场景:
     - Redis-1时钟快2秒,锁在T+28秒过期
     - Redis-2时钟正常,锁在T+30秒过期
     - 客户端认为锁还有效,但Redis-1的锁已过期
   
   解决: 使用NTP同步时钟,增加锁过期时间的缓冲

2. 网络延迟问题
   问题: 网络延迟可能导致锁状态不一致
   场景:
     - T1: 客户端向5个Redis发送加锁请求
     - T2: 3个Redis响应成功(网络快)
     - T3: 客户端认为获取锁成功
     - T4: 另外2个Redis响应成功(网络慢)
     - 结果: 实际上5个Redis都加锁成功,但客户端只收到3个响应
   
   解决: 设置合理的超时时间,等待所有响应

3. 性能开销
   问题: 需要向多个Redis实例发送请求,性能开销大
   场景: 5个Redis实例,每次加锁需要5次网络请求
   
   解决: 权衡可靠性和性能,选择合适的实例数量(通常3-5个)

4. 脑裂问题
   问题: 网络分区可能导致多个客户端同时持有锁
   场景:
     - 网络分区1: Redis-1, Redis-2, Redis-3, 客户端A
     - 网络分区2: Redis-4, Redis-5, 客户端B
     - 客户端A在分区1中获取锁(3/5)
     - 客户端B在分区2中获取锁(2/5,但分区2认为自己是多数)
   
   解决: 使用ZooKeeper等强一致性系统

Martin Kleppmann的质疑:

Martin Kleppmann(《数据密集型应用系统设计》作者)对RedLock的质疑:

1. RedLock假设所有Redis实例时钟同步
   → 实际环境中很难保证

2. RedLock假设网络延迟可预测
   → 实际网络延迟波动很大

3. RedLock无法保证锁的安全性
   → 在某些故障场景下,可能多个客户端同时持有锁

建议:
  - 如果只需要效率(efficiency),使用单Redis实例即可
  - 如果需要正确性(correctness),使用ZooKeeper等强一致性系统

9.3 性能与可靠性疑问

Q6: Redis分布式锁在高并发场景下性能如何?会不会成为瓶颈?

疑问: 如果所有请求都要获取锁,Redis会不会成为性能瓶颈?

解答:

Redis性能分析:

单Redis实例性能:
  - SET命令: ~100,000 QPS
  - GET命令: ~100,000 QPS
  - Lua脚本: ~80,000 QPS

分布式锁操作:
  1. SET NX EX(获取锁): ~100,000 QPS
  2. Lua脚本(释放锁): ~80,000 QPS

实际场景分析:

场景: 电商秒杀活动
  - 并发用户: 10,000
  - 每个用户请求: 1次锁操作(获取锁 + 释放锁)
  - 总QPS: 10,000 × 2 = 20,000 QPS
  
  结论: Redis单实例可以轻松应对(100,000 QPS >> 20,000 QPS)

场景: 高频交易系统
  - 并发请求: 100,000
  - 每个请求: 1次锁操作
  - 总QPS: 100,000 × 2 = 200,000 QPS
  
  结论: 需要Redis集群或优化锁粒度

性能优化策略:

/**
 * 性能优化示例
 */
public class OptimizedLockService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 优化1: 减少锁粒度
     */
    public void deductInventory(String productId, int quantity) {
        // ❌ 粗粒度锁(锁整个库存表)
        // RLock lock = redissonClient.getLock("inventory:all");
        
        // ✅ 细粒度锁(只锁单个商品)
        RLock lock = redissonClient.getLock("inventory:product:" + productId);
        
        try {
            lock.lock();
            // 业务逻辑
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 优化2: 使用读写锁(读多写少场景)
     */
    public Inventory getInventory(String productId) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("inventory:product:" + productId);
        RLock readLock = readWriteLock.readLock();
        
        try {
            readLock.lock();
            // 读操作(多个读可以并发)
            return inventoryMapper.selectById(productId);
        } finally {
            readLock.unlock();
        }
    }
    
    /**
     * 优化3: 锁超时快速失败
     */
    public void quickFailLock(String key) {
        RLock lock = redissonClient.getLock(key);
        
        try {
            // 快速失败,不等待
            boolean acquired = lock.tryLock(0, 30, TimeUnit.SECONDS);
            if (!acquired) {
                throw new LockAcquisitionException("Lock busy");
            }
            // 业务逻辑
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

性能对比:

锁粒度对性能的影响:

粗粒度锁(锁整个表):
  - 并发度: 1(所有请求串行)
  - QPS: ~1,000

细粒度锁(锁单条记录):
  - 并发度: N(N条记录可以并发)
  - QPS: ~10,000(假设100条记录)

读写锁(读多写少):
  - 读并发度: 无限(多个读可以并发)
  - 写并发度: 1(写操作串行)
  - QPS: ~50,000(假设读:写 = 10:1)

Q7: ZooKeeper分布式锁为什么比Redis更可靠?代价是什么?

疑问: 为什么说ZooKeeper分布式锁更可靠?它有什么代价?

解答:

可靠性对比:

┌────────────────────────────────────────────────────┐
│                   Redis分布式锁                     │
├────────────────────────────────────────────────────┤
│  可靠性机制: 主从复制(异步)                        │
│  一致性保证: 最终一致性                              │
│  故障场景:                                          │
│    - 主节点崩溃 → 从节点提升为主(可能丢失部分数据)   │
│    - 网络分区 → 可能出现脑裂                         │
│  适用场景: 高并发、可容忍短暂不一致                   │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│                ZooKeeper分布式锁                    │
├────────────────────────────────────────────────────┤
│  可靠性机制: ZAB协议(强一致性)                     │
│  一致性保证: 强一致性(线性一致性)                   │
│  故障场景:                                          │
│    - 节点崩溃 → 自动故障转移(不丢失数据)            │
│    - 网络分区 → 少数派节点停止服务(保证一致性)       │
│  适用场景: 高一致性要求、可容忍较低性能               │
└────────────────────────────────────────────────────┘

ZooKeeper的代价:

1. 性能代价
   Redis: ~100,000 QPS
   ZooKeeper: ~10,000 QPS(性能低10倍)

2. 复杂度代价
   Redis: 简单(SET NX EX)
   ZooKeeper: 复杂(临时顺序节点、Watch机制)

3. 资源代价
   Redis: 内存占用小
   ZooKeeper: 需要更多内存和磁盘空间

4. 运维代价
   Redis: 运维简单
   ZooKeeper: 需要维护集群,配置复杂

选择建议:

选择Redis分布式锁:
  ✅ 高并发场景(QPS > 10,000)
  ✅ 可容忍短暂不一致
  ✅ 性能优先
  ✅ 简单易用

选择ZooKeeper分布式锁:
  ✅ 高一致性要求
  ✅ 可容忍较低性能
  ✅ 需要强一致性保证
  ✅ 已有ZooKeeper基础设施

9.4 实际应用疑问

Q8: 分布式锁会不会导致性能下降?如何评估是否需要使用?

疑问: 使用分布式锁会不会让系统变慢?什么时候该用,什么时候不该用?

解答:

性能影响分析:

无锁场景:
  - 操作耗时: 10ms
  - QPS: 100

使用分布式锁场景:
  - 获取锁: 2ms
  - 业务操作: 10ms
  - 释放锁: 1ms
  - 总耗时: 13ms
  - QPS: 77(下降23%)

结论: 分布式锁确实会带来性能开销,但通常是可接受的

是否需要分布式锁的判断标准:

/**
 * 判断是否需要分布式锁的决策树
 */
public class LockDecisionTree {
    
    /**
     * 场景1: 单机应用 → 不需要分布式锁
     */
    public void singleMachineScenario() {
        // 使用synchronized或ReentrantLock即可
        synchronized (this) {
            // 业务逻辑
        }
    }
    
    /**
     * 场景2: 分布式应用 + 无共享资源竞争 → 不需要分布式锁
     */
    public void noResourceContention() {
        // 每个请求处理不同的资源,无竞争
        // 例如: 用户A修改自己的信息,用户B修改自己的信息
        // → 不需要锁
    }
    
    /**
     * 场景3: 分布式应用 + 有共享资源竞争 → 需要分布式锁
     */
    public void hasResourceContention() {
        // 多个请求竞争同一资源
        // 例如: 多个用户同时购买同一商品(库存竞争)
        // → 需要分布式锁
    }
    
    /**
     * 场景4: 数据库唯一约束可以解决 → 不需要分布式锁
     */
    public void databaseConstraint() {
        // 例如: 防止重复订单
        // 方案1: 使用分布式锁
        // 方案2: 使用数据库唯一索引(更简单)
        
        // ✅ 推荐: 使用数据库唯一索引
        try {
            orderMapper.insert(order);  // 唯一索引防止重复
        } catch (DuplicateKeyException e) {
            // 重复订单,返回已有订单
            return orderMapper.selectByRequestId(request.getRequestId());
        }
    }
}

性能优化建议:

/**
 * 减少分布式锁的使用
 */
public class LockOptimization {
    
    /**
     * 优化1: 使用数据库唯一约束代替分布式锁
     */
    public void useDatabaseConstraint() {
        // ❌ 使用分布式锁防止重复订单
        // RLock lock = redissonClient.getLock("order:create:" + userId);
        
        // ✅ 使用数据库唯一索引
        // ALTER TABLE orders ADD UNIQUE KEY uk_request_id (request_id);
        try {
            orderMapper.insert(order);
        } catch (DuplicateKeyException e) {
            // 处理重复订单
        }
    }
    
    /**
     * 优化2: 使用乐观锁代替分布式锁
     */
    public void useOptimisticLock() {
        // ❌ 使用分布式锁
        // RLock lock = redissonClient.getLock("inventory:product:" + productId);
        
        // ✅ 使用数据库乐观锁(版本号)
        Inventory inventory = inventoryMapper.selectById(productId);
        int oldVersion = inventory.getVersion();
        
        inventory.setStock(inventory.getStock() - quantity);
        int updated = inventoryMapper.updateByIdAndVersion(
            inventory, oldVersion
        );
        
        if (updated == 0) {
            // 版本冲突,重试
            throw new OptimisticLockException();
        }
    }
    
    /**
     * 优化3: 减少锁持有时间
     */
    public void minimizeLockTime() {
        RLock lock = redissonClient.getLock("resource:123");
        
        try {
            lock.lock();
            
            // ❌ 在锁内执行耗时操作
            // heavyComputation();
            // networkCall();
            
            // ✅ 只锁关键操作
            Resource resource = resourceMapper.selectById(123);
            resource.setStatus("PROCESSING");
            resourceMapper.updateById(resource);
            
        } finally {
            lock.unlock();
        }
        
        // 在锁外执行耗时操作
        heavyComputation();
        networkCall();
    }
}

Q9: 分布式锁在微服务架构中如何设计?有哪些最佳实践?

疑问: 在微服务架构中,分布式锁应该如何设计和使用?

解答:

微服务架构中的分布式锁设计:

┌─────────────────────────────────────────────────────────┐
│                   微服务架构示例                          │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  服务A ──┐                                               │
│         │                                               │
│  服务B ──┼──> [分布式锁服务] ──┐                          │
│         │                    │                          │
│  服务C ──┘                    ├──> 共享资源(数据库)     │
│                                │                          │
│  服务D ───────────────────────┘                          │
│                                                          │
└─────────────────────────────────────────────────────────┘

最佳实践:

/**
 * 微服务架构中的分布式锁最佳实践
 */
@Service
@Slf4j
public class MicroserviceLockService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 实践1: 锁命名规范(服务名:资源类型:资源ID)
     */
    public void lockNamingConvention(String serviceName, String resourceType, String resourceId) {
        String lockKey = String.format("%s:%s:%s", serviceName, resourceType, resourceId);
        // 示例: "order-service:create:user123"
        // 示例: "inventory-service:deduct:product456"
    }
    
    /**
     * 实践2: 锁超时时间根据业务场景设置
     */
    public void lockTimeoutByScenario(String operationType) {
        long timeout;
        switch (operationType) {
            case "QUICK":   timeout = 5; break;   // 5秒(快速操作)
            case "NORMAL":  timeout = 30; break;  // 30秒(一般操作)
            case "SLOW":    timeout = 300; break; // 5分钟(慢速操作)
            default:        timeout = 30; break;
        }
        
        RLock lock = redissonClient.getLock("lock:key");
        try {
            lock.tryLock(timeout, TimeUnit.SECONDS);
            // 业务逻辑
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 实践3: 使用锁模板减少重复代码
     */
    public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime, 
                                 Supplier<T> supplier) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            boolean acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (!acquired) {
                throw new LockAcquisitionException("Failed to acquire lock: " + lockKey);
            }
            return supplier.get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LockAcquisitionException("Interrupted", e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 实践4: 分布式锁与事务结合使用
     */
    @Transactional
    public void lockWithTransaction(String resourceId) {
        String lockKey = "resource:" + resourceId;
        
        // 在事务外获取锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock();
            
            // 在事务内执行数据库操作
            Resource resource = resourceMapper.selectById(resourceId);
            resource.setStatus("PROCESSING");
            resourceMapper.updateById(resource);
            
            // 事务提交后释放锁(在finally中)
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 实践5: 监控和告警
     */
    public void lockWithMonitoring(String lockKey) {
        long startTime = System.currentTimeMillis();
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            boolean acquired = lock.tryLock(5, 30, TimeUnit.SECONDS);
            
            if (!acquired) {
                // 记录锁获取失败
                log.warn("Failed to acquire lock: {}", lockKey);
                meterRegistry.counter("lock.acquire.failed", "lock", lockKey).increment();
                return;
            }
            
            // 记录锁获取成功
            meterRegistry.counter("lock.acquire.success", "lock", lockKey).increment();
            
            // 业务逻辑
            doBusinessLogic();
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                long holdTime = System.currentTimeMillis() - startTime;
                meterRegistry.timer("lock.hold.duration", "lock", lockKey)
                    .record(holdTime, TimeUnit.MILLISECONDS);
                lock.unlock();
            }
        }
    }
}

微服务架构中的注意事项:

1. 锁的粒度要合理
   ✅ 细粒度: 锁单个资源(如单个商品库存)
   ❌ 粗粒度: 锁整个服务(影响性能)

2. 避免跨服务锁
   ✅ 锁在服务内部使用
   ❌ 跨多个服务使用同一把锁(增加耦合)

3. 锁超时时间要合理
   ✅ 根据业务执行时间设置
   ❌ 设置过长(进程崩溃后锁很久才释放)

4. 监控锁的使用情况
   ✅ 记录锁获取成功率、持有时间
   ❌ 不监控(无法发现性能问题)

5. 异常处理要完善
   ✅ 确保锁在finally中释放
   ❌ 异常时锁未释放(导致死锁)

9.5 深入技术疑问

Q10: 分布式锁的CAP理论权衡是什么?如何选择?

疑问: 分布式锁在CAP理论中如何权衡一致性、可用性和分区容错性?

解答:

CAP理论在分布式锁中的应用:

┌─────────────────────────────────────────────────────────┐
│                   CAP理论                                │
├─────────────────────────────────────────────────────────┤
│  C (Consistency): 一致性                                 │
│    所有节点在同一时刻看到相同的数据                       │
│                                                          │
│  A (Availability): 可用性                                │
│    系统持续可用,每个请求都能得到响应                     │
│                                                          │
│  P (Partition Tolerance): 分区容错性                     │
│    系统在网络分区时仍能继续工作                         │
└─────────────────────────────────────────────────────────┘

分布式锁的CAP权衡:

Redis分布式锁:
  - 选择: AP(可用性 + 分区容错性)
  - 牺牲: C(一致性)
  - 特点: 高可用、高性能,但可能出现不一致

ZooKeeper分布式锁:
  - 选择: CP(一致性 + 分区容错性)
  - 牺牲: A(可用性)
  - 特点: 强一致性,但网络分区时可能不可用

实际场景选择:

/**
 * CAP理论在分布式锁选择中的应用
 */
public class CAPTradeOff {
    
    /**
     * 场景1: 电商库存扣减(选择AP)
     * 
     * 需求:
     *  - 高并发(10万+ QPS)
     *  - 可容忍短暂不一致(最终一致性)
     *  - 高可用(不能因为锁服务故障导致整个系统不可用)
     * 
     * 选择: Redis分布式锁(AP)
     */
    public void ecommerceInventory() {
        // 使用Redis分布式锁
        // 即使出现短暂不一致,也可以通过最终一致性保证正确性
    }
    
    /**
     * 场景2: 金融交易(选择CP)
     * 
     * 需求:
     *  - 强一致性(不能出现数据不一致)
     *  - 可容忍短暂不可用(一致性优先)
     *  - 并发量相对较低(1万+ QPS)
     * 
     * 选择: ZooKeeper分布式锁(CP)
     */
    public void financialTransaction() {
        // 使用ZooKeeper分布式锁
        // 即使网络分区导致不可用,也要保证一致性
    }
    
    /**
     * 场景3: 配置管理(选择CP)
     * 
     * 需求:
     *  - 配置必须一致(所有节点看到相同配置)
     *  - 可容忍短暂不可用
     * 
     * 选择: ZooKeeper分布式锁(CP)
     */
    public void configurationManagement() {
        // 使用ZooKeeper分布式锁
    }
    
    /**
     * 场景4: 缓存更新(选择AP)
     * 
     * 需求:
     *  - 高可用(缓存服务不能影响主业务)
     *  - 可容忍短暂不一致(缓存可以重建)
     * 
     * 选择: Redis分布式锁(AP)
     */
    public void cacheUpdate() {
        // 使用Redis分布式锁
    }
}

Q11: 分布式锁和消息队列的区别是什么?什么时候用哪个?

疑问: 分布式锁和消息队列都能保证顺序执行,它们有什么区别?

解答:

分布式锁 vs 消息队列:

┌─────────────────────────────────────────────────────────┐
│                   分布式锁                               │
├─────────────────────────────────────────────────────────┤
│  用途: 互斥访问(同一时刻只有一个进程能执行)            │
│  模式: 同步阻塞                                          │
│  场景: 防止重复操作、资源竞争                            │
│  示例: 防止重复订单、库存扣减                            │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                   消息队列                               │
├─────────────────────────────────────────────────────────┤
│  用途: 异步处理、解耦、削峰填谷                          │
│  模式: 异步非阻塞                                        │
│  场景: 任务队列、事件驱动、流量控制                      │
│  示例: 订单处理、邮件发送、日志收集                      │
└─────────────────────────────────────────────────────────┘

使用场景对比:

/**
 * 分布式锁 vs 消息队列使用场景
 */
public class LockVsQueue {
    
    /**
     * 场景1: 防止重复订单(使用分布式锁)
     * 
     * 需求: 同一用户不能同时创建多个订单
     * 特点: 需要立即返回结果,同步处理
     */
    public Order createOrder(CreateOrderRequest request) {
        String lockKey = "order:create:" + request.getUserId();
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            lock.lock();
            // 检查是否已存在订单
            Order existingOrder = orderMapper.selectByRequestId(request.getRequestId());
            if (existingOrder != null) {
                return existingOrder;
            }
            
            // 创建订单
            return createOrderInternal(request);
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 场景2: 订单处理(使用消息队列)
     * 
     * 需求: 订单创建后异步处理(发送邮件、更新库存等)
     * 特点: 不需要立即返回结果,异步处理
     */
    public void processOrder(Order order) {
        // 发送到消息队列
        kafkaTemplate.send("order-created", order);
        
        // 立即返回,不等待处理完成
    }
    
    /**
     * 场景3: 库存扣减(使用分布式锁)
     * 
     * 需求: 防止超卖,需要立即返回结果
     */
    public void deductInventory(String productId, int quantity) {
        String lockKey = "inventory:product:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            lock.lock();
            // 扣减库存,立即返回结果
            Inventory inventory = inventoryMapper.selectById(productId);
            if (inventory.getStock() >= quantity) {
                inventory.setStock(inventory.getStock() - quantity);
                inventoryMapper.updateById(inventory);
            }
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 场景4: 批量处理订单(使用消息队列)
     * 
     * 需求: 批量处理订单,削峰填谷
     */
    public void batchProcessOrders() {
        // 订单发送到消息队列
        List<Order> orders = getPendingOrders();
        for (Order order : orders) {
            kafkaTemplate.send("order-processing", order);
        }
        
        // 消费者批量处理
        // @KafkaListener(topics = "order-processing")
        // public void processOrder(Order order) { ... }
    }
}

选择原则:

使用分布式锁:
  ✅ 需要互斥访问(同一时刻只有一个进程能执行)
  ✅ 需要立即返回结果
  ✅ 同步处理
  ✅ 防止重复操作

使用消息队列:
  ✅ 需要异步处理
  ✅ 需要解耦
  ✅ 需要削峰填谷
  ✅ 不需要立即返回结果
  ✅ 批量处理

总结

常见疑问快速参考

Q1: 为什么需要分布式锁?
A1: 单机锁无法跨JVM,分布式环境下需要跨进程协调

Q2: 分布式锁和数据库事务的区别?
A2: 事务保证单个请求内的原子性,分布式锁保证跨请求的互斥

Q3: 为什么用Lua脚本?
A3: 保证原子性,防止误释放其他进程的锁

Q4: 为什么需要看门狗?
A4: 自动续期,避免业务执行时间超过锁过期时间

Q5: RedLock真的完美吗?
A5: 不完美,存在时钟同步、网络延迟等问题

Q6: Redis锁会成为瓶颈吗?
A6: 通常不会,但需要合理设计锁粒度

Q7: ZooKeeper为什么更可靠?
A7: 强一致性保证,但性能较低

Q8: 什么时候该用分布式锁?
A8: 有共享资源竞争时,但要评估性能影响

Q9: 微服务中如何设计?
A9: 合理命名、设置超时、监控告警、异常处理

Q10: CAP理论如何权衡?
A10: Redis选择AP,ZooKeeper选择CP

Q11: 锁和消息队列的区别?
A11: 锁用于互斥访问,队列用于异步处理

希望这些疑问和解答能帮助你更深入地理解分布式锁!

分布式锁选择建议

选择建议:

1. 高并发、低一致性要求 → Redis / Redisson
2. 高一致性要求 → ZooKeeper / etcd
3. 简单场景、低并发 → 数据库锁
4. 需要可重入、公平锁 → Redisson
5. 需要读写锁 → Redisson
6. 需要高可用 → RedLock / ZooKeeper集群

核心要点

✅ 必须设置锁过期时间(防止死锁)
✅ 释放锁时验证value(防止误释放)
✅ 使用Lua脚本保证原子性
✅ 合理设置锁超时时间
✅ 监控锁的使用情况
✅ 异常情况下确保锁释放

参考资源:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值