• redis缓存数据和表数据一致性之延时双删策略


    一、什么是 Redis 延迟双删?

    1、延迟双删策略是分布式系统中数据库存储和缓存数据保持一致性的常用策略,但它不是强一致。不管哪种方案,都无法绝对避免Redis存在脏数据的问题,只能减轻这个问题

    2、因为延迟双删策略执行的结果就是把redis中保存的那条数据删除了,以后的查询就都会去查询数据库。经常修改的数据表不适合使用redis缓存

    3、Redis适用的是读频率远远大于改频率的数据表,不适合改频率大于读频率的数据

    二、为什么要使用延迟双删?

    先看下面两种脏数据情况:
    1、情况一:先删除缓存数据,再update更新数据表
    当请求1执行删除缓存数据后,还未来得及更新数据表或更新动作还未完成,此时请求2查询到数据表中仍然是更新之前的数据、并把脏数据写入了Redis缓存

    2、情况二:先update更新数据表,再删除缓存数据
    当请求1执行update更新数据表后,还未来得及删除缓存数据或删除缓存动作还未完成,此时请求2查询到Redis缓存中仍然是旧数据、并返回给前端

    为了避免上述的两种情况数据不一致问题,就需要用到我们介绍的延迟双删策略:先删除缓存数据 》再执行update更新数据表 》最后(延迟N秒)再删除缓存

    三、实现延迟双删,示例:

    1、有实现注释,代码如下

    更新操作代码:

    1. @Service
    2. public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
    3. @Resource
    4. private RedisUtil redisUtil;
    5. @Resource
    6. private ScheduledExecutorService scheduledExecutorService;
    7. /**
    8. *

      保存用户信息

    9. *
    10. * @author hkl
    11. * @date 2022/11/22
    12. */
    13. @Override
    14. @Transactional(rollbackFor = Exception.class)
    15. public void saveUserInfo(UserInfo userInfo) {
    16. if(ObjectUtil.isNull(userInfo)){
    17. return;
    18. }
    19. if(ObjectUtil.isNull(userInfo.getId())){
    20. this.save(userInfo);
    21. //Hash方式 start
    22. redisUtil.putHash(RedisConstants.USER_INFO_HASH, String.valueOf(userInfo.getId()), userInfo);
    23. //Hash方式 end
    24. } else {
    25. //测试Redis延迟双删,Hash方式 start
    26. //1、删除缓存数据
    27. UserInfo getUserInfoResFirst = this.getById(userInfo.getId());
    28. Optional.ofNullable(getUserInfoResFirst).ifPresent(obj -> {
    29. redisUtil.deleteHash(RedisConstants.USER_INFO_HASH, String.valueOf(obj.getId()));
    30. });
    31. //2、更新数据表
    32. this.updateById(userInfo);
    33. //3、延迟2秒后,再删除缓存数据
    34. scheduledExecutorService.schedule(() -> {
    35. Optional.ofNullable(getUserInfoResFirst).ifPresent(obj -> {
    36. redisUtil.deleteHash(RedisConstants.USER_INFO_HASH, String.valueOf(obj.getId()));
    37. });
    38. }, 2, TimeUnit.SECONDS);
    39. //测试Redis延迟双删,Hash方式 end
    40. }
    41. }
    42. }

    查询操作代码:

    1. @ApiOperation(value = "根据用户Id查询用户详情信息")
    2. @ApiImplicitParam(value = "用户Id", name = "userId", required = true)
    3. @GetMapping("/getUserInfoById")
    4. public CommonResult<UserInfo> getUserInfoById(Integer userId) {
    5. //Hash方式 start
    6. Object userInfoObj = redisUtil.getHash(RedisConstants.USER_INFO_HASH, String.valueOf(userId));
    7. if(ObjectUtil.isNotNull(userInfoObj)){
    8. return success(userInfoObj);
    9. }
    10. UserInfo userInfo = userInfoService.getById(userId);
    11. Optional.ofNullable(userInfo).ifPresent(obj -> {
    12. redisUtil.putHash(RedisConstants.USER_INFO_HASH, String.valueOf(userId), obj);
    13. });
    14. //Hash方式 end
    15. return success(userInfo);
    16. }

    说明:
    以上就是实现缓存和数据表一致的延迟双删策略Demo,经过测试可以保持数据一致

    四、小结

    1、使用延迟双删策略是为了保持Redis缓存与数据表一致

    2、第一次删除不再赘述,为了清除缓存中的旧数据

    3、主要是第二次删除,第二次删除为什么要延迟呢?延迟1是为了等待更新数据表动作完成、2是为了等待更新数据表之前查询查到的旧数据写入缓存动作完成,最后再把写入缓存的旧数据删除

    4、延迟双删依然无法保证一致,只能减轻出现脏数据的情况,所以对一致性要求较高的数据尽量不要放入缓存

  • 相关阅读:
    Java实现树形结构和递归查询
    使用mtrace追踪JVM堆外内存泄露
    中兴面试-Java开发
    弘辽科技:做好产品标题,让链接免费流量快速爆起来
    O2OA(翱途)开发平台 V8.2已发布,更安全、更高效、更开放
    VSCode相对问题
    主流开发语言和开发环境介绍
    Istio 入门(六):版本控制
    档案宝档案管理系统在微信小程序上线了!
    c语言练习93:环形链表的约瑟夫问题
  • 原文地址:https://blog.csdn.net/hkl_Forever/article/details/127798965