Redis分布式锁
1. 引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 分布式锁实现
-
setnx命令
set if not exist,顾名思义,当不存在key的时候,则创建key并赋值为value。
分布式架构中,每当一个线程进入方法,则调用redis的setnx的方法:
如果赋值成功,则说明当前没有对应的key,也就是当前没有其他线程操作该方法,获取到锁,执行逻辑,最后释放锁。
如果没有赋值成功,则说明当前key已存在,即已经有线程在操作该方法,需要等待线程释放锁。
-
代码实现
@Autowired RedisTemplate redisTemplate; public void testLock(){ String lock = "cz"; //如果没有这个key,则赋值,返回成功。 Boolean absent = redisTemplate.opsForValue().setIfAbsent(lock, "lock"); if(absent){ //如果拿到锁,可以执行自己的业务逻辑 }else{ //没拿到,可以给出提示或者一些娄底的措施。 } //释放锁 redisTemplate.delete(lock); }
以上写法有没有问题?虽然实现了锁功能,但是,如果程序运行中,代码耗时长,或者因为异常,导致一直没有执行释放锁的操作,则会导致其他线程一直等待状态;另外,如果第一次获取锁失败了,是否可以做一些重试机制?
-
锁优化–设置超时与重试
public void testLock(){ String lock = "cz"; Integer i = 0; while (true && i<2){ Boolean absent = redisTemplate.opsForValue().setIfAbsent(lock, "lock"); if(absent){ //如果复制成功了,说明拿到了锁,设置key的有效期 redisTemplate.expire(lock,10, TimeUnit.SECONDS); }else { //拿不到锁,歇100毫秒重试一次 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } redisTemplate.delete(lock); }
上述代码,已经基本解决了异常超时问题,并且添加锁机制。但是,仔细分析,是否存在这么一个情况,x线程A进入方法,设置了key有效期为10s,但是实际由于业务逻辑复杂,代码执行时间超过了10s,redis还未删除key的时候,key已经先行失效。B进入方法,发现没有key,可以赋值,也可以拿到锁,正准备执行逻辑时,A线程代码执行结束,准备执行delete()方法删除key,但是A的key早就失效,删除了线程B生成的key。。。
要想解决上述问题的关键点,就是只能删除自己所持有的key。所以,再执行删除key的时候,需要判断value是不是自己设置的value。
-
优化
} public void testLock(){ String lock = "cz"; Integer i = 0; while (true && i<2){ //如果没有这个key,则赋值,返回成功。 Boolean absent = redisTemplate.opsForValue().setIfAbsent(lock, "lock");//此处value要设置唯一值 if(absent){ //如果复制成功了,说明拿到了锁,设置key的有效期 redisTemplate.expire(lock,10, TimeUnit.SECONDS); }else { //拿不到锁,歇100毫秒重试一次 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } String lockStr = redisTemplate.opsForValue().get(lock).toString(); if(!StringUtils.isEmpty(lockStr) && lockStr.equals("lock")){ //如果获取到的value不为空,并且与自己之前设置的value一致,则删除key。 redisTemplate.delete(lock); } }
扩展
SpringBoot敏捷开发的重要原因之一是无需过多配置,可自动装配。集成Redis后,可以通过读取spring.factories中的配置类信息,直线自动配置。
1.spring-boot-autoconfigure:250
2. 找到redis相关的配置类
3.redis配置