缓存击穿、缓存穿透、缓存雪崩

缓存穿透

缓存穿透是指请求的数据既不在缓存中 ,也不在数据库中,从而导致每次请求都需要直接查询数据库。这种情况会让缓存失去作用,给数据库带来巨大压力。

具体场景

通常情况下,应用程序会先查询缓存(如 Redis)以获取数据。如果数据存在于缓存中,就直接返回结果,减少了对数据库的访问。但是在缓存穿透的情况下,用户请求的某些数据根本不存在,不会被缓存,导致每次请求都必须查询数据库。

例如,用户请求一个 ID 为负数或根本不存在的商品信息,这种数据不可能存在于缓存中,也不会在数据库中有记录。由于缓存中找不到,系统必须每次都访问数据库进行查询,数据库每次都返回空值,且结果不会被缓存。这种频繁查询会导致数据库的负载剧增。

常见原因

  1. 恶意攻击:攻击者故意请求大量不存在的数据,绕过缓存直接冲击数据库。
  2. 用户误操作:用户请求的数据在数据库中确实不存在,导致缓存未能生效。

缓存穿透的最终结果就是,每次请求都穿透了缓存层,直接到达数据库,极大地削弱了缓存的作用,也增加了数据库的负载风险。

举例说明

假设一个系统中有 user_id 为 1 到 100 的用户信息,用户请求 user_id = -1 的数据:

  • 由于 -1 不是合法的 user_id,缓存中没有这个数据。
  • 系统查询数据库,发现数据库中也不存在这个 ID 的记录。
  • 因为查询结果是空的,系统不会缓存这个空结果,导致下一次相同的请求还是直接查询数据库。

这样重复请求无效数据,会对数据库造成很大压力,称之为缓存穿透。

解决方案

Redis 缓存穿透问题的优化方法可以从多个方面进行处理,以下是几种常见的解决方案:

1. 布隆过滤器(Bloom Filter)

  • 原理:在查询 Redis 之前,先通过布隆过滤器来判断请求的数据是否存在。如果布隆过滤器判断该数据不存在,就可以直接返回,而不进行数据库查询,避免了对数据库的无效查询。
  • 优势:布隆过滤器能有效减少对数据库的压力,内存占用较少。
  • 缺点:布隆过滤器有一定的误判率,可能会误判一些存在的数据为不存在。

2. 缓存空值

  • 原理:对于缓存穿透导致的频繁访问数据库的情况,可以将查询结果为空的数据也缓存起来,设置一个较短的过期时间(如几分钟),避免短时间内重复访问同样的数据。
  • 优势:能有效降低缓存穿透的频率。
  • 缺点:需要增加对空值的缓存处理,会消耗一定的缓存空间。

3. 增加接口层防护

  • 原理:通过接口层的参数校验或限制,对非法的请求(如 ID 无效或明显不合理的请求)进行过滤,避免将这些请求传递到数据库或缓存层。
  • 优势:减少无效请求的处理。
  • 缺点:需要根据业务场景设计合理的校验机制。

4. 限流与降级

  • 原理:对短时间内对同一请求的访问进行限流或降级处理,避免缓存和数据库层的过载。可以通过对同一客户端 IP 或者同一请求的频次进行控制。
  • 优势:防止短时间内大量无效请求冲击缓存和数据库。
  • 缺点:需要平衡限流与业务需求,防止误限流。

5. 采用动态黑名单

  • 原理:对于特定的高频无效请求,动态将这些请求的 IP 或参数加入黑名单,在缓存或数据库查询之前进行过滤。
  • 优势:可以防止恶意攻击或频繁的无效请求。
  • 缺点:黑名单机制需要动态调整和维护。

6. 热点数据提前缓存

  • 原理:根据业务特点和数据的访问规律,提前将热点数据缓存起来,减少无效的数据库查询。
  • 优势:提升热点数据的访问速度,避免穿透问题。
  • 缺点:需要提前分析数据的访问规律。

综合来看,布隆过滤器和缓存空值是解决缓存穿透的主要手段,根据具体场景可以结合多种优化策略。

布隆过滤器

布隆过滤器本身并不"查询"数据,而是通过一种概率性的数据结构来判断一个元素是否"可能存在"或"确定不存在"。它是由多个哈希函数和一个位数组(bit array)组成的。

布隆过滤器的工作原理
  1. 数据加入过滤器
    • 当某个数据(例如一个 user_id)被加入布隆过滤器时,布隆过滤器会通过多个哈希函数对该数据进行计算,得到多个哈希值。
    • 每个哈希值对应位数组的某些位置,这些位置被置为 1。随着更多数据的加入,位数组中的 1 的数量会增多。
  2. 数据查询过程
    • 当你查询某个数据时,布隆过滤器会使用相同的哈希函数来计算该数据对应的哈希值,并检查这些哈希值对应的位数组位置是否都为 1。
    • 如果所有对应位置都是 1,则该数据"可能存在"。
    • 如果任意一个位置为 0,则布隆过滤器可以确定该数据不存在
布隆过滤器的来源数据

布隆过滤器不直接从数据库或缓存中查询数据。它的来源数据通常是在系统启动时或在某些定期操作中,将可能存在的数据预先加载到布隆过滤器中。例如:

  • 如果你有一个用户表,系统可以在启动时将所有用户 ID 加入布隆过滤器。
  • 如果有新的数据(如新增用户),也会动态地将这些数据加入布隆过滤器。
布隆过滤器的误判

布隆过滤器可以确定数据"不存在 ",但它对于"可能存在"的判断是有误判的。因为哈希函数和位数组的特性,多个不同的元素可能映射到同一位,从而导致误判。因此:

  • 布隆过滤器可以很快排除不存在的数据,避免无效查询。
  • 但如果布隆过滤器判断数据可能存在,仍然需要进一步查询 Redis 或数据库以确认。
举例

假设你有一个用户系统,布隆过滤器被初始化为存储所有合法的用户 ID:

  1. 添加数据到布隆过滤器
    • 系统中有 user_id 为 1 到 100 的用户信息,启动时这些 ID 都加入布隆过滤器。
  2. 查询数据
    • 用户请求 user_id = 50,布隆过滤器计算哈希值,检查位数组对应的位置。如果都为 1,则说明该 ID 可能存在,需要查询 Redis 或数据库进一步确认。
    • 用户请求 user_id = 200,布隆过滤器通过哈希值计算发现某个位置为 0,可以确定该用户 不存在,直接返回错误或空数据,而不再查询 Redis 或数据库。

因此,布隆过滤器不直接从外部查询数据,而是通过位数组和哈希函数进行存在性判断。

缓存击穿

缓存击穿 (Cache Breakdown),也叫热点缓存失效,是指某个在缓存中非常热点的数据(被频繁访问),突然在缓存中过期失效,导致大量并发请求直接打到数据库的情况。这会导致数据库瞬间承受巨大的访问压力,甚至可能导致系统崩溃。

场景示例
  1. 商品详情查询
    • 电商平台中某些商品 ID(如热销商品)的详情被大量用户频繁查询。系统将这些数据缓存起来以减轻数据库压力。
    • 某个时刻,缓存中的商品详情数据过期,导致大量用户请求同时绕过缓存,直接查询数据库,导致数据库短时间内承受大量并发查询压力,影响系统性能。
  2. 用户信息查询
    • 社交平台的某个用户信息(如明星或热门用户)频繁被访问。由于缓存设置了过期时间,当该缓存失效时,大量请求会同时访问数据库,造成数据库压力过大。

常见原因分析

  1. 缓存过期
    • 热点数据的缓存设置了过期时间,当缓存失效时,新的请求直接打到数据库。如果这个数据是热点数据,并且访问量很大,就会导致大量请求同时查询数据库。
  2. 高并发请求
    • 在缓存失效的瞬间,可能会有大量的用户并发请求该数据,所有这些请求都会直接到数据库,给数据库带来非常大的压力,甚至可能导致数据库宕机。
  3. 缓存设置不当
    • 由于设置的过期时间不合理,热点数据失效时未能及时更新,导致在高并发情况下出现缓存击穿。

解决方案

1. 缓存永不过期
  • 原理:对于非常热点的数据(如访问量很高的商品详情、用户信息等),可以将其缓存设置为永不过期,避免缓存失效带来的大量数据库查询压力。
  • 操作:数据更新时手动刷新缓存,确保缓存始终保持最新。
  • 优点:有效避免缓存失效导致的击穿问题。
  • 缺点:需要手动控制缓存更新,增加开发复杂度。

适用场景:访问频率极高的热点数据,特别是商品详情、用户信息等。

2. 加锁机制(互斥锁)
  • 原理:当某个缓存失效时,多个线程会同时发起数据库查询请求。这时,可以对第一个请求加锁,只有第一个线程去查询数据库并更新缓存,其他线程则等待缓存更新完毕再从缓存读取数据。
  • 操作:使用分布式锁(如 Redis 分布式锁)来保证只有一个线程能够查询数据库,其它线程等待缓存更新完成。
  • 优点:避免大量并发请求同时访问数据库,确保数据库只处理一次查询。
  • 缺点:加锁机制可能影响并发效率,需要合理设计锁粒度。

适用场景:高并发场景下的热点数据查询,避免数据库过载。

3. 提前缓存(缓存预热)
  • 原理:在系统启动或缓存失效前,提前将热点数据加载到缓存中,避免缓存突然失效时产生大量并发请求打到数据库上。
  • 操作:根据历史访问数据,分析出热点数据,并定期或在系统启动时将这些数据加载到缓存中。
  • 优点:确保缓存中始终有热点数据,防止缓存失效时数据库承压。
  • 缺点:需要分析和判断热点数据,适合访问量高的场景。

适用场景:热点数据较为固定,容易提前预估的场景。

4. 缓存重建机制
  • 原理:缓存失效时,在后台异步重建缓存,而前台用户继续读取旧数据。在缓存重建期间,不让用户直接查询数据库,避免同时大量请求击穿缓存。
  • 操作:在缓存过期后,先返回旧缓存数据,然后异步查询数据库,生成新的缓存。用户不会感知到缓存过期的情况。
  • 优点:用户体验较好,同时避免数据库压力激增。
  • 缺点:适合对数据实时性要求不高的场景。

适用场景:数据更新频率较低,但访问频繁的场景。

5. 分布式限流和降级
  • 原理:在缓存失效的情况下,限制访问数据库的并发量,防止大量请求瞬间打到数据库。例如,针对热点数据的访问,限制每秒访问次数。
  • 操作:使用分布式限流工具(如 Redis 限流、Nginx 限流等)限制同一时间段内允许访问的请求数量,超出部分可以直接返回默认值或错误提示。
  • 优点:有效防止数据库过载,确保系统的稳定性。
  • 缺点:可能会影响部分用户的请求体验。

适用场景:在高并发场景下,允许部分请求失败的非关键业务。

总结

  • 缓存击穿是因为某个热点数据在缓存中失效,导致大量并发请求直接访问数据库,从而使数据库承受巨大压力。
  • 常见原因包括缓存设置的过期时间不合理、并发量过高、缓存没有及时更新。
  • 解决方案可以使用缓存永不过期、加锁机制、提前缓存、异步重建缓存和分布式限流等方法,有效避免数据库瞬间高并发的压力,并保持系统稳定性。

缓存雪崩

缓存雪崩是指大量缓存同时失效 ,导致短时间内大量请求直接访问数据库,给数据库带来巨大的压力,甚至可能导致数据库崩溃或系统瘫痪。与缓存击穿不同,缓存雪崩的情况通常涉及大量缓存键同时过期。

场景示例

  1. 大量缓存同时设置相同的过期时间
    • 假设系统中大量缓存键设置了相同的过期时间,比如缓存的商品详情、用户数据等。这些缓存失效的时间点相同,当它们同时失效时,系统在短时间内会产生大量请求打到数据库,导致数据库负载骤增。
  2. 集中失效的热点数据
    • 电商大促销活动期间,多个商品的详情页面被频繁访问。如果这些商品的数据缓存同时设置了短暂的过期时间,突然失效时,所有用户请求都会绕过缓存,直接打到数据库,引发"雪崩"效应。
  3. 缓存服务宕机
    • 如果 Redis 等缓存服务发生宕机或崩溃,所有原本在缓存中的数据都会变得不可用。此时,所有的请求会瞬间转向数据库,导致数据库压力骤增。

常见原因分析

  1. 缓存集中失效
    • 大量缓存数据被设置了相同的过期时间,导致这些缓存数据在某个时间点同时失效,产生大量并发请求直接命中数据库。
  2. 缓存服务宕机
    • Redis 或 Memcached 等缓存服务异常宕机,导致所有请求都无法命中缓存,大量请求直接转向数据库,数据库承受的负载骤增。
  3. 缓存重建压力
    • 当大量缓存失效时,系统需要重新从数据库加载数据并重建缓存。这一过程本身会增加数据库的负载,如果并发量大,数据库可能无法承受。

解决方案

1. 缓存过期时间随机化
  • 原理 :为不同的缓存键设置不同的过期时间 ,引入一定的随机性,避免大量缓存同时失效。
  • 操作 :设置缓存的过期时间时,添加一个随机的时间偏移量。例如:在过期时间基础上,添加 1-5 分钟的随机时间。
  • 优点:分散缓存失效的时间,避免短时间内大量缓存失效。
  • 缺点:需要合理设置随机范围,确保缓存不会过快过期。

适用场景:系统中存在大量不同类型数据需要缓存且过期时间相近的情况。

2. 热点数据预缓存
  • 原理 :针对访问频率高的热点数据,在缓存即将失效前,提前刷新缓存,或者对热点数据设置更长的过期时间,甚至永久不过期。
  • 操作:定期检查系统中哪些数据是热点数据,及时将这些数据提前加载到缓存中,确保缓存中的数据不会大量同时失效。
  • 优点:确保热点数据始终存在于缓存中,降低数据库压力。
  • 缺点:对系统中热点数据的识别与预测有一定要求,适合访问量大的场景。

适用场景:有明确热点数据、访问频率高的场景,如电商平台的商品详情页等。

3. 多级缓存架构
  • 原理 :使用多级缓存策略,避免缓存层宕机时所有请求直接打到数据库。常见的做法是引入本地缓存(如 Guava Cache)或使用 CDN 作为外层缓存,在 Redis 或数据库之前拦截部分请求。
  • 操作:在 Redis 缓存之上引入本地缓存或 CDN 缓存,形成多级缓存架构,分散负载。
  • 优点:多级缓存减少对单一缓存系统的依赖,缓存层宕机时也能减轻数据库的压力。
  • 缺点:增加了系统架构的复杂度,需要管理多级缓存的一致性。

适用场景:高并发系统,如大型网站或分布式应用。

4. 分布式限流
  • 原理:当大量缓存失效时,通过限流机制控制请求流量,避免瞬间大量请求打到数据库,保护数据库的稳定性。
  • 操作:针对缓存失效的请求,使用分布式限流工具(如 Redis 限流、Nginx 限流)来限制单位时间内访问数据库的并发请求数量。超出限制的请求可以返回默认值或错误提示,稍后再处理。
  • 优点:有效缓解缓存失效时数据库的并发压力,保障系统的稳定运行。
  • 缺点:需要合理设置限流策略,可能导致部分用户请求失败或延迟。

适用场景:高并发的场景,尤其是突发流量较大的应用。

5. 缓存重建机制
  • 原理 :当大量缓存失效时,避免所有请求同时去数据库查询数据并重建缓存。可以通过异步重建缓存的方式,在缓存失效时首先返回旧数据,然后异步更新缓存。
  • 操作:在缓存失效后,先返回过期数据给用户,异步更新数据库并重建缓存。
  • 优点:防止数据库短时间内承受高并发访问,有助于提高系统的稳定性。
  • 缺点:适合对数据实时性要求不高的场景,复杂度增加。

适用场景:对数据实时性要求较低但并发量大的业务场景。

6. 缓存服务高可用设计
  • 原理 :设计缓存服务的高可用方案,避免单点故障导致缓存服务宕机。例如使用Redis 主从架构Redis 集群,或搭配持久化方案,保障缓存系统的高可用性。
  • 操作:使用 Redis 哨兵、主从复制等方式,确保 Redis 宕机时从节点能够自动接管,或者使用 Redis Cluster 来分散负载。
  • 优点:提高缓存系统的可靠性,避免缓存宕机导致的雪崩问题。
  • 缺点:增加了系统的部署和维护复杂度。

适用场景:对系统可靠性要求高的业务场景,特别是分布式系统。

总结

  • 缓存雪崩是指大量缓存同时失效,导致短时间内大量请求直接命中数据库,造成数据库压力骤增甚至宕机。
  • 常见原因包括大量缓存设置了相同的过期时间,缓存服务宕机,或者缓存重建机制不合理。
  • 解决方案包括缓存过期时间随机化、热点数据预缓存、多级缓存架构、分布式限流、缓存重建机制、以及缓存服务的高可用设计。这些措施可以有效减轻缓存雪崩对系统的影响,提高系统的稳定性和可靠性。

区别

  • 缓存穿透:数据根本不存在,无论在缓存还是数据库中,系统每次请求都会绕过缓存查询数据库,返回空值。

  • 缓存击穿:热点数据在缓存中失效,导致大量请求在短时间内直接访问数据库,但数据在数据库中是存在的。

  • 缓存雪崩:多个缓存同时失效,导致数据库短时间内承受大量并发查询。

    为什么你觉得缓存雪崩类似于缓存穿透?

    • 相似之处:两者都会导致大量请求直接打到数据库,进而对数据库造成压力甚至导致宕机。在缓存雪崩时,大量请求无法命中缓存,虽然请求的数据是有效的,但会表现得像"穿透"了缓存一样,最终击中数据库。
    • 不同的根本原因
      • 缓存穿透 :是缓存机制无法处理无效的、不存在的数据,也就是说系统没有为这些请求准备缓存。
      • 缓存雪崩 :则是缓存存在并且有效,但由于某种原因失效了,导致原本应该被缓存处理的请求,突然全部转向数据库。

某一时刻,大量新用户访问系统,对mysql和redis造成压力,怎么处理呢?

当大量新用户同时访问系统时,可能会对MySQLRedis 造成很大的压力,尤其是在高并发场景下。为了处理这种情况,以下是一些常见的优化策略,从数据库层面缓存层面架构层面等不同角度出发,帮助系统有效应对高并发流量。

1. 数据库层面的优化(MySQL)

1.1 读写分离
  • 原理 :将数据库的读操作和写操作分开处理,常见方案是使用主从复制,即主数据库负责写入操作,从数据库负责读取操作。这样可以分散对数据库的压力,特别是在读操作繁重时,减轻主库的负担。

  • 操作

    • 设置主从数据库架构,主库负责处理写入请求,从库处理读取请求。
    • 使用中间件(如 MyCat、ShardingSphere)或手动代码层面实现读写分离。

适用场景:新用户注册、登录时,读请求远多于写请求的场景。

1.2 数据库连接池优化
  • 原理:数据库连接池通过复用数据库连接,减少频繁创建和关闭数据库连接的开销。合理配置连接池的大小可以帮助系统应对短时间的并发访问。
  • 操作 :根据系统的并发量,调整连接池的最小连接数最大连接数超时时间,确保连接池能够在高并发时平稳运行。

适用场景:大量并发请求需要频繁访问数据库的情况。

1.3 SQL优化与索引优化
  • 原理:通过优化 SQL 查询语句和添加合适的索引,可以显著提高数据库查询效率,减少查询时间,降低数据库压力。

  • 操作

    • 优化复杂查询,减少不必要的多表关联或子查询。
    • 检查和优化数据库表的索引,确保查询条件匹配合适的索引。

适用场景:新用户注册、登录时涉及的复杂查询操作。

2. 缓存层面的优化(Redis)

2.1 热点数据缓存
  • 原理:将访问频率较高的热点数据提前缓存到 Redis 中,减少对 MySQL 的查询次数。对于新用户访问时,可能会涉及到一些公共资源(如首页内容、注册信息校验等)的请求,尽量将这些数据存储在 Redis 中,减少数据库压力。

  • 操作

    • 分析哪些数据是热点数据,并将这些数据加载到缓存中。
    • 设置合理的过期时间,避免缓存失效导致缓存击穿。

适用场景:对于系统中的公共资源(如静态信息、热门内容)访问,适合使用缓存。

2.2 缓存预热
  • 原理:在高并发流量到来之前,提前将系统中的部分关键数据或热点数据加载到缓存中,防止流量突然增加时直接访问数据库。

  • 操作

    • 系统启动或流量高峰前,通过后台任务提前将相关数据预加载到 Redis 中。

适用场景:可以预判的高并发场景,如大促活动、系统启动等。

2.3 设置合理的缓存过期时间
  • 原理:设置不同数据的缓存过期时间,防止大量缓存同时失效,避免缓存雪崩。
  • 操作 :为不同类型的数据设置不同的过期时间,并引入随机化过期时间机制,减少缓存雪崩风险。

适用场景:新用户访问时涉及的各类数据缓存。

3. 架构层面的优化

3.1 使用消息队列(MQ)削峰填谷
  • 原理:通过消息队列(如 RabbitMQ、Kafka 等),将高并发请求写入队列中,系统可以异步处理,避免大量请求瞬间打到数据库和缓存中。对于用户注册、登录等操作,可以通过队列将请求缓冲,平滑处理高并发流量。

  • 操作

    • 对于非实时性强的请求(如注册、数据写入),可以通过消息队列进行异步处理。
    • 消息队列可以帮助系统应对流量高峰,减缓数据库的压力。

适用场景:用户注册、数据写入操作不要求立即返回的场景。

3.2 限流和降级
  • 原理:在系统高并发时,通过限流策略控制进入系统的请求数量,避免过多请求同时打到数据库或缓存中。对于某些非关键服务或功能,可以通过降级策略,确保核心功能的正常运行。

  • 操作

    • 可以通过 Nginx 或 Redis 实现分布式限流,根据用户访问频率设置限流规则。
    • 实现服务降级,例如在极端情况下,部分功能(如推荐服务、非必要查询)可以返回默认值或提示稍后再试。

适用场景:新用户短时间内大量访问时,通过限流和降级保证系统的稳定性。

3.3 数据分片和分库分表
  • 原理:将数据按某种规则分布到不同的数据库或表中,减少单一数据库的压力,提高系统的并发处理能力。针对新用户的注册、登录请求,可以将用户表按用户 ID 分片,减轻单一表的压力。

  • 操作

    • 可以根据用户 ID 或其他字段对用户表进行分库分表。
    • 使用分库分表中间件(如 ShardingSphere)来管理分片。

适用场景:用户量大,且需要对单一数据库压力进行分散的情况。

4. 前端和用户体验层面优化

4.1 前端页面静态化
  • 原理:将频繁访问的页面(如首页、注册页面)静态化,减少服务器动态生成页面的开销,从而减轻后端数据库和缓存的压力。
  • 操作:通过 CDN 分发静态资源,避免每次都重新渲染动态页面,减少服务器的负担。

适用场景:适用于首页等静态内容较多的页面。

4.2 异步处理和延迟加载
  • 原理:在前端部分请求可以异步加载,避免用户在第一次加载页面时同时发起大量请求。比如,用户注册后不需要立即同步所有数据,可以分批加载。
  • 操作:前端采用异步加载策略,分批请求数据,减少瞬间并发压力。

适用场景:用户注册或登录时的一些非关键数据加载场景。

总结

应对大量新用户访问系统时对 MySQL 和 Redis 的压力,通常采用多层次优化方案:

  • 数据库层面的优化包括读写分离、连接池优化、SQL优化。
  • 缓存层面通过热点数据缓存、预热、缓存时间管理等手段缓解数据库的压力。
  • 架构层面则可通过限流、降级、消息队列等手段来削峰填谷。
  • 前端优化可以通过静态化页面和异步加载来减少瞬时压力。

这些策略的结合可以有效提高系统应对高并发的能力,确保在大量用户访问时保持稳定。

相关推荐
BestandW1shEs1 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师1 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球1 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...1 小时前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民00011 小时前
MySQL的权限管理机制--授权表
数据库
wqq_9922502771 小时前
ssm旅游推荐系统的设计与开发
数据库·旅游
难以触及的高度2 小时前
mysql中between and怎么用
数据库·mysql
Jacky(易小天)2 小时前
MongoDB比较查询操作符中英对照表及实例详解
数据库·mongodb·typescript·比较操作符
Karoku0663 小时前
【企业级分布式系统】ELK优化
运维·服务器·数据库·elk·elasticsearch