黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)

目录

    • redis缓存
      • 读写缓存
        • 整体流程
        • 添加店铺读写缓存
        • 添加店铺类型读写缓存
      • 更新缓存
        • 更新缓存策略
        • 更新店铺缓存
      • 缓存问题
        • 缓存穿透
          • 缓存空对象
          • 布隆过滤
          • 在读取店铺中防止缓存穿透
        • 缓存雪崩
        • 缓存击穿
          • 互斥锁
            • 互斥锁的表示
            • 添加店铺查询热点key过期问题
          • 逻辑过期
            • 添加店铺查询热点key过期问题
          • 互斥锁与逻辑删除对比

redis缓存

缓存:数据交换缓冲区,临时存储数据,读写性能高,例如:Cpu缓存
项目使用缓存好处

  • 降低后端负载:如果请求内容缓存中存在(命中缓存),则后端不在重新处理
  • 提高读写效率:降低响应时间 缓存的存储位置性能更高且已经存在的比重新处理速度更快

缓存的成本

  • 数据一致性 :如果源数据被修改,缓存中存放的可能是旧数据,导致数据不一致
  • 代码维护成本高 :添加了缓存,使得项目代码变得复杂,考虑的情况更多
  • 运维成本: 添加了额外的服务器用作缓存
    黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第1张图片
    缓存适合不经常变动,但经常用到的数据

读写缓存

整体流程

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第2张图片

添加店铺读写缓存

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第3张图片修改service实现 ,缓存数据结构采用String 类型 因此要求 将对象转为json

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第4张图片

添加店铺类型读写缓存

使用 缓存的对象的另一种形式 hash 缓存 ,将对象转为map ,并注意对象属性非String的字段 (使用StringRedisTemplate 要求全部为String)
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第5张图片
数据类型选择
本次数据是List类型与 以往不同

  1. 使用String 类型,将整个List集合看做一个vaule,并转为一个json字符串 ,形成一个K_V键值对。
  2. 使用Hash类型,将集合中每一条数据看做 filed(自己生成)和value
  3. 使用List类型,是将集合中的对象看做value。遍历数据,将对象类型转为json,再放入缓存。取出同样将集合的String类型转为对象

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第6张图片
效果
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第7张图片

更新缓存

更新缓存策略

redis中缓存中的数据与数据库中的数据可能更新不及时,存在缓存不一致现象

  • 内存淘汰
    redis 自动完成,当内存不足时,reids随机删除一些数据 。下次查询,缓存中数据不存在又会重新放入缓存
    • 数据一致性:redis随机删除,一致性全靠概率
    • 维护成本:redis自动完成,无需维护
  • 超时剔除
    给键值对添加ttl过期时间,当设置的键值对过期时被redis删除。下次查询,缓存中数据不存在又会重新放入缓存
    • 数据一致性:如果数据库在键值对没有过期时进行修改,redis中仍保存旧数据,会导致短暂的不一致性 。不一致性时间<=TTL时间。可靠性一般
    • 维护成本:只需要在设置键值对时,添加过期时间
  • 主动更新
    每次对数据的修改,都同时对缓存进行修改
    • 数据一致性:绝大数情况下一致
    • 维护成本:每次修改数据库,都要手动编码修改缓存,麻烦

因此:内存淘汰结合超时剔除适用于对数据一致性要求不高,修改不频繁的数据

  • 手动编码,可控性最高
    问题:

    • 数据变动时,删除缓存or更新缓存
      更 新 缓 存 问 题 \color{red}{更新缓存问题} :每次数据库修改都会写入缓存,在修改过程中,如果仍然没有请求访问,那么只有最后一次请求有效,之前对缓存的多次写入导致无效写过多
      因此 ,应该以读缓存时为准,在读数据时写入缓存,所以修改时,只需删除缓存。在请求到达前,无论多少此修改,都只删除一次,请求到来时,写入一次。但小问题是,第一次请求要访问数据库
      黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第8张图片

    • 缓 存 事 务 问 题 \color{red}{缓存事务问题} 在删除缓存时,如何保证数据库与缓存之间的原子性(同时成功或同时失败)
      单体应用:将缓存与数据库使用同一个事务
      分布式系统:利用TCC等分布式事务方案

    • 多 线 程 问 题 \color{red}{多线程问题} 线 先操作缓存还是数据库
      若先删除缓存,可能存在当 把缓存删除后,数据库还未还未更新完,此时由一个请求查找不到缓存,读取了数据库旧值,并将旧值写入缓存,当更新完数据后,只有等待再次查询缓存中旧数据过期才会使用新的数据
      黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第9张图片

    若先操作数据库,在删除缓存:在查询数据缓存未命中时,查询数据库,正准备将数据写入缓存,这时一个线程将数据修改并删除了缓存,而写入缓存的是旧数据。同样只有等待再次查询缓存中旧数据过期才会使用新的数据
    黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第10张图片
    二者都可能导致旧数据新写入缓存(使用超时剔除兜底),但二者概率相差很大
    第一种概率:在更新数据库时,另一个线程完成数据库读取并写入缓存
    第二种概率:缓存未命中 在写入缓存时(时间较写入数据库短很多)这期间另一个线程完成更新数据库并删除缓存

  • 使用第三方服务,自动维护,向上透明,无需关注一致性

  • 单独开辟一个线程,异步更新缓存 。可将多次修改进行压缩,统一修改。效率比较高,但一致性不好

更新店铺缓存

根据id查询店铺,缓存未命中就查询数据库,并将结果写入缓存后返回这一基础上,设置缓存的过期时间(采用超时剔除用来更新出错时兜底)

在根据id修改店铺时,先修改数据库,在将缓存删除

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第11张图片
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第12张图片

缓存问题

缓存穿透

请求的数据缓存中和数据库中都不存在(只有数据库存在才能放入缓存),这样缓存永远不会失效,请求一直打到服务器
如果恶意频繁发送数据库不存在的数据,大量请求穿透缓存直接到达数据库,会搞垮数据库
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第13张图片
两种解决方案

缓存空对象

将缓存和数据库都不存在的数据,将key对应的空值返回并进redis,使得缓存能够命中(尽管返回空),减少数据库压力

由此每次从缓存中取出都要判断是否为空
优点:实现简单、维护方便
缺点

  1. 大量的空值的key占用内存 (设置过期时间缓解)
  2. 数据短期不一致 ,如果缓存的空对象数据库后来有值了,而缓存却仍返回空(将过期时间置短缓解,或新增数据时 添加缓存覆盖)
    黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第14张图片
布隆过滤

在缓存与请求之间,添加一个过滤器。该过滤器具有统计功能,先计算数据库中存在哪些数据,在请求到达缓存之前根据所需数据是否存在进行拦截 。布隆过滤器只知道数据有没有,不知道具体数据,简化存储 (不存在,一定不存在。存在,可能不存在)降低穿透概率

原理是 一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。
根据 :两个哈希值相同但原始值不一定相同(哈希碰撞),但原始值相同哈希值一定相同 。将key转为哈希值,并将值对应bit的位置 置为1,即代表key存在。查询时,将key转为hash值,在去bitMap的对应位置比对,判断是否为1,若为0则代表数据中一定不存在,进行拦截 。 若hash数字过长,可除bitMap设定的最大值(会增大误差),确保所有hash结果都能找到对应的bitMap中bit位。

特点

  • 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在。 (只要存在就一定会将对应bit位为1,但bit位 为1 ,可能是其他hash值相同的key)
  • 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。(因为一个bit位 可能代表多个key)
    可用于拦截 如:缓存穿透;去重;,但存在小概率误差

相关链接

优点

  • 占用内存小
    缺点
  • 实现复杂
  • 存在误判

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第15张图片
增加url复杂度:防止被猜到规律,通过拦截,进入请求
对格式进行校验:在查询数据之前,进行一些非法过滤
加强用户权限:减少非登录用户发起恶意请机会,被拦截器拦截
对用户进行限流:例如 ,某些用户每秒最多发起几次请求,防止突然发送大量恶意请求

在读取店铺中防止缓存穿透

采用缓存空对象方式
相比之前 多了个: 当数据库中也不存在时,添加空值放入缓存 ,并在从缓存中查询时,注意判断数据是否为空
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第16张图片

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第17张图片

整体导图

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第18张图片

缓存雪崩

同一时间,大量请求无法使用缓存(key集体过期,缓存服务器宕机),像雪崩一样冲向数据库
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第19张图片
解决方案
针对大量key过期

  • 将key的过期时间设置不相同,例如在指定过期时间的基础上加小范围随机时间,防止过期时间相同,集体失效,数据库压力过大
    针对缓存服务器宕机
  • redis采用集群方式部署,高可用
  • 添加容灾处理,牺牲部分服务,确保能够运行
  • 给业务添加多级缓存

整体导图
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第20张图片

缓存击穿

热点的key过期,导致一瞬间大量访问该key的请求打到数据库 。该key有两个特征:1. 被大量请求访问 2. 缓存重建业务复杂(在重建长时间里,大量请求透过,多次进行缓存重建)
原有逻辑 如果缓存中不存在,判断是否是缓存穿透处理,从数据数查找,并放入缓存 。这样没有考虑热点key不存在且重建缓存耗时,多个请求都要查询数据库,重建缓存的情况

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第21张图片

解决方式

互斥锁

原有逻辑 如果缓存中不存在,判断是否是缓存穿透处理,从数据数查找,并放入缓存
引入互斥后,多了一步, 当数据缓存不存在时,先去判断是否有线程正在创建缓存(根据互斥锁),若有 则等待 然后继续访问缓存,重复以上步骤,若没有直接访问数据库,放入缓存 这种方式就减少了数据库的查询,避免缓存多次重建

一个请求拿到锁后,独自进行创建缓存。在重建缓存完成之前,其他线程只能等待一会后重复以上步骤

问题:多个请求等待一个请求重构缓存,如果这个缓存构建时间多久,其余也只能等待
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第22张图片

互斥锁的表示

jdk中提供的 synchronizedlock固定流程,只有拿到锁才能执行,其余只能等待。而互斥锁没有拿到锁的不只是等待(等待过后,不是向下执行,还要继续从缓存中获取)
使用redis自定义互斥锁:
利用锁的特征:不存在时才可以获取,一但获取在释放前不可再次获取
所以使用 Setnx key value 建立一个标识作为锁
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第23张图片

添加店铺查询热点key过期问题

流程图

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第24张图片

创建两个工具方法
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第25张图片

将之前解决 单线程缓存以及缓存穿透问题封装成一个方法,并创建解决多线程热点key问题的方法。二者可切换

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第26张图片

逻辑过期

不设置TTL,而是value中标识的时间,过期不是由redis自动决定,使得热点key永不过期,交给业务决定。业务判断过期一边更新缓存,一边仍然使用旧数据。
引入逻辑过期后:请求从缓存中拿到数据,先去判断是否过期,若过期则在判断是否有线程正在重建(锁),若没有开辟一个子进程去访问数据库重建缓存,父线程则直接返回旧数据;若有锁,则继续返回旧数据
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第27张图片

添加店铺查询热点key过期问题

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第28张图片

使用逻辑过期法

  1. 提前在redis写入店铺数据,并设置逻辑过期时间进行数据预热
    已知shop类不含有过期时间属性 ,解决思路:
    • 创建一个类,继承shop类,并在本类中添加过期字段
    • 创建一个类,使用包装shop类
    • 黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第29张图片
      在缓存中添加预热的key
      黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第30张图片

最终效果
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第31张图片

在这里插入图片描述

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第32张图片

最大问题:
数据有不一致情况

互斥锁与逻辑删除对比

黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第33张图片
整体导图
黑马点评项目笔记(二)缓存问题(缓存穿透、缓存雪崩、缓存击穿、添加缓存、修改缓存)_第34张图片

你可能感兴趣的:(笔记,缓存,java,redis,nosql数据库)