Redis缓存问题| 青训营笔记

112 阅读9分钟

Redis的缓存问题

  1. 说一下什么是缓存雪崩、缓存击穿、缓存穿透,解决方案?

    其中缓存的作用是减轻数据库的压力,提升系统的性能,无论是缓存雪崩、缓存击穿还是缓存穿透都是缓存失效了导致数据库压力过大。

    缓存雪崩 (大规模缓存失效)

    • 什么是缓存雪崩? 缓存雪崩是指在某一个时刻出现大规模的缓存失效的情况,大量的请求直接打在数据库上面,可能会导致数据库宕机,如果这时重启数据库并不能解决根本问题,会再次造成缓存雪崩。

    • 为什么会造成缓存雪崩? 一般来说,造成缓存雪崩主要有两种可能

      • Redis宕机了
      • 很多key采取了相同的过期时间
    • 如何解决缓存雪崩?

      • 为避免Redis宕机造成缓存雪崩,可以搭建Redis集群
      • 尽量不要设置相同的过期时间,例如可以在原有的过期时间加上随机数
      • 服务降级,当流量到达一定的阈值时,就直接返回“系统繁忙”之类的提示,防止过多的请求打在数据库上,这样虽然难用,但至少可以使用,避免直接把数据库搞挂。

    缓存击穿 (热key缓存失效)

    • 什么是缓存击穿? 缓存雪崩是大规模的key失效,而缓存击穿是一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增,这种现象就叫做缓存击穿。

    比较经典的例子是商品秒杀时,大量的用户在抢某个商品时,商品的key突然过期失效了,所有请求都到数据库上了。

    • 如何解决缓存击穿

      • 热点key不设置过期时间,避免key过期失效

        逻辑过期,把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建。

      • 加锁,如果缓存失效的情况,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求,防止数据库宕机,不过这样会导致系统的性能变差。

    缓存穿透 (缓存和数据库中不存在)

    • 什么是缓存穿透 缓存穿透是指用户的请求在缓存和数据库中都不存在,这样每次都会直接请求到数据库上了,比如用户请求的key在Redis中不存在,或者用户恶意伪造大量不存在的key进行请求,都可以绕过缓存,导致数据库压力太大挂掉。

    • 如何解决缓存穿透

      • 参数校验,例如可以对用户id进行校验,直接拦截不合法的用户的请求。
      • 缓存空值,如果某个key在Redis中不存在,在数据库中也不存在,则将把这个Key值保存进Redis,设置value="null"。
      • 布隆过滤器,布隆过滤器可以判断这个key在不在数据库中,特点是如果判断这个key不在数据库中,那么这个key一定不在数据库中,如果判断这个key在数据库中,也不能保证这个key一定在数据库中。就是会有少数的漏网之鱼,造成这种现象的原因是因为布隆过滤器中使用了hash算法,对key进行hash时,不同的key的hash值一定不同,但相同的hash的值不能说明这两个key相同。

    缓存预热:

    • 缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。
    • 如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

    缓存预热解决方案:

    • 数据量不大的时候,工程启动的时候进行加载缓存动作;
    • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
    • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。

    缓存降级:

    • 缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。
    • 在项目实战中通常会将部分热点数据缓存到服务的内存中,这样一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。
  2. 布隆过滤器?

    布隆过滤器可以验证元素一定不存在或者可能存在,不能验证元素一定存在!!!

    布隆过滤器底层使用bit数组存储数据,该数组中的元素默认值是0。

    布隆过滤器第一次初始化的时候,会把数据库中所有已存在的key,经过一系列的无偏hash算法计算,算出每个key的位置,并将该位置的值置为1,为了减少哈希冲突的影响,可以对每个key进行多次hash计算,用户所有的请求都要经过布隆过滤器过滤一遍,如果只有用户请求的key的hash值都是1才可以通过,否则直接拦截。

    优点:空间效率和查询时间都远远超过一般的算法。

    缺点:

    • 布隆过滤器存在误判的情况
    • 布隆过滤器不支持删除,因为布隆过滤器中存的1可能涉及多个key,直接删除可能会影响到其他key
    • 如果数据库中数据更新同步到布隆过滤器时失败,布隆过滤器则会将本来正常的请求拦截住,这是非常致命的

    解决:

    • a)增加二进制位数组的长度。这样经过hash后数据会更加的离散化,出现冲突的概率会大大降低。

      b)增加Hash的次数,变相的增加数据特征,特征越多,冲突的概率越小。

    • 1:开发定时任务,每隔几个小时,自动创建一个新的布隆过滤器数组,替换老的,有点CopyOnWriteArrayList的味道。

      2:布隆过滤器增加一个等长的数组,存储计数器,主要解决冲突问题,每次删除时对应的计数器减一,如果结果为0,更新主数组的二进制值为0。

    应用:

    Redis中的应用:

    • 解决缓存穿透问题:

      1. 方式1:当查询DB中发现某数据不存在时,则将此数据ID存入布隆过滤器,每次查询时先判断是否存在于布隆过滤器,存在则说明数据库无此数据,无需继续查询了。当然此种方式仅能处理同一个ID重复访问的场景。
      2. 方式2:如果攻击者恶意构造了大量不重复的且数据库中不存在的数据呢,此时可将数据库中已有的数据的唯一ID放入布隆过滤器,每次查询时先判断是否存在于布隆过滤器,存在才调用后端系统查询,则可有效过滤恶意攻击。
    • 黑名单校验

    • 解决缓存穿透网页爬虫对URL的去重

    • 避免爬取相同的URL地址反垃圾邮件

    • 从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱

  3. 如何保证缓存与数据库双写时的数据一致性?

    这个问题是没有最优解的,只能数据一致性和性能之间找到一个最适合业务的平衡点。

    首先先来了解下一致性,在分布式系统中,一致性是指多副本问题中的数据一致性。一致性可以分为强一致性、弱一致性和最终一致性。

    强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。强一致性对用户比较友好,但对系统性能影响比较大。 弱一致性:系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。 最终一致性:也是弱一致性的一种特殊形式,系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。 大多数系统都是采用的最终一致性,最终一致性是指系统中所有的副本经过一段时间的异步同步之后,最终能够达到一个一致性的状态,也就是说在数据的一致性上存在一个短暂的延迟。

    如果想保证缓存和数据库的数据一致性,常见的方案主要有以下几种:

    • 先更新数据库,后更新缓存

      更新缓存失败

      在更新缓存之前(已经成功修改数据库),有别的写线程已经完成了更新,最终缓存会和数据库不一致。

    • 先更新缓存,后更新数据库 不推荐采用

    • 先更新数据库,后删除缓存 !!!

      删除缓存失败,每次读到的都是缓存的旧值

      删除重试机制

      • 将重试数据写表,使用分布式定时任务进行重试
      • 将重试的请求写入MQ等消息中间件中
      • 订阅mysql的binlog,在订阅者中,如果发现了更新数据请求,则删除相应的缓存
    • 先删除缓存,后更新数据库

      在修改mysql数据之前先删除缓存,有新的请求到达,查询到了mysql的旧数据,写入缓存,然后更新数据库。

      延迟双删机制

      • 先删除缓存->更新数据库->间隔一段时间,删除缓存