背景

在微服务项目中,大家都会去使用到分布式锁,一般也是使用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的时间轮

Redis分布式锁这样用,有坑?的更多相关文章

  1. redis 分布式锁的 5个坑,真是又大又深

    引言 最近项目上线的频率颇高,连着几天加班熬夜,身体有点吃不消精神也有些萎靡,无奈业务方催的紧,工期就在眼前只能硬着头皮上了.脑子浑浑噩噩的时候,写的就不能叫代码,可以直接叫做Bug.我就熬夜写了一个 ...

  2. redis分布式锁的这些坑,我怀疑你是假的开发

    摘要:用锁遇到过哪些问题? 一.白话分布式 什么是分布式,用最简单的话来说,就是为了较低单个服务器的压力,将功能分布在不同的机器上面:就比如: 本来一个程序员可以完成一个项目:需求->设计-&g ...

  3. 面试官问我,Redis分布式锁如何续期?懵了。

    前言 上一篇[面试官问我,使用Dubbo有没有遇到一些坑?我笑了.]之后,又有一位粉丝和我说在面试过程中被虐了.鉴于这位粉丝是之前肥朝的粉丝,而且周一又要开启新一轮的面试,为了回馈他长期以来的支持,所 ...

  4. 死磕 java同步系列之redis分布式锁进化史

    问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...

  5. springBoot实现redis分布式锁

    参考:https://blog.csdn.net/weixin_44634197/article/details/108308395 .. 使用redis的set命令带NX(not exist)参数实 ...

  6. Redis 分布式锁|从青铜到钻石的五种演进方案

    缓存系列文章: 缓存实战(一):20 图 |6 千字|缓存实战(上篇) 缓存实战(二):Redis 分布式锁|从青铜到钻石的五种演进方案 缓存实战(三):分布式锁中的王者方案 - Redisson 上 ...

  7. 卧槽,redis分布式如果用不好,坑真多

    前言 在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁的首先,被我们用到了很多实际业务场景当中. 但不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引 ...

  8. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  9. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  10. redis分布式锁和消息队列

    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...

随机推荐

  1. js 全屏显示指定区域

    小猪最近使用vue开发项目的时候,需要实现将指定区域全拼展示的操作. 小猪选择使用js来实现,实现原理:使用requestFullscreen()方法将指定的dom元素在全屏模式下打开. 支持的部分浏 ...

  2. excel制作表格

    我们这个表格举例: 1. 序号列自动生成序号.选中数字"1"所在的单元格,将鼠标停留在图片中标红的区域向下拖动即可                                 ...

  3. python——tips

    一:python分号使用 每一条语句最后个加个分号:这是c,oc,java,php等语言中不可缺少的部分,但是对于python,分号是可加,可不加的 如:不加分号代码 class Person: na ...

  4. OSIDP-内存管理-07

    专业术语 页框:内存中固定长度的块. 页:外存中固定长度的块. 段:外存中可变长度的块. 内存管理需求 重定位:程序从内存换出到外存后,再换回内存时,在内存空间中的位置和原先的位置有极大可能不相同.此 ...

  5. 字符串替换Replace仅替换第一个匹配项

    C#里面的String.Replace(string,string)方法替换的时候是替换所有的匹配项,但是有时候我们会遇到这样的需求,就是只替换第一个匹配项. 我这里自己写另一个方法来实现这个功能,求 ...

  6. 安卓手机qpython使用感觉

    我是写C#的,最佳一时兴起,下载了个qpython来玩儿,发现这东西写点简单的爬虫还行,配合sqlite,可以做一些简单的事情,于是乎想写一个有趣的东西,在qpython写一个脚本,去收蚂蚁森林的能量 ...

  7. 关于 ubuntu 22 desktop 安装 网易云音乐无法启动解决办法

    - 报错现象 /opt/netease/netease-cloud-music/netease-cloud-music: /opt/netease/netease-cloud-music/libs/l ...

  8. MSVC-用于其他IDE的手工环境配置,手工提取

    ​ 最近因为在使用Code::Blocks编程,遇到了MSVC编译的库,不愿意换VS,所以手工配置了MSVC路径.CB是有点老了,不像现在新的IDE都是自动搜索的,而且我又不会批处理orz. 这里面可 ...

  9. zsh以及oh-my-zsh的安装配置

    Oh My Zsh是一款社区驱动的命令行工具,正如它的主页上说的,Oh My Zsh 是一种生活方式.它基于zsh命令行,提供了主题配置,插件机制,已经内置的便捷操作.给我们一种全新的方式使用命令行. ...

  10. 微信支付服务商api对接

    引入官方sdk <!--微信v3支付sdk {https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient}--> < ...