首先了解一下单机版redis和集群版redis的存储机制原理:
1.使用单节点时的redis时只有一个表,所有的key都放在这个表里;2.改用Redis Cluster以后会自动为你生成16384个分区表
2.只有了解了redis的存储原理才能更好的理解使用redis充当分布式锁的原因
redis存储机制:https://blog.csdn.net/qq_38545713/article/details/89600442
一:之前的方式实现redis分布式锁:
1.在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,
2.将 key 的值设为 value ,当且仅当 key 不存在。成功返回1
3.若给定的 key 已经存在,则 SETNX 不做任何动作。失败返回0
4.然后再给key设置一个失效时间
5.redis.setnx("key",value);
6.redis.expri("key",time);
上述实现方式的缺点,不是原子性,有可能会造成key一直存在形成死锁
因之前redis版本低不支持setnx中加入时间限制现在高版本的redis都已经支持
redis cluster下创建分布式锁,官方推荐的RedLock 加锁算法原理
二:redission是对redis进一步的封装,实现分布式锁的方式
高一点版本的redis已经支持了setnx加入时间的参数,所以操作是原子性的不会
再因为断电宕机等因素出现死锁
maven
<!--redis分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
private static final String KEY = "test-redis-lock";
@Autowired
private RedissonClient redissonClient;
@RequestMapping(value = "/test", method = RequestMethod.GET)
public Response<String> getTest(@RequestParam("couponId") long couponId) {
/**
* 这是根据不同的优惠券id进行加锁,每张优惠券需保证只能被一个人领取,
* 但是不同的优惠券之间互不影响
*
* 如果是抽奖类的加锁则可以写一个固定的锁的名字例如:
* redissonClient.getLock("object");这是保证不管那个人获得的都是这个名字的锁
*/
//获取一个名字为KEY +couponId的锁
RLock rLock = redissonClient.getLock(KEY + couponId);
boolean isLock = false;
try {
/**
* 第一个参数:2s之内一直尝试加锁
* 第二个参数:加锁成功后10s失效,防止服务器断电执行不到unlock
* TimeUnit.SECONDS时间单位
*尝试加锁,为c+couponId的key设置setnx并设置过期时间
*因为redis是单线程的,所以即使是项目集群部署在这里每个服务同时请求尝试加锁
*redis也会一个一个的执行
* redis集群模式官方推荐的RedLock算法原理:就是客户端在redis集群中的大多数节
点上(n/2+1)尝试加锁,在超时时间内当大多数节点都加锁成功了那么就认为
这个分布式锁创建成功,否则创建失败,并把创建成功的那些节点上的锁依次
删除掉。其实redis集群上创建分布式锁和单节点上创建分布式锁原理很相
似,都是setnx
*
*/
isLock = rLock.tryLock(2, 10, TimeUnit.SECONDS);
//true加锁成功
if (isLock) {
System.out.println("获取锁成功"+couponId);
Thread.sleep(500);
}else {
log.error("2s尝试时间使用完毕,加锁失败。。。。");
}
} catch (InterruptedException e) {
log.error("捕获异常。。。。", e);
} finally {
log.info("tryLock设置过期时间是因为怕服务器断电等原因执行不到这一步");
//加入该判断是因为如果有的线程没有加锁成功,那么也会执行finally中的释放锁的方法,然后就会报错,如下错误,如果加入改判断没有获得锁的线程就不会在执行释放锁的方法可以避免报错
if (isLock) {
rLock.unlock();
}
}
return Response.builderSuccess("ok");
}
1.lock(2,TimeUnit.SECONDS);表示锁超过两秒锁即释放
2.tryLock(2,5, TimeUnit.SECONDS);表示线程等待超过两秒锁即取消等待,key的失效时间为5s
报错日志分析:
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 2e9e88ca-a4e0-4c53-be05-98ed6a96ba83 thread-id: 62
http-nio-8080-exec-6----3.004
报错的原因是当并发线程来尝试加锁时,有的线程加锁成功,而有的线程加锁不成功过,但是尝试加锁时间也已经超时,这时这个加锁失败的线程就会执行finally里面的释放锁的方法,但是这个线程本身根本没有加锁成功,所以执行unlock时就会报“尝试解锁,不被当前线程按节点ID锁定:2e9e88ca-a4e0-4c53-be05-98ed6a96ba83 thread-id”尝试解锁但是锁不是当前线程所加的所以解锁失败报错