1.引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.4</version>
</dependency>
2.配置reids
redis官方文档:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
写一个配置类,注入RedissonClient
@Configuration
public class MyRedissonConfig {
/**
* 所有对Redisson 的使用都是通过RedissonClient对象
* @return
* @throws IOException
*/
@Bean(destroyMethod="shutdown")
RedissonClient redisson() throws IOException {
Config config = new Config();
//集群模式
// config.useClusterServers()
// .addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
//单节点模式
config.useSingleServer().setAddress("redis://192.168.56.01:6379");//虚拟机redis地址
return Redisson.create(config);
}
}
3.可重入锁
可重入锁简单理解: 例如方法A执行需要①锁,方法B执行也需要①锁,如果是可重入锁,方法A调用方法B,可以直接进行调用,执行完AB方法后释放锁。如果是不可重入锁,执行方法B需要等待方法A执行完释放锁,而方法A调用方法B,没有执行完,不会释放锁,形成死锁。redisson的锁是可重入锁。
redisson锁,测试程序:
@Autowired
RedissonClient redisson;
@ResponseBody
@GetMapping("/hello")
public String hello(){
//1.获取一把锁,只要锁的名字一样就是同一把锁
RLock lock = redisson.getLock("my-lock");
//2.加锁
//lock.lock();//阻塞式等待,默认加的锁都是30s的时间。
// 2.1.锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不同担心业务时间长,锁自动过期
// 2.2 加锁的业务只要运行完成就不会给当前锁续期,即使不手动解锁,锁默认再30s以后会自行删除
//最佳实践
lock.lock(30,TimeUnit.SECONDS); //省掉整个续期操作,手动解锁
try{
System.out.println("加锁成功执行业务..." + Thread.currentThread().getId());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3.解锁, 加锁加锁代码未执行,
System.out.println("释放锁..." + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
看门狗机制
lock.lock(); //带自动续期
lock.lock(10,TimeUnit.SECONDS);//不带自动续期
1.如果我们未指定超时时间,就会使用30*1000【LockWatchdogTimeout看门狗的默认事件】,只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期一次【看门狗时间 / 3】
读写锁
并发读,互不影响,并发读写,先写完才读
保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁
读 + 读 : 相当于无锁
读 + 写 :有读锁,写需要等待
写 + 读: 等待写锁释放
写 + 写:阻塞方式
有写的存在,都必须等待
信号量
可以用作分布式限流
获取信号量,获取一个值,信号量-1,可以阻塞式,也可以直接try,获取不到返回false
释放一个车位,信号量+1
闭锁
放假锁门
5个班全部走完可以锁大门
等待其他全部完成【计数减完】,才能执行自己要干的事
数据缓存一致性
双写模式
数据更新,写数据库,然后写缓存
读到的数据和最新的数据有延迟:最终一致性
由于卡顿等原因,导致写缓存2在最前,写缓存1在后面,就出现了不一致
脏数据问题:
这是暂时性的脏数据问题,在数据稳定后,缓存过期后,又能得到正确的数据
失效模式
数据更新,删除缓存
这也有脏数据问题
延迟双删可以降低脏数据几率
解决方案
- 如果是用户维度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
- 如果是菜单,商品介绍,等基础数据,也可以使用canal订阅binlog的方式
- 通过加锁保证并发读写,写写的时候排好队,读读无所谓。适合使用读写锁
总结:实时性、一致性高的不用缓存
iyatmall系统的一致性解决方案:
- 缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
- 读写数据的时候,加上分布式的读写锁
最终实现: 整合SpringCache简化开发