Redisson实现分布式锁原理及源码分析(待完善)

Redisson实现分布式锁

原理图:
Redisson实现分布式锁原理及源码分析(待完善)_第1张图片

代码示例

public String order(){
    String lockKey = "product:001";
    // 获取锁对象
    RLock lock = redisson.getLock(lockKey);
    try {
        // 加分布式锁
        lock.lock();
        // 执行业务逻辑
        System.out.println("抢单逻辑。。。");
    } finally {
        // 释放锁
        lock.unlock();
    }
    return "success";
}

源码分析

获取锁对象

获取锁对象,其实就是获取一个RedissonLock对象。

RLock lock = redisson.getLock(lockKey);

Redisson继承了RedissonExpirable抽象类,实现了RLock接口,如下图:
Redisson实现分布式锁原理及源码分析(待完善)_第2张图片

Redisson构造方法:

public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
    // 对应的RedissonExpirable构造方法
    super(commandExecutor, name);
    // commandExecutor为通过redis连接配置生成的命令执行器
    this.commandExecutor = commandExecutor;
    // id为通过UUID.randomUUID()生成的uuid
    this.id = commandExecutor.getConnectionManager().getId();
    // internalLockLeaseTime为redis连接配置文件配置的看门狗超时时间,默认30秒
    this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
}

加锁

开始竞争锁。

lock.lock();
// 上级方法会捕获中断异常,从而中断当前线程
// 传参为:this.lockInterruptibly(-1L, (TimeUnit)null);
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
    // 通过lua脚本原子地设置锁名称到redis,并设置过期时间(默认30秒)
    // 是一个hash结构,key为锁名称,value为uuid+当前线程id
    // ttl为剩余过期时间毫秒数
    Long ttl = this.tryAcquire(leaseTime, unit, threadId);
    if (ttl != null) {
        // ttl不为空,表示当前所已被占用
        RFuture<RedissonLockEntry> future = this.subscribe(threadId);
        this.commandExecutor.syncSubscription(future);

        try {
            while(true) {
                ttl = this.tryAcquire(leaseTime, unit, threadId);
                if (ttl == null) {
                    return;
                }

                if (ttl >= 0L) {
                    this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    this.getEntry(threadId).getLatch().acquire();
                }
            }
        } finally {
            this.unsubscribe(future, threadId);
        }
    }
}

加锁的lua脚本分析:

if (redis.call('exists', KEYS[1]) == 0) 
  then 
  // 锁名不存在,设置到redis,并设置过期时间(默认30秒)
  redis.call('hset', 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 
  // 锁存在,则对应的field加1,并重置过期时间
  redis.call('hincrby', KEYS[1], ARGV[2], 1); 
  redis.call('pexpire', KEYS[1], ARGV[1]); 
  return nil; 
end; 
// 获取key剩余时间毫秒数
return redis.call('pttl', KEYS[1]);

解锁

主动解锁。

lock.unlock();
public void unlock() {
    // 解锁
    Boolean opStatus = (Boolean)this.get(this.unlockInnerAsync(Thread.currentThread().getId()));
    if (opStatus == null) {
        throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + Thread.currentThread().getId());
    } else {
        if (opStatus) {
            this.cancelExpirationRenewal();
        }

    }
}

lua脚本:

if (redis.call('exists', KEYS[1]) == 0) 
  // 锁不存在,发布消息
  then redis.call('publish', KEYS[2], ARGV[1]); 
  return 1; 
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) 
  // 锁已释放
  then 
  return nil;
end; 
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if (counter > 0) 
  then redis.call('pexpire', KEYS[1], ARGV[2]); 
  return 0; 
else redis.call('del', KEYS[1]); 
  // 释放锁
  redis.call('publish', KEYS[2], ARGV[1]); 
  return 1; 
end; 
return nil;

lua脚本

redis中使用lua脚本格式:

eval script numkeys key [key ...] arg [arg ...]

命令解释:

script:具体的lua命令。
numkeys:参数用于指定键名参数的个数。
key [key …]:从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] ,KEYS[2] ,以此类推)。
arg [arg …]:附加参数,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、ARGV[2] ,诸如此类)。

lua示例:

> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

其中:这是一段有返回值的lua脚本。数字2指明了键名参数的数量,key1和key2是键名参数,分别使用KEYS[1]和KEYS[2]访问,first和second是附加参数,通过ARGV[1]和ARGV[2]访问。

在lua脚本中执行redis命令:

redis.call();
redis.pcall();

示例:

> eval "return redis.call('set','foo','bar')" 0
OK
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
> get foo
bar

你可能感兴趣的:(#,Redis,redis,redisson,分布式锁,原理,源码)