Redisson——浅析分布式锁之解锁

434 阅读2分钟

Redisson——浅析分布式锁之解锁

1. 解锁方法unlock()源码分析

@Override
public void unlock() {
    try {
        get(unlockAsync(Thread.currentThread().getId()));
    } catch (RedisException e) {
        if (e.getCause() instanceof IllegalMonitorStateException) {
            throw (IllegalMonitorStateException) e.getCause();
        } else {
            throw e;
        }
    }
}

@Override
public RFuture<Void> unlockAsync(long threadId) {
    RPromise<Void> result = new RedissonPromise<Void>();
    // 删除redis中的锁,并发送解锁消息
    RFuture<Boolean> future = unlockInnerAsync(threadId);

    future.onComplete((opStatus, e) -> {
        // 取消watchdog
        cancelExpirationRenewal(threadId);

        if (e != null) {
            result.tryFailure(e);
            return;
        }

        if (opStatus == null) {
            IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                                                                                  + id + " thread-id: " + threadId);
            result.tryFailure(cause);
            return;
        }

        result.trySuccess(null);
    });

    return result;
}

2. 解锁主流程

  • 删除redis中的key,并且发布解锁消息
  • 加锁的时候定义了解锁监听器(在上一篇加锁文章中讲述),收到解锁消息后执行解锁监听器任务
  • 取消watchdog

整个redisson加解锁流程:

分布式锁解锁主流程.png

3. redis解锁unlockInnerAsync()

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                          "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +   // Hash key下是否有线程ID属性,不存在则返回null
                          "return nil;" +
                          "end; " +
                          "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +  // 存在则属性值-1后获取属性值,如果属性值大于0重置锁的过期时间,返回0
                          "if (counter > 0) then " +
                          "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                          "return 0; " +
                          "else " +
                          "redis.call('del', KEYS[1]); " +                            // 否则删除锁,发布解锁消息,返回1
                          "redis.call('publish', KEYS[2], ARGV[1]); " +
                          "return 1; " +
                          "end; " +
                          "return nil;",
                          Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}

这段lua脚本的逻辑是:

  1. 锁名称下是否有属性等于线程ID,不存在返回null
  2. 存在的话,属性值减1,考虑重入锁,返回属性值counter
  3. 如果counter>0,则重置锁过期时间,返回0
  4. 如果counter=0,则删除锁,并发布解锁消息,返回1

4. 取消watchdog任务cancelExpirationRenewal()

void cancelExpirationRenewal(Long threadId) {
    ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (task == null) {
        return;
    }

    if (threadId != null) {
        task.removeThreadId(threadId);
    }

    if (threadId == null || task.hasNoThreads()) {
        Timeout timeout = task.getTimeout();
        if (timeout != null) {
            // 取消任务
            timeout.cancel();
        }
        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
    }
}


世界那么大,感谢遇见,未来可期...

欢迎同频共振的那一部分人

作者公众号:Tarzan写bug

公众号二维码.jpg