[总结]redisson分布式锁自动续期原理分析及源码分析

网上很多文章关于redission自动续期原理,基本中文一句话带过,但是对于源码都没有分析。大部分分析都是错误的!!!所以在此对着源码分析分析。

 

一句话总结

redission分布式锁自动续期,是在超市时间/3的时候,会触发锁检查,发现线程ID未解锁,则触发续锁操作。续锁会创建redission自己实现的TimerTask,然后放到时间轮中触发,触发延迟1500ms。

时间轮相当于一个倒计时的秒表,时间轮的格子数,每个格子代表的时间间隔(秒表倒计时指针多久走一格)都可以设置,每个格子内的任务有个队列缓存,初始长度是1024。然后当倒计时指针走到当前格子时,格子内的任务,当round轮数是0的时候触发,如果round轮数>0则减1;然后倒计时指针继续走下一个格子。

当倒计时指针走到最后一个格子的时候,复位到第一个格子(格子虽然是个列表,但是这种行为看起来像个环)。

因为采用了时间轮,只有一个倒计时主线程,所以不会太费性能。

 

以下是源码分析

RedissionLock源码

lock() -> lockInterruptibly

    @Override
    public void lock() {
        try {
            lockInterruptibly();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    @Override
    public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return;
        }

        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) {
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    getEntry(threadId).getLatch().acquire();
                }
            }
        } finally {
            unsubscribe(future, threadId);
        }
//        get(lockAsync(leaseTime, unit));
    }

在tryAcquire(leaseTime, unit, threadId)方法中,会调用scheduleExpirationRenewal(final long threadId)

internalLockLeaseTime是用户加锁传入的超时时间,所以自动续期方法的执行周期是超时时间的1/3。

 

private void scheduleExpirationRenewal(final long threadId) {
    if (expirationRenewalMap.containsKey(getEntryName())) {
        return;
    }

    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            
            RFuture<Boolean> future = renewExpirationAsync(threadId);
            
            future.addListener(new FutureListener<Boolean>() {
                @Override
                public void operationComplete(Future<Boolean> future) throws Exception {
                    expirationRenewalMap.remove(getEntryName());
                    if (!future.isSuccess()) {
                        log.error("Can't update lock " + getName() + " expiration", future.cause());
                        return;
                    }
                    
                    if (future.getNow()) {
                        // reschedule itself
                        scheduleExpirationRenewal(threadId);
                    }
                }
            });
        }

    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

    if (expirationRenewalMap.putIfAbsent(getEntryName(), new ExpirationEntry(threadId, task)) != null) {
        task.cancel();
    }
}

 

续期的核心方法renewExpirationAsync(long threadId) ---> commandExecutor.evalWriteAsync()

----> org.redisson.command.CommandAsyncService#evalAsync()

----> org.redisson.command.CommandAsyncService#async() 里面的核心代码创建TimerTask任务

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));
}

 

TimerTask任务

----> connectionManager.newTimeout(retryTimerTask, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS); 通过时间轮来触发这个task。

public <V, R> void async(final boolean readOnlyMode, final NodeSource source, final Codec codec,
        final RedisCommand<V> command, final Object[] params, final RPromise<R> mainPromise, final int attempt, 
        final boolean ignoreRedirect) {
    if (mainPromise.isCancelled()) {
        free(params);
        return;
    }

    if (!connectionManager.getShutdownLatch().acquire()) {
        free(params);
        mainPromise.tryFailure(new RedissonShutdownException("Redisson is shutdown"));
        return;
    }

    Codec codecToUse = getCodec(codec);
    
    final AsyncDetails<V, R> details = AsyncDetails.acquire();
    final RFuture<RedisConnection> connectionFuture = getConnection(readOnlyMode, source, command);

    final RPromise<R> attemptPromise = new RedissonPromise<R>();
    details.init(connectionFuture, attemptPromise,
            readOnlyMode, source, codecToUse, command, params, mainPromise, attempt);

    FutureListener<R> mainPromiseListener = new FutureListener<R>() {
        @Override
        public void operationComplete(Future<R> future) throws Exception {
            if (future.isCancelled() && connectionFuture.cancel(false)) {
                log.debug("Connection obtaining canceled for {}", command);
                details.getTimeout().cancel();
                if (details.getAttemptPromise().cancel(false)) {
                    free(params);
                }
            }
        }
    };

    final TimerTask retryTimerTask = new TimerTask() {
    //...
    }
    //private int retryInterval = 1500;
    Timeout timeout = connectionManager.newTimeout(retryTimerTask, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS);
    details.setTimeout(timeout);
    details.setupMainPromiseListener(mainPromiseListener);

    connectionFuture.addListener(new FutureListener<RedisConnection>() {
        //...
    }
}

 

时间轮触发,默认org.redisson.config.BaseConfig的配置private int retryInterval = 1500;

private HashedWheelTimer timer;

public Timeout org.redisson.connection.MasterSlaveConnectionManager#newTimeout(TimerTask task, long delay, TimeUnit unit) {
    try {
        return timer.newTimeout(task, delay, unit);
    } catch (IllegalStateException e) {
        // timer is shutdown
        return dummyTimeout;
    }
}

 

 

 

Redisson 中,可以使用分布式锁来实现续期功能。续期指的是在获取锁的过程中,如果业务逻辑执行时间超过了锁的有效期,可以通过续期操作延长锁的有效时间,避免其他节点误删锁导致并发问题。下面是一个续期示例: ```java RLock lock = redisson.getLock("myLock"); boolean locked = lock.tryLock(); if (locked) { try { // 成功获取到锁 // 执行业务逻辑 // ... // 续期操作 long lockExpireTime = 60000; // 锁的有效时间,单位:毫秒 long renewTime = 30000; // 续期时间,单位:毫秒 while (true) { // 检查是否需要续期 long remainTime = lock.remainTimeToLive(); if (remainTime > renewTime) { // 还有足够的时间,不需要续期 break; } // 续期 boolean renewed = lock.forceRenew(lockExpireTime); if (renewed) { // 续期成功 break; } // 续期失败,可能是锁被释放或者其他原因 // 可以根据具体情况选择重试或者退出循环 } } finally { // 释放锁 lock.unlock(); } } else { // 获取锁失败,可以根据具体情况选择重试或者退出逻辑 } ``` 在上面的示例中,`tryLock()` 方法尝试获取锁,如果成功获取到锁,则执行业务逻辑。在业务逻辑执行过程中,通过 `remainTimeToLive()` 方法检查剩余的锁的有效时间是否足够,如果不足够,则通过 `forceRenew()` 方法进行续期操作。 请注意,续期操作是在获取锁期间进行的,如果在续期过程中锁被其他节点释放,则续期操作会失败。因此,需要根据具体情况选择是否进行重试或者退出逻辑。 希望对你有帮助!如果还有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值