当前位置:网站首页>分布式锁-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;
}
边栏推荐
- oracle19c does not support real-time synchronization parameters, do you guys have any good solutions?
- 2021-08-11 for循环结合多线程异步查询并收集结果
- 1036 跟奥巴马一起编程 (15 分)
- 动态代理学习
- 技术分享 | 实战演练接口自动化如何处理 Form 请求?
- go-grpc TSL认证 解决 transport: authentication handshake failed: x509 certificate relies on ... ...
- 1002 Write the number (20 points)
- Production and optimization of Unity game leaderboards
- 详述MIMIC 的ICU患者检测时间信息表(十六)
- 1003 I want to pass (20 points)
猜你喜欢
随机推荐
Redis source code: how to view the Redis source code, the order of viewing the Redis source code, the sequence of the source code from the external data structure of Redis to the internal data structu
Serverless + domain name can also build a personal blog? Really, and soon
1051 复数乘法 (15 分)
DDR4内存条电路设计
1003 我要通过 (20 分)
国密规范 SM2 SM3 SM4
tf.cast(), reduce_min(), reduce_max()
详述 MIMIC护理人员信息表(十五)
NFT 的价值从何而来
易观分析联合中小银行联盟发布海南数字经济指数,敬请期待!
【@网络工程师:用好这6款工具,让你的工作效率大翻倍!】
Service的两种启动方式与区别
Redis测试
linux 安装mysql服务报错
prometheus学习5altermanager
【latex异常和错误】Missing $ inserted.<inserted text>You can‘t use \spacefactor in math mode.输出文本要注意特殊字符的转义
matrix multiplication in tf
2022年中国软饮料市场洞察
【LaTex-错误和异常】\verb ended by end of line.原因是因为闭合边界符没有在\verb命令所属行中出现;\verb命令的正确和错误用法、verbatim环境的用法
【LeetCode每日一题】——682.棒球比赛