为什么需要分布式锁单机应用synchronized / ReentrantLock ← JVM 内锁 分布式应用多 JVM 实例synchronized 不够用← 需要分布式锁MySQL 分布式锁最朴素-- 用唯一索引实现分布式锁 CREATE TABLE distributed_lock ( lock_key VARCHAR(64) PRIMARY KEY, lock_value VARCHAR(64) NOT NULL, expire_time BIGINT NOT NULL ); -- 加锁插入 INSERT INTO distributed_lock (lock_key, lock_value, expire_time) VALUES (order_lock, uuid-123, UNIX_TIMESTAMP() 30); -- 释放锁删除 DELETE FROM distributed_lock WHERE lock_key order_lock AND lock_value uuid-123;Java 实现public boolean tryLock(String lockKey, long expireSeconds) { String value UUID.randomUUID().toString(); long expireTime Instant.now().getEpochSecond() expireSeconds; try { int rows jdbcTemplate.update( INSERT INTO distributed_lock (lock_key, lock_value, expire_time) VALUES (?, ?, ?), lockKey, value, expireTime); return rows 1; } catch (DuplicateKeyException e) { return false; // 锁已被其他线程持有 } } public boolean unlock(String lockKey, String value) { // ⚠️ 关键必须验证 value防止误删别人的锁 int rows jdbcTemplate.update( DELETE FROM distributed_lock WHERE lock_key ? AND lock_value ?, lockKey, value); return rows 1; }优缺点✅ 简单易用有数据库就能用❌性能差MySQL 写并发 1k TPS❌ 锁等待靠轮询⚠️小流量场景可以用Redis 分布式锁1 SETNX EX最基础// 加锁 public boolean tryLock(String lockKey, long expireSeconds) { String result redisTemplate.opsForValue().set(lockKey, locked, expireSeconds, TimeUnit.SECONDS); return OK.equals(result); } // 释放锁 public void unlock(String lockKey) { redisTemplate.delete(lockKey); }⚠️ 3 大问题1.死锁设置锁后宕机锁永远不释放→ 用 EX 过期时间解决2.误删锁A 加锁 → A 业务慢 → 锁过期 → B 加锁 → A 释放锁删了 B 的锁→用 UUID 解决3.不可重入同一个线程不能再次获得锁 →用 ThreadLocal 计数器解决2 SETNX UUID Luapublic class RedisDistributedLock { // 加锁 Lua 脚本 private static final String LOCK_SCRIPT if redis.call(setnx, KEYS[1], ARGV[1]) 1 then redis.call(expire, KEYS[1], ARGV[2]) return 1 else return 0 end; // 释放锁 Lua 脚本⚠️ 必须验证 UUID防止误删 private static final String UNLOCK_SCRIPT if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; public boolean tryLock(String lockKey, long expireSeconds) { String uuid UUID.randomUUID().toString(); Long result redisTemplate.execute( new DefaultRedisScript(LOCK_SCRIPT, Long.class), Arrays.asList(lockKey), uuid, String.valueOf(expireSeconds)); return result ! null result 1; } public boolean unlock(String lockKey, String uuid) { Long result redisTemplate.execute( new DefaultRedisScript(UNLOCK_SCRIPT, Long.class), Arrays.asList(lockKey), uuid); return result ! null result 1; } }实战代码// DLQConsumer.java RabbitListener(queues dlq.queue) public void processDLQ(ReportMessage message, Channel channel) throws IOException { String lockKey dlq:lock: message.getMessageId(); String uuid UUID.randomUUID().toString(); try { // 1. Redis 分布式锁30 秒过期 if (!redisLock.tryLock(lockKey, uuid, 30)) { log.info(任务已被其他 Consumer 锁定跳过: {}, message.getMessageId()); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); return; } // 2. 业务处理 reportService.process(message); // 3. ACK channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { log.error(处理失败, e); channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); } finally { // 4. 释放锁验证 UUID redisLock.unlock(lockKey, uuid); } }优缺点✅性能高Redis 写 10w TPS✅ 实现简单❌不保证强一致Redis 主从切换可能丢锁❌ 需要自己实现可重入Redisson 分布式锁// 1. 引入 Redisson dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.23.4/version /dependency // 2. 配置 Redisson Configuration public class RedissonConfig { Bean public RedissonClient redissonClient() { Config config new Config(); config.useSingleServer() .setAddress(redis://127.0.0.1:6379) .setPassword(mova_redis_pwd); return Redisson.create(config); } } // 3. 使用分布式锁 Service public class ReportTaskService { Autowired private RedissonClient redissonClient; public void processReport(ReportTask task) { // ⚠️ 关键可重入锁基于 Redis Hash 实现 RLock lock redissonClient.getLock(report:lock: task.getId()); try { // 1. 尝试加锁10 秒过期watchdog 自动续期 if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { try { // 业务处理 reportService.process(task); } finally { // 2. 释放锁 lock.unlock(); } } else { log.info(获取锁失败跳过任务: {}, task.getId()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }Redisson 7 大优势1.可重入基于 Redis Hash同一线程可多次加锁2.看门狗自动续期默认 30 秒续期一次业务卡住锁不会过期3.公平锁按请求顺序排队4.联锁MultiLock同时加多个锁转账场景 A 减 B 加5.红锁RedLock5 个独立 Redis 节点强一致6.读写锁RReadWriteLock7.信号量RSemaphore限流场景项目用Redisson 分布式锁 看门狗自动续期机制30 秒自动续期业务卡住锁不会过期。4 个 JVM 实例同时抢锁只让 1 个实例处理任务任务零重复。Redisson 看门狗机制┌────────────────────────────────────────────┐ │ Redisson 看门狗机制 │ ├────────────────────────────────────────────┤ │ 1. 加锁时设置 lockTime 30 秒 │ │ ↓ │ │ 2. 后台线程每 10 秒检查锁是否还在 │ │ ↓ │ │ 3. 如果还在自动续期 30 秒 │ │ ↓ │ │ 4. 业务执行完主动 unlock │ │ ↓ │ │ 5. 如果宕机30 秒后锁自动过期 │ └────────────────────────────────────────────┘Redisson 看门狗机制业务执行时锁自动续期30 秒一次避免业务执行时间超过锁过期时间导致锁失效。Redisson 联锁转账场景// 转账场景A 账户 -100B 账户 100必须同时成功 public void transfer(String fromAccount, String toAccount, BigDecimal amount) { RLock lockA redissonClient.getLock(lock:account: fromAccount); RLock lockB redissonClient.getLock(lock:account: toAccount); // ⚠️ 联锁要么都加成功要么都失败 RedissonMultiLock multiLock new RedissonMultiLock(lockA, lockB); try { if (multiLock.tryLock(10, 30, TimeUnit.SECONDS)) { try { // A 减 100 accountMapper.debit(fromAccount, amount); // B 加 100 accountMapper.credit(toAccount, amount); } finally { multiLock.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }Redisson 读写锁// mpvs 客户数据1 个写多个读 public Order queryOrder(Long orderId) { RReadWriteLock rwLock redissonClient.getReadWriteLock(order:lock: orderId); // 读锁多个线程可同时读 RLock readLock rwLock.readLock(); readLock.lock(); try { return orderMapper.selectById(orderId); } finally { readLock.unlock(); } } public void updateOrder(Order order) { RReadWriteLock rwLock redissonClient.getReadWriteLock(order:lock: orderId); // 写锁独占 RLock writeLock rwLock.writeLock(); writeLock.lock(); try { orderMapper.updateById(order); } finally { writeLock.unlock(); } }性能提升读多写少场景用读写锁性能提升 5-10 倍。ZooKeeper 分布式锁// ZK 分布式锁基于临时顺序节点 public class ZKDistributedLock { private ZooKeeper zk; private String lockPath /locks; public void acquire(String lockKey) throws Exception { // 1. 创建临时顺序节点 String nodePath zk.create(lockPath / lockKey _, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // 2. 获取所有子节点 ListString children zk.getChildren(lockPath, false); Collections.sort(children); // 排序 // 3. 判断自己是否最小节点 if (nodePath.equals(lockPath / children.get(0))) { // 获取锁 } else { // 监听前一个节点 String prevNode children.get(Collections.binarySearch(children, nodePath.substring(lockPath.length() 1)) - 1); zk.exists(lockPath / prevNode, new Watcher() { Override public void process(WatchedEvent event) { synchronized (this) { notifyAll(); // 唤醒等待 } } }); wait(); // 等待前一个节点释放 } } }优缺点✅强一致ZK 集群半数以上节点写成功才返回✅ 公平锁按请求顺序❌性能差ZK 集群写性能 1k TPS❌ 部署复杂需要 ZK 集群⚠️老项目用过❌新项目不推荐用 Redisson 更简单RedLock 红锁强一致场景// RedLock5 个独立的 Redis 节点部署在不同机器 public class RedLock { private ListRedissonClient redissonClients; // 5 个独立的 Redis 节点 public boolean tryLock(String lockKey, long expireSeconds) { // 同时向 5 个节点请求加锁 ListRLock locks redissonClients.stream() .map(client - client.getLock(lockKey)) .collect(Collectors.toList()); RedissonRedLock redLock new RedissonRedLock(locks.toArray(new RLock[0])); try { // ⚠️ 关键5 个节点中 N/2 1 个加锁成功才算成功 return redLock.tryLock(100, expireSeconds * 1000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } }5 节点架构┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ Redis1 │ │ Redis2 │ │ Redis3 │ │ Redis4 │ │ Redis5 │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ ↑ ↑ ↑ ↑ ↑ └─────────┴─────────┴─────────┴─────────┘ RedissonRedLock 5 个节点中 3 个加锁成功才算成功优缺点✅强一致5 节点多数派2 个节点同时挂掉也能用❌成本高要部署 5 个独立 Redis❌性能差要等 5 个节点响应⚠️金融项目强一致场景用央行清算、跨行转账❌一般业务用不上Redisson 单 Redis 够用三、5 种分布式锁对比维度MySQLRedis SETNXRedissonZooKeeperRedLock性能1k TPS10w TPS10w TPS1k TPS5w TPS可靠性中中主从切换可能丢锁高看门狗高最高强一致✅❌❌✅✅可重入需自己实现❌✅❌✅公平锁❌❌✅✅✅复杂度低低中高极高部署成本0用现有 DB0用现有 Redis低高要 ZK 集群极高5 Redis推荐度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐