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

文章目录

数据一致性

1 思路

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

2 代码实现

  • 修改更新方法,添加超时时间
java 复制代码
 @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
java 复制代码
  @PutMapping
    public Result updateShop(@RequestBody Shop shop) {
        // 写入数据库

        //shopService.updateById(shop);
        //return Result.ok();
        return  shopService.update(shop);
    }
  • 修改service代码 延时双删策略
java 复制代码
  @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

json 复制代码
{
  "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,并设置空值过期时间

java 复制代码
@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

java 复制代码
@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(有效时间)
  • 集群方案防止宕机不可用
相关推荐
jakeswang14 分钟前
应用缓存不止是Redis!——亿级流量系统架构设计系列
redis·分布式·后端·缓存
秋难降1 小时前
零基础学SQL(八)——事务
数据库·sql·mysql
Starry_hello world1 小时前
MySql 表的约束
数据库·笔记·mysql·有问必答
RestCloud1 小时前
ETLCloud中的数据转化规则是什么意思?怎么执行
数据库·数据仓库·etl
一个天蝎座 白勺 程序猿2 小时前
Apache IoTDB(4):深度解析时序数据库 IoTDB 在Kubernetes 集群中的部署与实践指南
数据库·深度学习·kubernetes·apache·时序数据库·iotdb
.Shu.2 小时前
Redis zset 渐进式rehash 实现原理、触发条件、执行流程以及数据一致性保障机制【分步源码解析】
数据库·redis·缓存
君不见,青丝成雪2 小时前
大数据技术栈 —— Redis与Kafka
数据库·redis·kafka
悟能不能悟2 小时前
排查Redis数据倾斜引发的性能瓶颈
java·数据库·redis
切糕师学AI2 小时前
.net core web程序如何设置redis预热?
redis·.netcore
DemonAvenger2 小时前
事务管理:ACID特性与隔离级别详解
数据库·mysql·性能优化