1、双写一致性 业务代码逻辑实现
/**
* Redis 双写一致性 业务逻辑代码
*
* 1、先查询Redis 若Redis存在 则直接返回
* 2、若Redis不存在 则查询mysql 若mysql不存在 返回null (对不存在的key做标记,以防止缓存击穿)
* 3、若mysql存在 则还需将mysql数据回写到Redis
*
* @param id
* @return
*/
@Override
public User selectOneById(Integer id) {
User user = null;
// 1.先查询Redis 若Redis存在 则直接返回
String key = CHECK_USER_KEY + id;
user = (User) redisTemplate.opsForValue().get(key);
// 2.若Redis不存在 则查询mysql 若mysql不存在 返回null
if (user == null){
user = userDao.selectOne(id);
if (user == null) {
return null;
}else {
// 3.若mysql存在 则还需将mysql数据回写到Redis
redisTemplate.opsForValue().setIfAbsent(key,user,7L, TimeUnit.DAYS);
return user;
}
}
return user;
}
2、双写一致性 业务代码逻辑实现(双检加锁)
/**
* Redis 双写一致性 业务逻辑代码
* -------------------------- 大厂 高并发 加强版之双检加锁
*
* @param id
* @return
*/
@Override
public User selectOneByIdPlus(Integer id) {
User user = null;
// 1.先查询Redis 若Redis存在 则直接返回
String key = CHECK_USER_KEY + id;
// 第一次检查
user = (User) redisTemplate.opsForValue().get(key);
// 2.若Redis不存在 则查询mysql 高并发情况下 需要加锁 以防止发生缓存击穿
if (user == null){
synchronized (UserServiceImpl.class){
// 第二次检查 为什么要做第二次检查? 可能有多个线程同时执行了第一次检查,此时user均为null,到第二次检查,是为了防止多个线程同时写入Redis
user = (User) redisTemplate.opsForValue().get(key);
// 第二次从Redis查询仍为null 则去mysql查询
if (user == null){
user = userDao.selectOne(id);
if (user == null) {
return null;
}else {
// 3.若mysql存在 则还需将mysql数据回写到Redis
redisTemplate.opsForValue().setIfAbsent(key,user,7L, TimeUnit.DAYS);
return user;
}
}
}
}
return user;
}
3、Redis缓存一致性方案
在分布式场景下,不可能做到强一致性原则,但可以实现最终一致性,即满足AP原则。
给缓存设置过期时间,定期清理缓存并回写,是保证数据最终一致性的解决方案。
我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,切记,要以mysql的数据库写入库为准。
- 先更新数据库,再更新缓存
更新缓存时,若Redis宕机,则会导致Redis数据丢失。
- 先更新缓存,再更新数据库
不推荐,因为要以mysql业务库兜底。
- 先删除缓存,再更新数据库
在高并发情况下,缓存删除成功但数据库更新中,有并发读请求,并发读请求从数据库读到旧值并回写到redis,导致后续都是从redis读取到旧值。
可以采用延迟双删策略。
例:A线程删除缓存成功后处于数据库更新中,此时B线程读数据,从mysql中读取并写回Redis,在A更新完数据库后,休眠一会,再对缓存进行一个二次删除,即延时双删。
(线程A休眠时间要大于线程B读取数据再写入缓存的时间)
- 先更新数据库,再删除缓存
推荐使用。
4、Redis缓存一致性最终方案