redisson 能干嘛
为什么要用分布式锁 redisson ,不用分布式锁 redisson 我们需要解决哪些问题?
- 单机版的服务使用
Synchronize
和Lock
是没问题的,如果一上集群,每个服务各自加锁的对象是不一样的,这时我们加的锁就不能在各自的服务里,需要借助外力,redis 进场; - 把锁放在redis后,如果服务突然挂了,锁就永远解不了,所以必须在加锁的时候设置过期时间,且加锁操作和设置过期时间必须是原子操作;
stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
- 我们知道解锁的方法必须写在
finally
里面,但可能业务处理时间过长,锁到了过期时间自动解锁了,而业务处理完后又执行了finally
里面的解锁方法,这时是解了别人的锁,A线程解B线程的锁,B线程解C线程的锁,后果不堪设想;所以还要判断 key(锁)里面的value是否等于我们拿到锁时value,如果相等才能删除;从拿到锁到比较value,到删除锁必须是原子操作,所以需要写LUR脚本或者用redis的事务,加上CAS自旋,一直比较和判断; - 我们再来看集群 redis 的问题,我们知道 redis 是 AP 的,即高可用和分区容错性,没有强一致性;假如有3个redis节点ABC,程序加锁到A主节点后,A节点突然挂机了,没法将这个key同步到其他BC节点,而重新投票后B节点变成主节点,这时第二个线程进来也拿到了锁,拿到锁后又为所欲为…
- 第3点中提到业务处理时间过长自动解锁的问题;我们需要自己编写自动续期的逻辑;
分布式锁 redisson 可以帮我们解决以上的问题;
分布式锁 redisson 的使用
1、引入依赖
<!-- 以后使用Redisson作为所有分布式锁,分布式框架等功能框架 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2、配置类
@Configuration
public class MyRedissonConfig {
/**
* 所有对Redisson的使用都是通过RedissonClient
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson(){
//1、创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
//2、根据Config创建出RedissonClient实例
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
3、测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redisson;
@Test
public void redissonTest() {
System.out.println(redisson);
}
}
//打印
org.redisson.Redisson@30dd00b6
redission 自动续期,看门狗原理
模拟业务超时:
@GetMapping(value = "/hello")
@ResponseBody
public String hello() {
//1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock lock = redisson.getLock("my-lock");
//2、加锁 默认加锁时间30s
lock.lock();
try {
System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3、解锁
System.out.println("释放锁..." + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
lock.lock();
是阻塞式等待的,默认加锁时间是30s;如果业务超长,运行期间会自动续期到30s。不用担心业务时间长,锁自动过期被删掉;加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题;
也可以自己指定解锁时间lock.lock(10,TimeUnit.SECONDS)
,10秒钟自动解锁,自己指定解锁时间redis不会自动续期;
指定解锁时间带来的问题:如果业务执行的时间超过指定时间,redis会自动解锁;当前业务执行完后又要解锁,可能会解锁到另一条线程加的锁,所以自己指定的解锁时间一定大于业务执行的时间!
看门狗原理
1、如果我们指定了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们制定的时间,不会自动续期;
2、如果我们未指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000
【看门狗默认时间】
只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动再续成30秒;
自动续期时间:internalLockLeaseTime 【看门狗时间 30s】 / 3, 10s