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("返回成功");
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三氧化真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值