当前位置: 首页 > article >正文

redis+mysql数据一致性+缓存穿透解决方案

在分布式事务中我们知道有cap定理,即 我们保证高可用的情况下,必然要牺牲一些一致性,在保证强一致性的情况下,必然会牺牲一些可用性。而我们redis+mysql数据一致性的使用策略就是在我们保证可用性的情况下尽量保证数据的一致性。想要达到强一致性,不加锁,只用 一些缓存策略那必然不是不可能的

1.一般 查询的业务情况

我们常用的 情况有  对一些热点的数据,或者频繁的查询的数据,如果频繁访问数据库,必然会对数据库造成很大的压力,为了减缓这种压力,我们 用redis做缓存,数据库做底    我们的查询数据的情况一般如下

这样看似好像并没有什么问题,但是当我们想象这样一个业务场景

现在 我要对 该数据 A进行 修改,那么我们修改数据A之后,必然会要修改缓存 和数据库中的数据

那么问题来了,高并发的情况下,在修改数据的时候,如果来了一个查询操作,在这之间会怎么样呢?我们是先修改缓存数据,还是先修改数据库的数据呢?

我们要么先删除缓存 再修改数据库 ,要么先修改数据库再删除缓存,但是这两种情况都会出现 redis 和数据库 数据不一致的情况 

2.删除 缓存 +修改数据库

3.先修改数据库 再删除缓存

 所以综上所述  我们一般会采用 先修改数据库 然后再删除缓存,这也是 大家 选择的的比较多的方案

4.数据一致性兜底解决方案

先写库再删缓存”方案结合事务控制,能彻底保证缓存和数据库的一致性,但会极大程度损耗性能。而且对于业务操作来说,执行业务逻辑、更新库都没报错,偏偏走到最后删缓存时出错,因此需要将整个事务回滚,这是极不公平的  

 所以我们对删除缓存 进行一系列的 处理   

1. try catch  捕捉到 缓存删除失败,在catch中再次删除缓存,也就是 失败的话 重试多次

2. 延时双删  

1.删除缓存

2. 修改数据库

3.休眠 一段时间

4.再次删除缓存

3.异步处理  

这也是比较推荐的方案 

我们先 修改数据库,修改之后,使用mq 发送消息,然后由mq进行对缓存的删除,这样对业务代码侵入也比较低

5.缓存穿透解决

我们知道什么是缓存穿透,就是 请求 一个不存在的数据,然后 由于数据不存在,就不会加入到缓存,会一直访问数据库,这时候会对数据库造成非常大的压力  解决方案一般有两种

 布隆过滤器   和返回null 值  我们  这里只实现返回null值

 当我们 数据 在数据库查不到的时候,我们缓存一个空数据,为了防止内存浪费,我们给该空数据设置一个过期时间,这样 无论谁 用空数据 过度访问 ,都不会给数据库造成太大压力

我们以根据 商品 id 查询数据为例子,同时也对应我们 标题1 的业务实现

 

  @Override
    public Result getByShopId(Long id) {
        //查缓存
        Map entriesMap = redisTemplate.opsForHash().entries(RedisConstants.CACHE_SHOP_KEY + id);

        if(StrUtil.isNotBlank((CharSequence) entriesMap.get("nullId"))){
            log.info(entriesMap.get("nullId").toString());
            log.info("店铺信息不存在");
            return Result.fail("店铺信息不存在");
        }
        // 有的话返回
        if(!entriesMap.isEmpty()){
            Shop shop = BeanUtil.fillBeanWithMap(entriesMap, new Shop(), false);
            log.info("缓存查询到了");
            return  Result.ok(shop);
        }
        //没有的话查数据库
        Shop shopById = query().eq("id", id).one();
        if(BeanUtil.isNotEmpty(shopById)){
            //数据库有的话 添加缓存并且返回
            Map<String, Object> stringObjectMap = BeanUtil.beanToMap(shopById);
            redisTemplate.opsForHash().putAll(RedisConstants.CACHE_SHOP_KEY + id,stringObjectMap);
            // 设置过期时间 防止内存 占满
            redisTemplate.expire(RedisConstants.CACHE_SHOP_KEY+id,RedisConstants.CACHE_SHOP_TTL,TimeUnit.MINUTES);
            return  Result.ok(shopById);
        }
        //数据库没有 返回false
        //解决缓存穿透   访问不存在的数据 缓存为null值 并且设置过期时间
        redisTemplate.opsForHash().put(RedisConstants.CACHE_SHOP_KEY+id,"nullId","null");
        redisTemplate.expire(RedisConstants.CACHE_SHOP_KEY+id,RedisConstants.CACHE_NULL_TTL, TimeUnit.SECONDS);
        return Result.ok("返回成功");
    }


http://www.kler.cn/news/335511.html

相关文章:

  • Python知识点:如何使用SpaCy进行文本预处理与分析
  • Python知识点:如何使用Multiprocessing进行并行任务管理
  • Java | Leetcode Java题解之第457题环形数组是否存在循环
  • Golang | Leetcode Golang题解之第455题分发饼干
  • L1415 【哈工大_操作系统】CPU调度策略一个实际的schedule函数
  • [Offsec Lab] ICMP Monitorr-RCE+hping3权限提升
  • Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
  • 4款专业电脑数据恢复软件,帮你保障数据安全。
  • CUDA与TensorRT学习四:模型部署基础知识、模型部署的几大误区、模型量化、模型剪枝、层融合
  • Oracle RAC中停止has、crs、cluster的区别
  • WooCommerce与wordpress是什么关系
  • 遥感影像-实例分割数据集:iSAID 从切图到YOLO格式数据集制作详细介绍
  • 字段临时缓存包装器
  • Qt/C++开源控件 自定义雷达控件
  • 计算机取证
  • win用户数据保存路径更改
  • 银河麒麟桌面操作系统修改默认Shell为Bash
  • linux下sudo执行的程序会有一个额外的进程的问题
  • 螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习01(环境准备)
  • 【笔记】数据结构