当前位置:网站首页>分布式锁-Redission - 缓存一致性解决
分布式锁-Redission - 缓存一致性解决
2022-08-11 06:54:00 【花棉袄】
🧑 个人主页:花棉袄
本章内容:【Redission - 缓存一致性解决】
版权: 本文由【花棉袄】原创在CSDN首发需要转载请联系博主
️如果文章对你有帮助【关注点赞️收藏】
双写模式
- 两个线程写 最终只有一个线程写成功,后写成功的会把之前写的数据给覆盖,这就会造成脏数据
失效模式
三个连接
一号连接 写数据库 然后删缓存
二号连接 写数据库时网络连接慢,还没有写入成功
三号链接 直接读取数据,读到的是一号连接写入的数据,此时 二号链接写入数据成功并删除了缓存,三号开始更新缓存发现更新的是二号的缓存
解决方案
无论是双写模式还是失效模式,都会到这缓存不一致的问题,即多个实力同时更新会出事,怎么办?
1、如果是用户纯度数据(订单数据、用户数据),这并发几率很小,几乎不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
2、如果是菜单,商品介绍等基础数据,也可以去使用 canal 订阅,binlog 的方式
3、缓存数据 + 过期时间也足够解决大部分业务对缓存的要求
4、通过加锁保证并发读写,写写的时候按照顺序排好队,读读无所谓,所以适合读写锁,(业务不关心脏数据,允许临时脏数据可忽略)
- 总结:
我们能放入缓存的数据本来就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前的最新值即可
我们不应该过度设计,增加系统的复杂性
遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点
配置Redission
- 引入依赖
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.5</version>
</dependency>
- 自定义配置类
@Configuration
public class MyRedissonConfig {
/** * 对 Redisson 的使用都是通过 RedissonClient 对象 * @return * @throws IOException */
@Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
public RedissonClient redisson() throws IOException {
// 1.创建配置
Config config = new Config();
// 集群模式
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
// 2.根据 Config 创建出 RedissonClient 示例。
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}
- 使用分布式锁Redission解决缓存一致性问题
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
// 设置值同时设置过期时间
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
// 加锁成功..执行业务
// 设置过期时间,必须和加锁是同步的,原子的
Map<String, List<Catelog2Vo>> dataFromDb;
try {
dataFromDb = getCatelogJsonFromDb();
} finally {
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//删除锁
Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDb;
} else {
// 加锁失败,重试 synchronized()
// 休眠200ms重试
System.out.println("获取分布式锁失败,等待重试");
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDbWithRedisLock();
}
}
//三级分类
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDb() {
// 0.得到锁之后从缓存中获取
String catelogJSON = redisTemplate.opsForValue().get("categoryJSON");
if (!StringUtils.isEmpty(catelogJSON)) {
// 转换为我们指定的对象
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
System.out.println("获得锁-从缓存中获取");
return result;
}
System.out.println("获得锁-查询了数据库");
//将数据库的多次查询变为一次查询
List<CategoryEntity> selectList = baseMapper.selectList(null);
//1.查询所有一级分类
List<CategoryEntity> categoryOne = getParent_cid(selectList, 0L);
//2.封装数据
Map<String, List<Catelog2Vo>> collect = categoryOne.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//2.1根据一级分类ID查询二级分类
List<CategoryEntity> categoryTwo = getParent_cid(selectList, v.getCatId());
List<Catelog2Vo> catelog2Vos = null;
if (categoryTwo != null) {
//2.2 封装二级分类
catelog2Vos = categoryTwo.stream().map(levelTwo -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, levelTwo.getCatId().toString(), levelTwo.getName().toString());
List<CategoryEntity> categoryThree = getParent_cid(selectList, levelTwo.getCatId());
if (categoryThree != null) {
List<Catelog2Vo.Catelog3Vo> catalog3List = categoryThree.stream().map(levelThree -> {
//2.3 封装三级分类
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(levelTwo.getCatId().toString(), levelThree.getCatId().toString(), levelThree.getName().toString());
return catelog3Vo;
}).collect(Collectors.toList());
//设置三级分类
catelog2Vo.setCatalog3List(catalog3List);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
return collect;
}
边栏推荐
- Redis源码-String:Redis String命令、Redis String存储原理、Redis字符串三种编码类型、Redis String SDS源码解析、Redis String应用场景
- NTT的Another Me技术助力创造歌舞伎演员中村狮童的数字孪生体,将在 “Cho Kabuki 2022 Powered by NTT”舞台剧中首次亮相
- There may be fields that cannot be serialized in the abnormal object of cdc and sqlserver. Is there anyone who can understand it? Help me to answer
- prometheus学习5altermanager
- 详述 MIMIC护理人员信息表(十五)
- 无服务器+域名也能搭建个人博客?真的,而且很快
- Internet phone software or consolidation of attack must be "free" calls security clearance
- 为什么我使用C#操作MySQL进行中文查询失败
- Tf中的平方,多次方,开方计算
- Unity3D 学习路线?
猜你喜欢
随机推荐
伦敦银规则有哪些?
流式结构化数据计算语言的进化与新选择
1091 N-Defensive Number (15 points)
Waldom Electronics宣布成立顾问委员会
js根据当天获取前几天的日期
Unity程序员如何提升自己的能力
2.1-梯度下降
Implementation of FIR filter based on FPGA (5) - FPGA code implementation of parallel structure FIR filter
1051 Multiplication of Complex Numbers (15 points)
1036 跟奥巴马一起编程 (15 分)
Redis源码:Redis源码怎么查看、Redis源码查看顺序、Redis外部数据结构到Redis内部数据结构查看源码顺序
oracle19c不支持实时同步参数,请教一下大佬们有什么好的解决办法吗?
博途PLC 1200/1500PLC ModbusTcp通信梯形图优化汇总(多服务器多从站轮询)
Unity3D 学习路线?
数仓开发知识总结
prometheus学习5altermanager
从苹果、SpaceX等高科技企业的产品发布会看企业产品战略和敏捷开发的关系
Depth (relay supervision)
Tensorflow中使用tf.argmax返回张量沿指定维度最大值的索引
matplotlib