1、简介
在开发中,我们只要用到缓存就会涉及缓存和数据库双写,保持数据的一致性,如何解决数据的一致性,以及数据库和缓存如何更新保证数据一致性成为大家主要的问题,本文将从这两个方面分别介绍相应的解决方案。
2、缓存双写一致性
对于缓存双写一致性来说,分为两种情况:1)Redis有数据;2)、Redis无数据。对于Redis有数据来说就是确认缓存中的数据和数据库一致;对于Redis无数据来说,我们在查询到Mysql中的数据后可以回写Redis,保证数据最新。
对于第二种情况,我们可以采用双检加锁策略实现,如下:
public static final String RREDIS_KEY = "user";
public User queryUserById(Integer id)
{
User user = null;
String key = RREDIS_KEY + id;
//1、先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql,
// 第1次查询redis,加锁前
user = (User) redisTemplate.opsForValue().get(key);
if(user == null) {
//2、对于高QPS情况,进来就先加锁,保证一个请求操作,让其他的等待一下,避免击穿mysql
synchronized (UserService.class){
//第2次查询redis,加锁后
user = (User) redisTemplate.opsForValue().get(key);
//3、二次查redis还是null,可以去查mysql了(mysql默认有数据)
if (user == null) {
//4、查询mysql拿数据(mysql默认有数据)
user = userMapper.selectByPrimaryKey(id);
if (user == null) {
return null;
}else{
//5、mysql里面有数据的,需要回写redis,完成数据一致性的同步工作
redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);
}
}
}
}
return user;
}
}
3、数据库和缓存更新策略
数据库和缓存更新策略主要有四点:1)、先更新数据库,再更新缓存;2)、先更新缓存,再更新数据库;3)、先删除缓存,再更新数据库;4)、先更新数据库,再删除缓存。
3.1、先更新数据库,再更新缓存
对于先更新数据库,再更新缓存,会出现两种异常情况:
1)、出现Mysql更新成功,Redis中更新失败,导致数据不一致,Redis里面出现脏数据;
2)、在多线程情况下,会导致不同线程更新不同步,出现错乱导致数据不一致。(不推荐使用)
3.2、先更新缓存,再更新数据库
对于先更新缓存,再更新数据库,会出现两种异常情况:
1)、出现Redis更新成功,Mysql更新失败,导致数据不一致;
2)、在多线程情况下,会导致不同线程更新不同步,出现错乱导致数据不一致。(不推荐使用)
3.3、先删除缓存,再更新数据库
对于先删除缓存,再更新数据库,会出现以下异常情况:
1)、在多线程情况下,线程1将Redis中缓存删除了,Mysql还没有更新,线程2读取不到缓存,就到数据库中读取旧值,并将旧值写回Redis,导致缓存是脏数据,Mysql中是最新的;
解决方式:延时双删策略
核心思想:让更新线程更新的时候进行休眠,让其他线程读取数据库中的旧值,更新到缓存中,当更新线程休眠结束,将缓存删除,就可以保证缓存中和数据库中数据一致。
具体实现如下:
public void update(User user){
Jedis jedis = RedisUtils.getJedis();
try{
// 更新之前删除
jedis.del(key);
userDao.update(user);
try{
TimeUnit.SECONDS.sleep(2);
}catch (Exception ex){
ex.printStackTrace();
}
// 更新之后删除,这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
jedis.del(key);
}catch (Exception ex){
ex.printStackTrace();
}
}
注:这种实现方式有个小缺陷,就是需要更新线程睡会,如何保证吞吐量,通过异步线程的方式,将缓存中的脏数据删除。
3.4、先更新数据库,再删除缓存
对于先更新数据库,再删除缓存,会出现以下异常情况:
1)、当数据库更新成功,缓存删除失败,会导致其他线程读取到的缓存还是旧值,缓存和Mysql中的数据不一致。
解决方案:保证最终一致性
在Mysql更新完数据后,将尝试删除缓存,如果删除失败,将更新的数据写入mq中,进行异步重试操作,保证Redis中的数据和Mysql中的最终一致。
以上各种方式都不能够保证Mysql和Redis的实时一致性,可以使用canal进行监控Mysql更新日志,将更新操作同步到Redis,具体见博文:使用canal实现数据库(MySQL)和缓存(Redis)数据一致_canal 缓存一致性实现-优快云博客
4、总结
在工作中我们经常遇到使用Redis缓存来保证高并发情况下的高吞吐量,因此就会遇到两者库中数据一致性问题,本文详细介绍了在各种情况下如何进行操作保证数据一致性。
本人是一个从小白自学计算机技术,对运维、后端、各种中间件技术、大数据等有一定的学习心得,想获取自学总结资料(pdf版本)或者希望共同学习,关注微信公众号:it自学社团。后台回复相应技术名称/技术点即可获得。(本人学习宗旨:学会了就要免费分享)


本文讨论了在开发中如何处理缓存和数据库双写时的数据一致性问题,包括Redis有数据和无数据两种情况下的解决方案,以及先更新数据库后更新缓存、先更新缓存后更新数据库、先删除缓存再更新数据库等策略的优缺点及解决方法。还提到了canal在监控MySQL更新日志同步到Redis的应用。
2万+

被折叠的 条评论
为什么被折叠?



