Redisson文档
:::info
redisson作为分布式锁等功能的框架,内部的所有锁机制,都是原子操作。看门狗机制保证不会出现死锁的情况
:::
1、引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2、配置redisson
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson(){
// 1、创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
// 根据Config创建出RedissonClient
return Redisson.create(config);
}
}
3、lock锁机制
- 获取一把锁,只需要锁的名字一样就是同一把锁
RLock myLock = redisson.getLock("my_lock");
- 加锁
myLock.lock(); 阻塞式等待
1)、锁的自动续期,如果业务时间超长,运行时间自动给锁续上新的30秒。不用担心锁过期之后自动删除
2)、加锁的业务只要运行完,就不会给当前锁续期,即使不手动释放锁,也会在默认的30秒内自动删除
myLock.lock(10 , TimeUtil.SECONDS); 10秒之后自动过期,自动解锁时间一定要大于业务执行时间
- 问题:lock.lock(10,TimeUtil.SECONDS); 在锁时间到了之后,不会自动续期
1)如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时时间就是我们指定的时间
2)如果我们未指定超时时间,就使用默认的 30*1000【LockWatchdogTimeout 看门狗的默认时间】;
只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】
internalLockLeaseTime【看门狗时间】 / 3
:::info
推荐使用myLock.lock(10 , TimeUtil.SECONDS) 自定义锁失效时间,之后手动解锁 myLock.unlock();
:::
4、读写锁
:::info
读写锁能保证一定能读到最新的数据,修改期间,写锁是排他锁(互斥锁,独享锁),读锁是一个共享锁
:::
@GetMapping("/write")
@ResponseBody
public String writeValue(){
String s = UUID.randomUUID().toString();
// 获取读写锁
RReadWriteLock lock = redisson.getReadWriteLock("wr-lock");
RLock rLock = lock.writeLock();
try {
rLock.lock();
Thread.sleep(30000);
redisTemplate.opsForValue().set("uuid", s);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
@GetMapping("/read")
@ResponseBody
public String readValue(){
// 获取读写锁
RReadWriteLock lock = redisson.getReadWriteLock("wr-lock");
RLock rLock = lock.readLock();
String uuid = "";
try {
rLock.lock();
uuid = redisTemplate.opsForValue().get("uuid");
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return uuid;
}
- 读 + 读 :相当于无锁,并发读只会在redis中记录好当前的读锁,他们都会枷锁成功
- 写 + 读 :等待写锁释放
- _写 + 写 : 阻塞方式,等待写锁释放 _
- 读 + 写 :阻塞方式,有读锁,写锁也会等待
总结:只要有写锁的存在,都必须等待
5、闭锁
:::info
只有所有要执行的业务都执行完之后,才会去执行最终业务
:::
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor(){
RCountDownLatch lockDoor = redisson.getCountDownLatch("lockDoor");
lockDoor.trySetCount(5);
try {
lockDoor.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "锁门了";
}
@GetMapping("/gohome/{id}")
@ResponseBody
public String gohome(@PathVariable("id") Long id){
RCountDownLatch lockDoor = redisson.getCountDownLatch("lockDoor");
try {
lockDoor.countDown();
} catch (Exception e) {
e.printStackTrace();
}
return "放学了"+id;
}
6、信号量
:::info
可以用来做限流操作
:::
@GetMapping("/park")
@ResponseBody
public String park(){
RSemaphore park = redisson.getSemaphore("park");
try {
park.acquire();//获取一个信号
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "停";
}
@GetMapping("/go")
@ResponseBody
public String go(){
RSemaphore park = redisson.getSemaphore("park");
try {
park.release();//获取一个信号
} catch (Exception e) {
e.printStackTrace();
}
return "走";
}
acquire:阻塞时等待
tryAcquire:非阻塞式等待,如果获取到则返回true,否则返回false
7、缓存一致性
- 缓存数据一致性—双写模式
- 缓存数据一致性—失效模式
:::info
缓存数据一致性—解决方案
:::
- 无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?
- 1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加 上过期时间,每隔一段时间触发读的主动更新即可
- 2、如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
- 3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
- 4、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心 脏数据,允许临时脏数据可忽略);
- 总结
- 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保 证每天拿到当前最新数据即可。
- 我们不应该过度设计,增加系统的复杂性
- 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
canal-----解决数据一致性