马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
背景
在微服务项目中,大家都会去使用到分布式锁,一般也是使用Redis去实现,使用RedisTemplate、Redisson、RedisLockRegistry都行,公司的项目中,使用的是Redisson,一般你会怎么用?看看下面的代码,是不是就是你的写法- 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();
- }
复制代码 分析
像上面的写法,符合我们的常规思维,一般,为了避免程序挂了的情况,没有释放锁,都会设置一个过期时间
但这个过期时间,一般设置多长?
设置过短,会导致我们的业务还没有执行完,锁就释放了,其它线程拿到锁,重复执行业务
设置过长,如果程序挂了,需要等待比较长的时间,锁才释放,占用资源
这时候,你会说,一般我们可以根据业务执行情况,设置个过期时间即可,对于部分执行久的业务,Redisson内部是有个看门狗机制,会帮我们去续期,简单来说,就是有个定时器,会去看我们的业务执行完没,没有就帮我们进行延时,看似没有问题吧,那我们来简单看下源码,无论我们使用哪种方式,最终都会进到这个方法,就是看门狗机制的核心代码- 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 = (Long)future.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 ((Boolean)future.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的时间轮
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |