Java 使用Redisson实现分布式锁,为什么叫分布式,分布式是如何实现的?


Java 使用Redisson实现分布式锁,为什么叫分布式,分布式是如何实现的?

我们使用Redisson实现分布式锁,之所以称为“分布式”锁,是因为它在分布式环境下协调多个节点(多个JVM或多个服务器)对共享资源的访问。分布式锁的目的是在分布式系统中提供一种互斥机制,确保在同一时间只有一个节点(中的一个线程)可以访问共享资源。

Redisson分布式锁的实现依赖于Redis,因为Redis本身是一个分布式缓存数据库,可以被多个节点访问,从而作为分布式锁的存储层。Redisson通过Redis的原子操作和特性来实现分布式锁。

下面详细解释Redisson分布式锁的实现原理:

  1. 锁的获取 当多个客户端(即分布式系统中的多个节点)尝试获取同一个锁时,Redisson会使用Redis的SET命令(实际上使用Lua脚本保证原子性)来设置一个键(key),这个键代表锁。如果键不存在,则设置成功,表示获取锁;如果键已经存在,则获取失败。

Redisson在设置键的时候会设置一个过期时间(leaseTime),这是为了防止客户端崩溃导致锁无法释放。如果设置了过期时间,即使客户端崩溃,锁也会在过期后自动释放,避免死锁。

  1. 锁的释放 当客户端完成业务逻辑后,需要释放锁。释放锁就是删除Redis中对应的键。但是,为了避免误删其他客户端的锁,Redisson在设置锁的时候会生成一个唯一标识(通常使用UUID和线程ID),作为键的值。在删除键之前,会检查值是否匹配,只有匹配才删除。

  2. 可重入性 Redisson的锁支持可重入,即同一个客户端上的同一个线程可以多次获取同一把锁。Redisson使用Redis的Hash结构来存储锁,其中键是锁的名称,字段是客户端唯一标识(UUID和线程ID),值是重入次数。当线程第一次获取锁时,重入次数设置为1;如果同一个线程再次获取锁,重入次数加1;释放锁时,重入次数减1,当重入次数为0时,删除锁。

  3. 锁的续期 如果业务逻辑执行时间较长,超过了锁的过期时间,那么锁会自动释放,可能导致其他客户端获取锁,造成问题。Redisson提供了锁续期机制(看门狗机制),在获取锁成功后,会启动一个定时任务,定期(默认每10秒)检查锁是否还存在,如果存在则重置过期时间(默认续期到30秒)。这样,只要客户端还在执行业务逻辑,并且没有主动释放锁,锁就不会因为过期而释放。

  4. 等待锁的机制 当锁被其他客户端持有时,当前客户端会尝试等待获取锁。Redisson使用了Redis的发布订阅功能来通知等待的客户端锁已被释放,从而避免频繁的轮询。当锁被释放时,等待的客户端会收到通知,然后再次尝试获取锁。

  5. 公平锁 Redisson还提供了公平锁的实现。公平锁按照客户端请求锁的顺序来分配锁,即先到先得。公平锁的实现使用了Redis的列表(List)结构来维护等待队列。

  6. 红锁(RedLock) 为了进一步提高分布式锁的可靠性,Redisson支持红锁算法。红锁算法需要在多个独立的Redis节点上获取锁,当在大多数节点上获取锁成功时,才认为获取锁成功。这样可以避免单个Redis节点故障导致的问题。

总结 Redisson分布式锁之所以称为“分布式”,是因为它利用Redis作为分布式协调中间件,在多个独立的客户端(可能运行在不同的JVM或不同的机器上)之间进行锁的协调。通过Redis的原子操作和数据结构,实现了互斥、可重入、锁续期、公平锁等特性,从而在分布式系统中提供可靠的锁服务。

需要注意的是,Redis本身是单线程的,所以它可以保证单个命令的原子性。而Redisson使用Lua脚本将多个命令组合成一个原子操作,从而避免了竞态条件。

然而,分布式锁的实现还需要考虑网络延迟、分区等问题。在某些极端情况下,比如发生网络分区时,可能会出现多个客户端同时持有锁的情况。因此,在使用分布式锁时,需要根据业务场景权衡一致性和可用性。

这个问题触及了分布式锁的核心概念。以下详细解释"分布式"的含义以及Redisson的具体实现机制。

一、为什么叫"分布式"锁?

1. 分布式系统背景

在分布式系统中,同一个应用会部署在多个节点(服务器)上:

[服务实例A]  [服务实例B]  [服务实例C]
     |           |           |
     +-----------+-----------+
               |
          [共享资源/服务]

2. 传统锁的局限性

传统的Java锁(如synchronizedReentrantLock)只能保证单个JVM进程内的线程安全:

  • synchronized:JVM级别锁,不同服务器之间无法协调
  • ReentrantLock:同样是进程内锁

3. 分布式锁的定义

分布式锁是在多个独立的进程/服务器之间实现互斥访问的协调机制。它的核心特征是:

  • 跨JVM:在多个独立的Java进程间生效
  • 跨服务器:在不同的物理机或虚拟机间协调
  • 共享状态:所有节点都能访问同一个权威的锁状态存储

二、Redisson分布式锁的具体实现机制

1. 核心架构:基于Redis的共享存储

[应用节点A]      [应用节点B]      [应用节点C]
     |               |               |
     +---------------+---------------+
                     |
              [Redis集群/单机]
              (共享状态存储)

2. 关键实现技术

(1)Redis数据结构设计

Redisson使用Redis的Hash结构存储锁信息:

# 锁的Key
"my_distributed_lock"

# Hash字段和值
Field: "b983c153-7421-4a1c-9f3a-7b68a1d5e6a9:1"  # 客户端ID:线程ID
Value: 1  # 重入次数

(2)原子操作:Lua脚本

这是实现分布式的核心技术!所有锁操作都是原子性的:

加锁Lua脚本:

-- KEYS[1] = 锁key, ARGV[1] = 过期时间, ARGV[2] = 客户端标识
if (redis.call('exists', KEYS[1]) == 0) then 
    -- 锁不存在,创建锁
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
end; 

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
    -- 重入锁
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
end; 

-- 返回锁的剩余生存时间
return redis.call('pttl', KEYS[1]);

(3)客户端唯一标识

每个Redisson客户端都有唯一标识,确保锁的归属明确:

// Redisson内部生成客户端ID
public class RedissonLock {
    protected String getLockName(long threadId) {
        return id + ":" + threadId;  // UUID:线程ID
        // 示例: "b983c153-7421-4a1c-9f3a-7b68a1d5e6a9:1"
    }
}

3. 分布式协调流程

加锁场景:

时序图:
节点A           Redis           节点B
 |              |                |
 | 尝试加锁      |                |
 | ---------->  |                |
 |              | 创建锁成功     |
 |              | <------------  | 尝试加锁
 |              | 返回锁已存在   |
 |              | ------------>  |
 | 执行业务      |                | 等待/重试
 | ---------->  |                |
 | 释放锁       |                |
 | ---------->  |                |
 |              | 通知节点B      |
 |              | ------------>  | 获取锁成功

具体代码演示分布式效果:

// 节点A(服务器192.168.1.10)
@Service
public class ServiceNodeA {
    @Autowired private RedissonClient redissonClient;
    
    public void businessMethod() {
        RLock lock = redissonClient.getLock("distributed_lock");
        try {
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                System.out.println("节点A获取锁成功,时间: " + new Date());
                Thread.sleep(10000); // 模拟业务处理10秒
            }
        } finally {
            lock.unlock();
        }
    }
}

// 节点B(服务器192.168.1.11)  
@Service
public class ServiceNodeB {
    @Autowired private RedissonClient redissonClient;
    
    public void businessMethod() {
        RLock lock = redissonClient.getLock("distributed_lock"); // 同一个锁名!
        try {
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                System.out.println("节点B获取锁成功,时间: " + new Date());
                Thread.sleep(5000);
            }
        } finally {
            lock.unlock();
        }
    }
}

运行结果:

节点A获取锁成功,时间: Wed Nov 01 10:00:00 CST 2023
节点B获取锁成功,时间: Wed Nov 01 10:00:10 CST 2023  # 10秒后才获取成功

4. 高可用和容错机制

(1)锁续期(Watchdog机制)

// 防止业务执行时间超过锁过期时间
private void renewExpiration() {
    // 每10秒检查一次,如果锁还存在就续期到30秒
    Timeout task = timer.newTimeout(timeout -> {
        // 续期逻辑:如果还是当前客户端持有锁,就重置过期时间
        if (redis.eval("if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " +
                      "return redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "else return 0; end;", 
                      lockKey, internalLockLeaseTime, clientId)) {
            // 递归调用,继续续期
            renewExpiration();
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
}

(2)Redis集群支持

// Redisson支持多种Redis部署模式
Config config = new Config();

// 单机模式
config.useSingleServer().setAddress("redis://127.0.0.1:6379");

// 集群模式
config.useClusterServers()
      .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001");

// 哨兵模式  
config.useSentinelServers()
      .setMasterName("mymaster")
      .addSentinelAddress("redis://127.0.0.1:26379");

(3)红锁(RedLock)算法

对于更高要求的场景,可以使用多主Redis实例:

RedissonClient client1 = Redisson.create(config1);
RedissonClient client2 = Redisson.create(config2); 
RedissonClient client3 = Redisson.create(config3);

RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RLock lock3 = client3.getLock("lock");

// 红锁:需要大多数实例加锁成功
RLock redLock = redisson.getRedLock(lock1, lock2, lock3);

三、与传统锁的对比

特性传统锁(synchronized)分布式锁(Redisson)
作用范围单个JVM进程内跨JVM、跨服务器
状态存储JVM内存Redis等外部存储
网络依赖需要网络访问Redis
性能极高(内存操作)高(网络+Redis操作)
适用场景单机应用分布式系统、微服务

四、总结

Redisson实现分布式的核心在于:

  1. 共享存储:使用Redis作为所有节点的统一状态存储
  2. 原子操作:通过Lua脚本确保操作的原子性
  3. 唯一标识:每个客户端有唯一ID,明确锁归属
  4. 网络通信:基于网络协议实现跨进程协调
  5. 容错机制:支持锁续期、集群模式、红锁等

正是通过这些机制,Redisson能够在分布式的环境中提供可靠的互斥访问控制,这才是"分布式锁"的真正含义。

JAVA-技能点
知识点
Redis