Redisson 分布式锁全面解析:锁类型(可重入锁、公平锁、联锁、红锁、读写锁)和锁常见方法解读

发布于:2025-03-17 ⋅ 阅读:(14) ⋅ 点赞:(0)

Redisson 分布式锁全面解析

一、Redisson 分布式锁原理

Redisson 分布式锁基于 Redis 实现,核心机制如下:

  1. Lua 脚本保证原子性
    使用 Lua 脚本在 Redis 中执行锁的获取和释放操作,确保多个 Redis 命令的原子性。

  2. 可重入锁设计
    同一线程可重复获取锁,通过计数器记录重入次数,避免死锁。

  3. 看门狗(Watchdog)自动续期
    后台线程定期检查锁状态,若锁仍被持有且未完成业务逻辑,则自动延长锁的过期时间(默认续期到 30 秒)。

  4. 锁释放与超时机制
    锁设置超时时间(默认 30 秒),即使客户端崩溃,锁也会自动释放,防止死锁。


二、Redisson 分布式锁的实现方式
1. 引入依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.17.0</version>
</dependency>
2. 配置 Redisson 客户端
Config config = new Config();
config.useSingleServer()
    .setAddress("redis://127.0.0.1:6379")
    .setPassword("password");
RedissonClient redisson = Redisson.create(config);
3. 获取锁实例
RLock lock = redisson.getLock("myLock");

三、Redisson 分布式锁的 Java 方法及作用
方法 作用 使用场景
void lock() 阻塞式获取锁,直到成功或线程中断。 必须获取锁的场景(如关键资源互斥访问)
void lock(long leaseTime, TimeUnit unit) 获取锁并指定锁自动释放时间(不触发看门狗续期)。 明确锁持有时间的场景
boolean tryLock() 非阻塞尝试获取锁,成功返回 true,否则立即返回 false 快速失败逻辑
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) waitTime 内尝试获取锁,成功返回 true,超时返回 false,并指定锁自动释放时间。 允许等待但限制时间的场景
void unlock() 释放锁。 必须放在 finally 块中释放锁
boolean isLocked() 判断锁是否被任何线程持有。 监控锁状态
boolean isHeldByCurrentThread() 判断当前线程是否持有锁。 防止其他线程误释放锁

四、不同锁类型及适用场景
锁类型 特点 适用场景
可重入锁(Reentrant Lock) 同一线程可多次获取锁,通过计数器实现。 递归调用或需要重复获取锁的业务逻辑
公平锁(Fair Lock) 按请求顺序分配锁,避免线程饥饿。 需要公平性的场景(如资源按顺序分配)
联锁(MultiLock) 同时获取多个锁,所有锁都成功才算成功。 需要同时锁定多个资源的场景(如分布式事务)
红锁(RedLock) 在多个独立的 Redis 节点上获取锁,多数成功才算成功。 高可用性要求严格的场景(如金融交易)
读写锁(ReadWriteLock) 读锁共享,写锁互斥。 读多写少的场景(如缓存数据更新)

五、防止死锁与超时的策略
  1. 自动续期(看门狗机制)

    • 默认情况下,若未显式指定 leaseTime,Redisson 会启动看门狗线程,每 10 秒续期锁至 30 秒。
    • 代码示例
      lock.lock(); // 自动续期
      
  2. 设置锁超时时间

    • 显式指定 leaseTime,锁在超时后自动释放,避免死锁。
    • 代码示例
      lock.lock(10, TimeUnit.SECONDS); // 10秒后自动释放
      
  3. 使用 tryLock 控制等待时间

    • 避免线程长时间阻塞,设置最大等待时间。
    • 代码示例
      if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { // 等待3秒,锁持有10秒
          // 业务逻辑
      }
      
  4. 强制释放锁

    • finally 块中释放锁,确保异常情况下锁能被释放。
    • 代码示例
      try {
          lock.lock();
          // 业务逻辑
      } finally {
          if (lock.isHeldByCurrentThread()) {
              lock.unlock();
          }
      }
      
  5. 红锁(RedLock)防脑裂

    • 在多个独立 Redis 节点上获取锁,半数以上成功才算有效。
    • 代码示例
      RLock lock1 = redisson1.getLock("lock");
      RLock lock2 = redisson2.getLock("lock");
      RLock lock3 = redisson3.getLock("lock");
      RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
      redLock.lock();
      

六、不同场景下的锁选择建议及代码示例

1. 简单互斥场景
  • 场景描述:秒杀扣减库存,确保同一商品不会被超卖。
  • 锁类型:可重入锁(RLock)。
  • 方法lock() + unlock()
  • 代码示例
    RLock lock = redisson.getLock("product_lock_" + productId);
    try {
        // 阻塞式获取锁,直到成功
        lock.lock();
        // 执行库存扣减逻辑
        int stock = productService.getStock(productId);
        if (stock > 0) {
            productService.reduceStock(productId);
        }
    } finally {
        // 确保只有持有锁的线程才能释放
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
    

2. 高并发读场景
  • 场景描述:缓存数据读取,允许多线程并发读,但写时互斥。
  • 锁类型:读写锁(RReadWriteLock)。
  • 方法readLock().lock() + readLock().unlock()
  • 代码示例
    RReadWriteLock rwLock = redisson.getReadWriteLock("cache_lock");
    RLock readLock = rwLock.readLock();
    try {
        readLock.lock();
        // 从缓存读取数据(允许多线程并发读)
        String data = cacheService.get("key");
        if (data == null) {
            // 若缓存未命中,获取写锁更新
            RLock writeLock = rwLock.writeLock();
            try {
                writeLock.lock();
                data = dbService.loadFromDB("key");
                cacheService.put("key", data);
            } finally {
                writeLock.unlock();
            }
        }
        return data;
    } finally {
        readLock.unlock();
    }
    

3. 多资源原子操作
  • 场景描述:转账操作需同时锁定转出和转入账户。
  • 锁类型:联锁(RedissonMultiLock)。
  • 方法:同时锁定多个资源。
  • 代码示例
    // 获取两个账户的锁
    RLock lockA = redisson.getLock("account_lock_" + fromAccountId);
    RLock lockB = redisson.getLock("account_lock_" + toAccountId);
    RedissonMultiLock multiLock = new RedissonMultiLock(lockA, lockB);
    try {
        // 同时锁定两个账户的锁
        multiLock.lock();
        // 执行转账逻辑
        accountService.transfer(fromAccountId, toAccountId, amount);
    } finally {
        multiLock.unlock();
    }
    

4. 高可用性要求
  • 场景描述:支付系统订单处理,需严格防止锁失效导致重复支付。

  • 锁类型:红锁(RedissonRedLock)。

  • 方法:在多个独立 Redis 节点上获取锁。

  • 代码示例

    // 连接三个独立的 Redis 实例
    RedissonClient redisson1 = createClient("redis://node1:6379");
    RedissonClient redisson2 = createClient("redis://node2:6379");
    RedissonClient redisson3 = createClient("redis://node3:6379");
    
    RLock lock1 = redisson1.getLock("order_lock_" + orderId);
    RLock lock2 = redisson2.getLock("order_lock_" + orderId);
    RLock lock3 = redisson3.getLock("order_lock_" + orderId);
    
    RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
    try {
        // 在多数节点上获取锁
        redLock.lock();
        // 处理支付订单
        paymentService.process(orderId);
    } finally {
        redLock.unlock();
    }
    
    5. 严格顺序处理要求
  • 场景描述:以 订单支付系统 为例,要求严格按照用户提交订单的时间顺序处理支付请求。

  • 锁类型:公平锁(RedissonFairLock)。

  • 方法:按照线程请求锁的顺序分配锁,确保先到先得,避免线程饥饿。

  • 代码示例

    • 获取公平锁实例

        // 获取公平锁实例
        RLock fairLock = redisson.getFairLock("order_payment_lock");
      
    • 使用公平锁处理订单

      public class OrderService {
         private final RLock fairLock;
      
         public OrderService(RedissonClient redisson) {
             this.fairLock = redisson.getFairLock("order_payment_lock");
         }
      
         public void processPayment(String orderId) {
             try {
                 // 获取公平锁(按请求顺序排队)
                 fairLock.lock();
      
                 // 模拟支付处理(关键业务逻辑)
                 System.out.println("处理订单: " + orderId + " | 线程: " + Thread.currentThread().getName());
                 Thread.sleep(500); // 模拟业务耗时
      
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
             } finally {
                 if (fairLock.isHeldByCurrentThread()) {
                     fairLock.unlock();
                 }
             }
         }
      }
      
    • 模拟多线程请求

      public static void main(String[] args) {
         RedissonClient redisson = Redisson.create();
         OrderService orderService = new OrderService(redisson);
       
         // 模拟10个订单按顺序提交
         for (int i = 1; i <= 10; i++) {
             String orderId = "ORDER-" + i;
             new Thread(() -> orderService.processPayment(orderId)).start();
       
             // 控制线程启动间隔,模拟请求顺序到达
             try {
                 Thread.sleep(10);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
      }
      
    • 输出结果

      处理订单: ORDER-1 | 线程: Thread-0
      处理订单: ORDER-2 | 线程: Thread-1
      处理订单: ORDER-3 | 线程: Thread-2
      处理订单: ORDER-4 | 线程: Thread-3
      ...
      处理订单: ORDER-10 | 线程: Thread-9
      
    • 观察结果:订单严格按照 ORDER-1 到 ORDER-10 的顺序处理,线程按启动顺序获取锁

代码示例关键点说明
  1. 锁的命名
    • 锁的 Key 需具备唯一性和业务语义(如 product_lock_1001)。
  2. 锁释放的可靠性
    • 必须在 finally 块中释放锁,并通过 isHeldByCurrentThread() 防止误释放。
  3. 读写锁的升级
    • 读锁升级为写锁时,需先释放读锁再获取写锁(Redisson 自动处理)。
  4. 红锁的节点独立性
    • 红锁要求 Redis 节点互相独立(非主从或集群模式)。
总结

通过具体场景的代码示例,可以看出 Redisson 分布式锁的灵活性和强大功能:

  1. 简单互斥:直接使用 RLock 实现原子操作。
  2. 读写分离:通过 RReadWriteLock 提升读性能。
  3. 多资源协同:利用 RedissonMultiLock 保证多资源操作的原子性。
  4. 高可用场景:通过 RedissonRedLock 防止 Redis 单点故障。

每个场景的代码均遵循以下原则:

  • 最小化锁范围:仅锁定必要资源。
  • 异常处理:确保锁最终释放。
  • 可维护性:锁 Key 具备业务含义,便于监控和调试。

七、异常处理最佳实践
  1. 锁释放失败

    • 原因:未在 finally 块释放锁或线程中断。
    • 解决方案:确保 unlock()finally 中执行,并检查当前线程是否持有锁。
  2. 锁超时与业务执行时间冲突

    • 原因:业务逻辑执行时间超过锁超时时间。
    • 解决方案:调整锁超时时间或优化业务逻辑。
  3. 网络分区与 Redis 故障

    • 原因:Redis 节点不可用导致锁状态不一致。
    • 解决方案:使用红锁(RedLock)或集群模式。

总结

Redisson 提供了丰富的分布式锁实现,核心在于 原子性操作可重入设计自动续期机制。选择锁类型时需根据场景权衡性能、可用性和复杂度。关键注意事项包括:

  1. 始终在 finally 块中释放锁
  2. 合理设置锁超时时间,避免业务未完成锁已过期。
  3. 高可用场景使用红锁,防止 Redis 单点故障。
  4. 读写分离场景使用读写锁,提升并发性能。

通过合理使用 Redisson 分布式锁,可有效解决分布式系统中的资源竞争问题,保障数据一致性。


网站公告

今日签到

点亮在社区的每一天
去签到