经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
Redis分布式锁这样用,有坑?
来源:cnblogs  作者:程序员Forlan  时间:2023/4/17 9:17:46  对本文有异议

背景

在微服务项目中,大家都会去使用到分布式锁,一般也是使用Redis去实现,使用RedisTemplate、Redisson、RedisLockRegistry都行,公司的项目中,使用的是Redisson,一般你会怎么用?看看下面的代码,是不是就是你的写法

  1. String lockKey = "forlan_lock_" + serviceId;
  2. RLock lock = redissonClient.getLock(lockKey);
  3. // 方式1
  4. try {
  5. lock.lock(5, TimeUnit.SECONDS);
  6. // 执行业务
  7. ...
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. } finally {
  11. // 释放锁
  12. lock.unlock();
  13. }
  14. // 方式2
  15. try {
  16. if (lock.tryLock(5, 5, TimeUnit.SECONDS)) {
  17. // 获得锁执行业务
  18. ...
  19. }
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. } finally {
  23. // 释放锁
  24. lock.unlock();
  25. }

分析

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

设置过短,会导致我们的业务还没有执行完,锁就释放了,其它线程拿到锁,重复执行业务
设置过长,如果程序挂了,需要等待比较长的时间,锁才释放,占用资源

这时候,你会说,一般我们可以根据业务执行情况,设置个过期时间即可,对于部分执行久的业务,Redisson内部是有个看门狗机制,会帮我们去续期,简单来说,就是有个定时器,会去看我们的业务执行完没,没有就帮我们进行延时,看似没有问题吧,那我们来简单看下源码,无论我们使用哪种方式,最终都会进到这个方法,就是看门狗机制的核心代码

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

scheduleExpirationRenewal方法

  1. private void scheduleExpirationRenewal(final long threadId) {
  2. if (!expirationRenewalMap.containsKey(this.getEntryName())) {
  3. Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
  4. public void run(Timeout timeout) throws Exception {
  5. // renewExpirationAsync就是执行续期的方法
  6. RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
  7. // 什么时候触发执行?
  8. future.addListener(new FutureListener<Boolean>() {
  9. public void operationComplete(Future<Boolean> future) throws Exception {
  10. RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());
  11. if (!future.isSuccess()) {
  12. RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());
  13. } else {
  14. if ((Boolean)future.getNow()) {
  15. RedissonLock.this.scheduleExpirationRenewal(threadId);
  16. }
  17. }
  18. }
  19. });
  20. }
  21. }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS); // 当跑了LockWatchdogTimeout的1/3时间就会去执行续期
  22. if (expirationRenewalMap.putIfAbsent(this.getEntryName(), new RedissonLock.ExpirationEntry(threadId, task)) != null) {
  23. task.cancel();
  24. }
  25. }

所以,结论是啥?

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

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

解决

正确的写法应该是,不指定过期时间

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

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

  1. config.setLockWatchdogTimeout(15000L);

总结

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

原文链接:https://www.cnblogs.com/huozhonghun/p/17323627.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号