东莞网站优化中易,钢丝网片每平米价格,台州百度关键词优化,大专网页设计工资怎么样一、背景
网上redis分布式锁的工具方法#xff0c;大都满足互斥、防止死锁的特性#xff0c;有些工具方法会满足可重入特性。如果只满足上述3种特性会有哪些隐患呢#xff1f;redis分布式锁无法自动续期#xff0c;比如#xff0c;一个锁设置了1分钟超时释放#xff0c;…一、背景
网上redis分布式锁的工具方法大都满足互斥、防止死锁的特性有些工具方法会满足可重入特性。如果只满足上述3种特性会有哪些隐患呢redis分布式锁无法自动续期比如一个锁设置了1分钟超时释放如果拿到这个锁的线程在一分钟内没有执行完毕那么这个锁就会被其他线程拿到可能会导致严重的线上问题。
既然存在锁过期而任务未执行完毕的情况那是否有一种可以在任务未完成时自动续期的机制呢几年前在redisson中找到了看门狗的自动续期机制就是解决这种分布式锁自动续期的问题的。 Redisson 锁的加锁机制如上图所示线程去获取锁获取成功则执行lua脚本保存数据到redis数据库。如果获取失败: 一直通过while循环尝试获取锁(可自定义等待时间超时后返回失败)获取成功后执行lua脚本保存数据到redis数据库。Redisson提供的分布式锁是支持锁自动续期的也就是说如果线程仍旧没有执行完那么redisson会自动给redis中的目标key延长超时时间这在Redisson中称之为 Watch Dog 机制
二、redisson 看门狗使用以及原理
1.redisson配置和初始化
pom.xml
dependencygroupIdorg.redisson/groupIdartifactIdredisson-spring-boot-starter/artifactIdversion3.16.4/version
/dependencyapplication.yaml
redis:host: xxxxxxxpassword: xxxxxxmax-active: 8max-idle: 500max-wait: 1min-idle: 0port: 6379timeout: 1000msdatabase: 0redisson配置类
Configuration
public class RedisConfig {//最简单的redisson初始化配置Beanpublic RedissonClient getRedisson() {Config config new Config();config.useSingleServer().setAddress(redis:// host : port).setPassword(password);return Redisson.create(config);}
}2.redisson看门狗使用
使用redisson分布式锁的目的主要是防止分布式应用产生的并发问题所以一般会进行一下调整改为AOP形式去进行业务代码解耦。这里会加入自定义注解和AOP。
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
public interface RedisLock {//锁的名称String lockName();//锁的失效时间long leaseTime() default 3;//是否开启看门狗默认开启开启时锁的失效时间不执行。任务未完成时会自动续期锁时间//使用看门狗锁默认redis失效时间未30秒。失效时间剩余1/3时进行续期判断是否需要续期boolean watchdog() default true;
}public class RedisLockAspect {Autowiredprivate RedissonClient redissonClient;private static final String REDIS_PREFIX redisson_lock:;Around(annotation(redisLock))public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {String lockName redisLock.lockName();RLock rLock redissonClient.getLock(REDIS_PREFIX lockName);Object result null;boolean isLock;if(redisLock.watchdog()){isLock rLock.tryLock(0, TimeUnit.SECONDS);}else {isLock rLock.tryLock(0,redisLock.leaseTime(), TimeUnit.SECONDS);}if(isLock){try {//执行方法result joinPoint.proceed();} finally {if (rLock.isLocked() rLock.isHeldByCurrentThread()) {rLock.unlock();}}}else {log.warn(The lock has been taken:{},REDIS_PREFIX lockName);}return result;}
}Scheduled(cron */10 * * * * ?)//使用注解进行加锁RedisLock(lockName npa_lock_test,watchdog true)public void redisLockTest() {System.out.println(get lock and perform a task);try {Thread.sleep(20000L);} catch (InterruptedException e) {e.printStackTrace();}}这里使用定时任务进行模拟调用10秒一次定时任务请求线程执行睡眠20秒后完成。下面看一下执行结果。当获取锁后第二次定时任务执行时。锁未被释放。所以失败第三次获取时所已经释放所以成功。 如果拿到分布式锁的节点宕机且这个锁正好处于锁住的状态时会出现锁死的状态为了避免这种情况的发生锁都会设置一个过期时间。这样也存在一个问题加入一个线程拿到了锁设置了30s超时在30s后这个线程还没有执行完毕锁超时释放了就会导致问题Redisson给出了自己的答案就是 watch dog 自动延期机制。 Redisson提供了一个监控锁的看门狗它的作用是在Redisson实例被关闭前不断的延长锁的有效期也就是说如果一个拿到锁的线程一直没有完成逻辑那么看门狗会帮助线程不断的延长锁超时时间锁不会因为超时而被释放。 默认情况下看门狗的续期时间是30s也可以通过修改Config.lockWatchdogTimeout来另行指定。另外Redisson 还提供了可以指定leaseTime参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了不会延长锁的有效期。
3.redisson源码
Redisson的源码版本基于3.16.4同时需要注意的是 watchDog 只有在未显示指定加锁时间leaseTime时才会生效。这点很重要 lockWatchdogTimeout设定的时间不要太小 比如我之前设置的是 100毫秒由于网络直接导致加锁完后watchdog去延期时这个key在redis中已经被删除了。 在调用lock方法时会最终调用到tryAcquireAsync。调用链为lock()-tryAcquire-tryAcquireAsync,详细解释如下
使用了RFuture相关内容涉及Netty异步回调模式-Future和Promise剖析去启动异步线程执行
private T RFutureLong tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFutureLong ttlRemainingFuture;//如果指定了加锁时间会直接去加锁if (leaseTime ! -1) {ttlRemainingFuture tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//没有指定加锁时间 会先进行加锁并且默认时间就是 LockWatchdogTimeout的时间//这个是异步操作 返回RFuture 类似netty中的futurettlRemainingFuture tryLockInnerAsync(waitTime, internalLockLeaseTime,TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}//这里也是类似netty Future 的addListener在future内容执行完成后执行ttlRemainingFuture.onComplete((ttlRemaining, e) - {if (e ! null) {return;}// lock acquiredif (ttlRemaining null) {// leaseTime不为-1时不会自动延期if (leaseTime ! -1) {internalLockLeaseTime unit.toMillis(leaseTime);} else {//这里是定时执行 当前锁自动延期的动作,leaseTime为-1时才会自动延期scheduleExpirationRenewal(threadId);}}});return ttlRemainingFuture;}scheduleExpirationRenewal 中会调用renewExpiration。 这里我们可以看到是启用一个timeout定时去执行延期动作
private void renewExpiration() {ExpirationEntry ee EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee null) {return;}Timeout task commandExecutor.getConnectionManager().newTimeout(new TimerTask() {Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent null) {return;}Long threadId ent.getFirstThreadId();if (threadId null) {return;}RFutureBoolean future renewExpirationAsync(threadId);future.onComplete((res, e) - {if (e ! null) {log.error(Cant update lock getRawName() expiration, e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {//如果 没有报错就再次定时延期// reschedule itselfrenewExpiration();} else {cancelExpirationRenewal(null);}});}// 这里我们可以看到定时任务 是 lockWatchdogTimeout 的1/3时间去执行 renewExpirationAsync}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}protected RFutureBoolean renewExpirationAsync(long threadId) {return this.evalWriteAsync(this.getRawName(), 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.singletonList(this.getRawName()), this.internalLockLeaseTime, this.getLockName(threadId));}
结论
watch dog 在当前节点存活时每10s给分布式锁的key续期 30swatch dog 机制启动且代码中没有释放锁操作时watch dog 会不断的给锁续期如果程序释放锁操作时因为异常没有被执行那么锁无法被释放所以释放锁操作一定要放到 finally {} 中要使 watchLog机制生效 lock时 不要设置 过期时间watchlog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间但是不要设置太小。如100watchdog 会每 lockWatchdogTimeout/3时间去延时。watchdog 通过 类似netty的 Future功能来实现异步延时watchdog 最终还是通过 lua脚本来进行延时