Redisson加锁和解锁和WatchDog机制的原理

一、加锁

默认加锁方法:RLock#lock()

redisson通过lua脚本来保证加锁的原子性,用客户端对应的线程的唯一标识来保证加锁的用户不被抢占,用过期时间和WatchDog机制(可选)保证不死锁。

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    long threadId = Thread.currentThread().getId();
    // 尝试加锁,并返回锁的剩余时间
    Long ttl = tryAcquire(leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
        // 返回null,表示加锁成功
        return;
    }

    // 加锁失败,订阅redisson_lock__channel:{锁名称}频道来获取锁的释放消息
    // protected RFuture<RedissonLockEntry> subscribe(long threadId) {
    //    return pubSub.subscribe(getEntryName(), getChannelName());
    // }
    RFuture<RedissonLockEntry> future = subscribe(threadId);
    commandExecutor.syncSubscription(future);

    try {
        // 收到锁的释放消息,自旋获取锁
        while (true) {
            ttl = tryAcquire(leaseTime, unit, threadId);
            // lock acquired
            if (ttl == null) {
                break;
            }

            // waiting for message
            if (ttl >= 0) {
                try {
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    if (interruptibly) {
                        throw e;
                    }
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                }
            } else {
                if (interruptibly) {
                    getEntry(threadId).getLatch().acquire();
                } else {
                    getEntry(threadId).getLatch().acquireUninterruptibly();
                }
            }
        }
    } finally {
        // 无论是否获取锁成功,都取消订阅
        unsubscribe(future, threadId);
    }
}

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
    return get(tryAcquireAsync(leaseTime, unit, threadId));
}

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
    if (leaseTime != -1) {
    	// 如果客户端自定义过期时间,则不适用WatchDog机制
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    // WatchDog机制看下面分析
    RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        if (e != null) {
            return;
        }

        // lock acquired
        if (ttlRemaining == null) {
            scheduleExpirationRenewal(threadId);
        }
    });
    return ttlRemainingFuture;
}

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);

    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                                          "if (redis.call('exists', KEYS[1]) == 0) then " +
                                          "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 " +
                                          "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                          "return nil; " +
                                          "end; " +
                                          "return redis.call('pttl', KEYS[1]);",
                                          Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

加锁对应的lua脚本

加锁成功返回null,失败返回锁的剩余时间。

--[[ 
参数:
KEYS[1] 锁名称
ARGV[1] 锁的过期时间
ARGV[2] 对应锁的唯一标识
--]]

if (redis.call('exists', KEYS[1]) == 0) then
	-- 如果锁不存在,则设置锁的重入次数为1和过期时间,返回null
    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 
	-- 如果锁存在,且客户端的唯一标识与当前锁的唯一标识相同,则增加锁的重入次数和设置过期时间,返回null
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
-- 获取锁失败,返回锁的剩余时间
return redis.call('pttl', KEYS[1]);

二、WatchDog机制

WatchDog机制的关键源码在scheduleExpirationRenewal方法里。

WatchDog机制就是在后台开启一个定时任务(默认每次10秒一次),去判断当前客户端是否持有锁,如果是就给锁续期。

private void scheduleExpirationRenewal(long threadId) {
    // ExpirationEntry是一个key为线程id,value为Integer的map
    ExpirationEntry entry = new ExpirationEntry();
    ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
    if (oldEntry != null) {
        oldEntry.addThreadId(threadId);
    } else {
        entry.addThreadId(threadId);
        renewExpiration();
    }
}

private void renewExpiration() {
    ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (ee == null) {
        return;
    }

    // 在客户端对应的连接上开启一个定时任务,每隔 internalLockLeaseTime / 3 秒就续期锁
    // internalLockLeaseTime == lockWatchdogTimeout == 30s,默认每隔10秒续期一次锁
    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
            if (ent == null) {
                return;
            }
            Long threadId = ent.getFirstThreadId();
            if (threadId == null) {
                return;
            }

            RFuture<Boolean> future = renewExpirationAsync(threadId);
            future.onComplete((res, e) -> {
                if (e != null) {
                    log.error("Can't update lock " + getName() + " expiration", e);
                    return;
                }

                // reschedule itself
                renewExpiration();
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

    // 设置ExpirationEntry的超时时间,当释放锁时,会撤销WatchDog任务后再移除ExpirationEntry
    ee.setTimeout(task);
}

protected RFuture<Boolean> renewExpirationAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                                          "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                          "return 1; " +
                                          "end; " +
                                          "return 0;",
                                          Collections.<Object>singletonList(getName()), 
                                          internalLockLeaseTime, getLockName(threadId));
}

续期锁对应的lua脚本

续期成功返回1,否则返回0。

--[[
参数:
KEYS[1] 锁名称
ARGV[1] 锁的过期时间
ARGV[2] 对应锁的唯一标识
--]]

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
	-- 如果当前客户端持有锁,则重新设置锁的过期时间
    redis.call('pexpire', KEYS[1], ARGV[1]); " 
	return 1;
end;
return 0;

三、解锁

解锁方法:RLock#unlock

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

public RFuture<Void> unlockAsync(long threadId) {
    RPromise<Void> result = new RedissonPromise<Void>();
    // 释放锁
    RFuture<Boolean> future = unlockInnerAsync(threadId);

    // 开启异步任务去撤销此锁上的定时任务
    future.onComplete((opStatus, e) -> {
        if (e != null) {
            cancelExpirationRenewal(threadId);
            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;
        }

        cancelExpirationRenewal(threadId);
        result.trySuccess(null);
    });

    return result;
}

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                                          "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;",
                                          Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

}

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

    if (threadId != null) {
        // 移除在此ExpirationEntry等待的线程
        task.removeThreadId(threadId);
    }

    if (threadId == null || task.hasNoThreads()) {
        // 撤销在此锁上的定时任务
        task.getTimeout().cancel();
        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
    }
}

解锁对应的lua脚本

锁不存在,直接返回null;锁释放,返回0,否则返回1。

--[[
参数:
KEYS[1] 锁名称
KEYS[2] 锁释放事件的频道名称
ARGV[1] 锁释放消息的标志
ARGV[2] 锁的过期时间
ARGV[3] 对应锁的唯一标识
--]]

if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    -- 如果锁不存在,则返回null
	return nil;
end;
-- 锁存在,则锁的重入次数-1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
    -- 客户端重入次数大于0,表示还持有锁,则重新设置过期时间,返回0
	redis.call('pexpire', KEYS[1], ARGV[2]);
	return 0;
else
    -- 客户端重入次数等于于0,表示释放锁,则删除锁对应的key并推送锁释放事件,返回1
    redis.call('del', KEYS[1]);
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1;
end;
return nil;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤独的深山老人

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值