Redis分布式锁这样用,有坑

科技资讯 投稿 13400 0 评论

Redis分布式锁这样用,有坑

背景

String lockKey = "forlan_lock_" + serviceId;
RLock lock = redissonClient.getLock(lockKey;

// 方式1
try {
	lock.lock(5, TimeUnit.SECONDS;
	// 执行业务
	...
} catch (Exception e {
	e.printStackTrace(;
} finally {
	// 释放锁
	lock.unlock(;
}

// 方式2
try {
	if (lock.tryLock(5, 5, TimeUnit.SECONDS {
		// 获得锁执行业务
		...
	}
} catch (Exception e {
	e.printStackTrace(;
} finally {
	// 释放锁
	lock.unlock(;
}

分析

像上面的写法,符合我们的常规思维,一般,为了避免程序挂了的情况,没有释放锁,都会设置一个过期时间
但这个过期时间,一般设置多长?

设置过长,如果程序挂了,需要等待比较长的时间,锁才释放,占用资源

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId {
    if (leaseTime != -1L {
        // 前面我们指定了过期时间,会进到这里,直接加锁
        return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG;
    } else {
        // 没有指定过期时间的话,默认采用LockWatchdogTimeout,默认是30s
        RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager(.getCfg(.getLockWatchdogTimeout(, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG;
        // ttlRemainingFuture执行完,添加一个监听器,类似netty的时间轮
        ttlRemainingFuture.addListener(new FutureListener<Long>( {
            public void operationComplete(Future<Long> future throws Exception {
                if (future.isSuccess( {
                    Long ttlRemaining = (Longfuture.getNow(;
                    if (ttlRemaining == null {
                        RedissonLock.this.scheduleExpirationRenewal(threadId;
                    }
                }
            }
        };
        return ttlRemainingFuture;
    }

scheduleExpirationRenewal方法

private void scheduleExpirationRenewal(final long threadId {
 if (!expirationRenewalMap.containsKey(this.getEntryName( {
     Timeout task = this.commandExecutor.getConnectionManager(.newTimeout(new TimerTask( {
         public void run(Timeout timeout throws Exception {
         	 // renewExpirationAsync就是执行续期的方法
             RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId;
             // 什么时候触发执行?
             future.addListener(new FutureListener<Boolean>( {
                 public void operationComplete(Future<Boolean> future throws Exception {
                     RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName(;
                     if (!future.isSuccess( {
                         RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName( + " expiration", future.cause(;
                     } else {
                         if ((Booleanfuture.getNow( {
                             RedissonLock.this.scheduleExpirationRenewal(threadId;
                         }

                     }
                 }
             };
         }
     }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS; // 当跑了LockWatchdogTimeout的1/3时间就会去执行续期
     if (expirationRenewalMap.putIfAbsent(this.getEntryName(, new RedissonLock.ExpirationEntry(threadId, task != null {
         task.cancel(;
     }
 }

所以,结论是啥?

// 方式1
lock.lock(5, TimeUnit.SECONDS;
// 方式2
lock.tryLock(5, 5, TimeUnit.SECONDS

我们这两种写法都会导致看门狗机制失效,如果业务执行超过5s,就会出问题

解决

// 方式1
lock.lock(;
// 方式2
lock.tryLock(5, -1, TimeUnit.SECONDS

你可以会觉得不妥,不指定的话,就默认按照30s续期时间,然后每10s去看看有没有执行完,没有就续期,
我们也可以指定续期时间,比如指定为15s

config.setLockWatchdogTimeout(15000L;

总结

    在使用Redisson实现分布式锁,不应该设置过期时间
  • 看门狗默认续期时间是30s,可以通过setLockWatchdogTimeout指定
  • 看门狗会每internalLockLeaseTime / 3L去续期
  • 看门狗底层实际就是类似Netty的时间轮

编程笔记 » Redis分布式锁这样用,有坑

赞同 (69) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽