分布式锁的基本特点:互斥,防死锁,性能,可重入
针对以上特点,Redisson都能做到很好的满足。
先看demo效果
pom依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.7.4</version>
</dependency>
配置文件
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
pool:
max-active: 10
max-wait: -1
max-idle: 8
min-idle: 0
imeout: 0
config配置
/**
* @author TangHaiZhi
* @date 2021/7/22
*/
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
未加锁时
用Jmeter在5秒内并发6000次,可以看到消费时又很多次都重复消费了
对代码进行改造,用redisson进行加锁
@Autowired
RedissonClient redissonClient;
@RequestMapping(value = "/consumer", method = RequestMethod.GET)
public String consumer() {
RLock lock = redissonClient.getLock("LOCKKEY");
try {
//这里的5指有5s的时间争抢锁,5s后为抢到锁返回false
boolean flag = lock.tryLock(5,TimeUnit.SECONDS);
if (flag){
List<ServHwMerchant> list = servHwMerchantDAO.findAll();
int num = list.get(0).getIndustry();
num--;
System.out.println("num=" + num);
list.get(0).setIndustry(num);
servHwMerchantDAO.save(list.get(0));
return "success";
} else {
return "fail";
}
} catch (Exception e){
System.out.println(e.getMessage());
}finally {
lock.unlock();
}
return "fail";
}
加锁后
用Jmeter在5秒内并发6000次,可以看到没有出现重复消费的情况了
深入分析
上面有说到Redission对互斥,防死锁,性能,可重入都有很好的满足。
互斥很好理解,当有一个线程获得锁之后别的线程拿不到锁,就一直循环等待锁。
防死锁,我们在加锁之后,redisson会自动设置一个锁的有效时间,到期之后redisson会自动释放锁。
那么这里就出现了几个问题
问题:如果到期之后我的业务逻辑其实还没有走完,这个时候redisson给我自动释放锁了怎么办。(默认30s后自动释放锁)
这里就要另外再说一个东西,redisson的看门狗。它的作用是在Redisson实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。
这里博主自己做过尝试,获取到锁后直接将线程sleep40秒,然后用redis的可视化工具观察锁的生存时间,发现获取到锁后是30,当生存时间减少到20时,看门狗启动了,将生存时间重置为30.也就是说不是20+30=50.
默认情况下,看门狗的续期时间是30s,也可以通过修改Config.lockWatchdogTimeout来另行指定。如下图所示:
再了解一下另一个tryLock方法
tryLock(long waitTime, long leaseTime, TimeUnit unit)
- waitTime 为获取锁的最大等待时间
- leaseTime 为多久之后制动释放锁
- unit 为锁的时间级别
指定leaseTime参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。因此最好不要使用这个方法。
另外在finally中必须释放锁,否则会出现业务报错后的死锁情况。
缺陷
redis在哨兵模式下,在master挂掉的情况下,会将一个备用节点切换为master。如果是在master上加了锁,还没有同步到备用节点上的时候挂了,这时候将备用节点切换过来。就会出现问题。