谈到redisson就不得不说Redis了,一想到Redis就不得不想到并发编程锁机制,一想到锁机制那么就不能不考虑一个很头疼的问题,如何保证原子性的问题,高QPS请求量的系统对每次执行数据的原子性由为的关键,保证不了原子性就会导致一系列重复提交的操作,重复的数据导致在某些逻辑运算的时候发生误差;
ACID的特性首先是原子性,原子性永远是放在首位的,所以我们首先要解决的就是接口请求的原子性;
Redis分布式锁到底能不能保证原子性,这是面试会被经常问到的一个问题。大部分的人回答都是不能保证原子性,但是其中的所以然大家都很模糊,我也一样,但为什么还在用Redis的分布式锁,如何让它可以具有原子性呢?
SET NX相信大家都知道是redis实现加锁的一个命令,这里用Jedis封装的Api接口去对redis进行使用,Jedis是Redis官方推荐的面向JAVA操作Redis的客户段,RedisTemplate是SpringDataRedis中对Redis的封装客户端,方便的就是可以搭配Spring框架使用,如Spring cache;
先上代码吧,这是我曾经写的一段关于redis分布式锁的代码,主要用于重复提交的判断,这里我用了jedis的setnx方法,加锁后的返回不为null && 值等于1的时候表示加锁成功,并且调用expire方法对key进行赋过期时间,业务处理完成后进行锁的释放,防止死锁,这里是常规的redis的加锁方式;
java复制代码
/**
* 分布式事务锁-判断是否请求过
*
* @param key 键
* @param time 过期时间
* @return true:存在
*/
@HystrixCommand(fallbackMethod = "isExistFAIl")
public boolean isExist(String key, int time) {
Jedis jedis = null;
try {
jedis = jedisPoolManager.getJedis();
String uniqKey = JEDIS_KEYNAME + key;
String val = jedis.get(uniqKey);
if (val != null) {
return true;
}
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
Long flag = jedis.setnx(uniqKey, LOCAL_VAL);
if (flag != null && flag.intValue() == 1) {
jedis.expire(uniqKey, time);
} else {
throw new ExceptionTyche("加锁失败! ");
}
} finally {
lock.unlock();
}
} else {
log.info("执行Redis超时!直接返回!");
}
} catch (Exception e) {
log.warn("Redis缓存异常! key={} errMsg:" + e.getMessage(), key, e);
} finally {
jedisPoolManager.close(jedis); // !!!关闭
}
return false;
}
细心的小伙伴可以看到, 我在setnx加锁之前,用了lock.tryLock这个方法,用jdk的锁先尝试进行获取锁,如果没有获取到直接返回,这样就能在一定程度上避免通过redis加锁后,业务逻辑还未执行完锁超时进行释放,导致下次同样的key获取到锁就会出现重复提交的操作
并且使用了HystrixCommand熔断注解,防止在高并发的情况下加锁方法出现异常,对其进行降级,保证业务提交的原子性;
关于Redisson是目前使用比较多的一个关于分布式锁的客户端,其主要原理相信大家知道,那就是watchDog机制,俗称“看门狗机制”,由于这种机制能对锁的过期时间进行续期在很大程度上能保证加锁的原子性;
关于Redisson的分布式锁之前的文章写过,通过注解的方式去实现juejin.cn/post/721514…
前段时间突然遇到了这么一个问题,Redisson锁是统一进行续期的还是分开续期的,之前确实没有考虑过,下面来看下源码具体分析下;
这里我使用的是,redisson3.8.2的版本
xml复制代码<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.2</version>
</dependency>
这个方法是Redisson加锁的核心代码,本质也是通过redis的lua脚本;
代码解读:*KEYS[1]*加锁key,*ARGV[2]*加锁的值,*ARGV[1]*看门狗机制的过期时间,internalLockLeaseTime续期时间;
既然有加锁,相对肯定有解锁实现;
通过对redisson的加锁、解锁源码分析,相信大家对这块已经有个很清楚的认识, 还有就是只有当前线程才是获取自己的锁,不是当前线程无法获取到锁,就意味着无法进行锁续期的操作 ,由此可证明Redisson锁的续期是分开进行的,
commandExecutor.evalWriteAsync的加锁方法比较长,这里就不截出来了,有兴趣的小伙伴可以去追踪看看;
主要续期逻辑就是,例如一个线程加锁成功,就是自动触发Watchdog锁续期机制,后台是一个工作线程,每隔10秒钟的时间会check当前线程是否还持有锁,如果持有锁就将锁的过期时间延长至30秒;
这里也是对之前redisson锁的知识遗漏点的一个学习,结合实际的业务开发使用的案例,来分析锁的底层原理,从而来避免我在使用过程中遇到问题,能有更加清晰的解决思路,理解不对的地方也欢迎大家在评论区提出。
原文链接:
https://juejin.cn/post/7269385060612309007