redis和数据库数据不一直问题,缓存常见的三大问题

数据一致性

1 思路

  • 查询数据的时候,如果缓存未命中,则查询数据库,将数据写入缓存设置超时时间
  • 修改数据时,先修改数据库,在删除缓存。

2 代码实现

  • 修改更新方法,添加超时时间
 @Override
    public Result queryById(Long id) {
        //1 redis中查询商户缓存
        String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);
        //2 判断是否存在
        if(StrUtil.isNotBlank(shopJson)){
            //3存在直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //4 不存在根据id去数据库查询
        Shop shop = this.getById(id);
        //5 数据库也不存在,返回错误
        if(shop==null){
            return Result.fail("店铺不存在");
        }
        //6 存在则写入redis中
       redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);
        //7 返回
        return Result.ok(shop);
    }
  • 修改ShopController
  @PutMapping
    public Result updateShop(@RequestBody Shop shop) {
        // 写入数据库

        //shopService.updateById(shop);
        //return Result.ok();
        return  shopService.update(shop);
    }
  • 修改service代码 延时双删策略
  @Override
    public Result update(Shop shop) {

        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不存在");
        }
         // 删除缓存
        redisTemplate.delete("cache.shop:" + id);
        // 更新数据库
        updateById(shop);
        Thread.sleep(800);
        // 删除缓存
        redisTemplate.delete("cache.shop:" + id);
        return Result.ok();
    }

3 修改完代码以后,将所有的缓存删除,执行查询操作,多了超时
在这里插入图片描述

4 用postman执行修改方法: localhost:8081/shop

{
  "area":"大关",
  "sold":3035,
  "address":"金华路锦昌文华苑29号",
  "name":"102茶餐厅",
  "x":120.149192,
  "y":30.316078,
  "typeId":1,
  "id":1
}

在这里插入图片描述

执行完成以后,数据库的数据发生改变,查看redis的数据已经删除了。


这样能保证百分之99的数据一致性问题,无法保证完全一致性,这个适合小项目,数据一致性要求不高的地方使用,如果对数据一致性要求高的不建议使用,建议使用数据库和redis数据同步进行的操作,可以上csdn进行搜索查看实现方式。

缓存常见问题

缓存穿透

客户端请求的数据,在数据库和redis中都不存在,这样缓存永远都不会生效,请求最终都到了数据库上。

解决方案:

  • 当在数据库查询的结果也不存在的时候,可以返回null值给redis,并且设置TTL

在这里插入图片描述

  • 布隆过滤器

    在这里插入图片描述

布隆过滤器是一种数据结构,底层是位数组,通过将集合中的元素多次hash得到的结果保存到布隆过滤器中。主要作用就是可以快速判断一个元素是否在集合里面,但是因为算法的原因,也有一定概率的错误。

开发的时候我们一般选择空值值方式。

  • 代码方式实现

    在这里插入图片描述

根据id查询的时候,如果信息不存在,则要将空值写入redis,并设置空值过期时间

@Override
    public Result queryById(Long id) {
        //1 redis中查询商户缓存
        String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);
        //2 判断是否存在
        if(StrUtil.isNotBlank(shopJson)){
            //3存在直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        if(shopJson!=null){
            return Result.fail("店铺不存在");
        }
        //4 不存在根据id去数据库查询
        Shop shop = this.getById(id);
        //5 数据库也不存在,返回错误
        if(shop==null){
            // 空值写入redis中
            redisTemplate.opsForValue().set("cache.shop:" + id,"",3, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        //6 存在则写入redis中
       redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);
        //7 返回
        return Result.ok(shop);
    }

缓存击穿

也叫热点key问题,一个被高并发访问且业务复杂的key突然失效了,无数的请求瞬间给数据库带来的巨大冲击
在这里插入图片描述
解决方案: 互斥锁 逻辑过期

在这里插入图片描述
在这里插入图片描述
互斥锁思路:
在这里插入图片描述
查询缓存的时候,未命中需要获取锁 代码ShopServiceImpl

@Override
public Result queryById(Long id) {
    //1 redis中查询商户缓存
    String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);
    //2 判断是否存在
    if(StrUtil.isNotBlank(shopJson)){
        //3存在直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    }
    if(shopJson!=null){
        return Result.fail("店铺不存在");
    }
    Shop shop = null;
    String lockKey = "lock.id:" + id;
    try {
        //代码到这里说明没有命中缓存,那么就可以获取锁了
        boolean isLock = tryLock(lockKey);
        // 如果没有拿到锁,则等待一会,递归执行代码
        if(!isLock){
            Thread.sleep(100);
            queryById(id);
        }
        //获取锁成功
        //4 不存在根据id去数据库查询
        shop = this.getById(id);
        //5 数据库也不存在,返回错误
        if(shop==null){
            // 空值写入redis中
            redisTemplate.opsForValue().set("cache.shop:" + id,"",3, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        //6 存在则写入redis中
        redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        unlock(lockKey);
    }
    //7 返回
    return Result.ok(shop);
}

    // 获取锁
private boolean tryLock(String key){
    Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
}
//释放锁
private void unlock(String key){
    redisTemplate.delete(key);
}

缓存雪崩

同一时间段内,大量的缓存key失效或者redis宕机,到时大量的请求到达数据库,带来巨大的压力。
在这里插入图片描述
解决方案

  • 给key设置随机的TTL(有效时间)
  • 集群方案防止宕机不可用

相关推荐

  1. 数据库Redis数据一致问题

    2024-04-04 15:12:02       33 阅读
  2. 一些常见Redis问题答案

    2024-04-04 15:12:02       45 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-04 15:12:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-04 15:12:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-04 15:12:02       87 阅读
  4. Python语言-面向对象

    2024-04-04 15:12:02       96 阅读

热门阅读

  1. 面试前端八股文十问十答第七期

    2024-04-04 15:12:02       32 阅读
  2. 设计模式之职责链模式(下)

    2024-04-04 15:12:02       41 阅读
  3. WPS二次开发系列:WPS SDK初始化

    2024-04-04 15:12:02       43 阅读
  4. HTML中js简单实现石头剪刀布游戏

    2024-04-04 15:12:02       37 阅读
  5. Husky使用简明教程

    2024-04-04 15:12:02       40 阅读
  6. python将visio转换为 PDF 文件

    2024-04-04 15:12:02       32 阅读
  7. 小于电商开放平台-订单线下发货

    2024-04-04 15:12:02       35 阅读
  8. MySQL数据库下载及安装教程(Windows、Linux)

    2024-04-04 15:12:02       40 阅读
  9. 225.队列实现栈

    2024-04-04 15:12:02       31 阅读
  10. Day19.

    2024-04-04 15:12:02       38 阅读