Redis分布式锁 注解和代码形式
对spring-boot-distributed-redisson项目进行讲解
关于分布式锁的介绍大家肯定可以说上很多,这里仅作一些补充:
针对分布式服务想要去对共享资源进行上锁,之前使用的线程级别的锁只能作用在当前服务下,通俗的讲就是一个jvm中,分布式架构下肯定是不行的,其实本质来讲两者区别不大
redisson
RLock rLock = redisson.getLock(lockName);
boolean getLock = false;
try {
//param1:等待时间
//param2:锁的有效时间
//param3:时间单位
getLock = rLock.tryLock( waitTime,leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("获取Redisson分布式锁[异常],lockName=" + lockName, e);
e.printStackTrace();
return false;
}
return getLock;
RLock rLock = redisson.getLock(lockName); //锁有效时间采用默认时间30秒
rLock.lock();
如果您系统中锁的使用比较频繁 可以通过自定义注解的形式结合AOP进行使用
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DistributedLock {
/**
* 锁的名称
*/
String value() default "redisson_lock";
/**
* 锁的有效时间
*/
int leaseTime() default 10;
}
@Aspect
@Component
@Slf4j
public class DistributedLockHandler {
@Autowired
RedissonLock redissonLock;
@Around("@annotation(distributedLock)")
public void around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
log.info("[开始]执行RedisLock环绕通知,获取Redis分布式锁开始");
//获取锁名称
String lockName = distributedLock.value();
//获取超时时间,默认10秒
int leaseTime = distributedLock.leaseTime();
redissonLock.lock(lockName, leaseTime);
try {
log.info("获取Redis分布式锁[成功],加锁完成,开始执行业务逻辑...");
//执行方法
joinPoint.proceed();
} catch (Throwable throwable) {
log.error("获取Redis分布式锁[异常],加锁失败", throwable);
throwable.printStackTrace();
} finally {
//如果该线程还持有该锁,那么释放该锁。如果该线程不持有该锁,说明该线程的锁已到过期时间,自动释放锁
if (redissonLock.isHeldByCurrentThread(lockName)) {
redissonLock.unlock(lockName);
}
}
log.info("释放Redis分布式锁[成功],解锁完成,结束业务逻辑...");
}
}
这样的情况下存在我们设置一个lock的有效时间为10秒,但是业务代码执行了20秒,导致其他线程获取到了锁,同样也是存在问题的,那么需要我们对key进行续期,Redisson有一个看门狗机制,自动进行key的续期,原理是:redisson在获取锁之后,会维护一个看门狗线程,当锁即将过期还没有释放时,不断的延长锁key的生存时间 需要主要以下几点:
- 看门狗启动后,对整体性能也会有一定影响,默认情况下看门狗线程是不启动的。如果使用redisson进行加锁的同时设置了锁的过期时间,也会导致看门狗机制失效。
- redisson在获取锁之后,会维护一个看门狗线程,在每一个锁设置的过期时间的1/3处,如果线程还没执行完任务,则不断延长锁的有效期。看门狗的检查锁超时时间默认是30秒,可以通过
Config.lockWatchdogTimeout
参数来改变。 - 加锁的时间默认是30秒,如果加锁的业务没有执行完,那么每隔 30 ÷ 3 = 10秒,就会进行一次续期,把锁重置成30秒,保证解锁前锁不会自动失效。
- 那万一业务的机器宕机了呢?如果宕机了,那看门狗线程就执行不了了,就续不了期,那自然30秒之后锁就解开了呗。
public String redissonLockWatchDog(String key){
//获取一把锁,只要锁的名字一样就是同一把锁
RLock lock = singleRedissonClient.getLock(key);
//1).redisson的自动续期,如果业务超长,运行期间自动续上30s,不用担心业务时间长,锁自动过期被删掉
//2).加锁得业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除
lock.lock();
try {
System.out.println("加锁成功,开始执行业务: " + lock.getName() + " 时间: " + getDateStr(new Date()));
Thread.sleep(65000);
System.out.println("加锁成功,结束执行业务: " + lock.getName() + " 时间: " + getDateStr(new Date()));
} catch (Exception e){
System.out.println("报错了: " + e);
} finally{
//解锁
System.out.println("释放锁"+lock.getName());
lock.unlock();
}
return "hello";
}
- watch dog 机制启动,且代码中没有释放锁操作时,watch dog 会不断的给锁续期;
- 如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {} 中;
- 要使 watchLog机制生效 。只要不穿leaseTime即可
- watchlog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间,但是不要设置太小。如100
- watchdog 会每 lockWatchdogTimeout/3时间,去延时。
- watchdog 通过 类似netty的 Future功能来实现异步延时
- watchdog 最终还是通过 lua脚本来进行延时