本文先简要介绍为什么需要使用Redis,以及过期键的删除策略,进而详细介绍Redis雪崩、穿透、击穿的发生场景和解决方案。
文章目录
- 前情提要
- 为什么使用redis?
- Redis 的过期键删除策略
- 数据读取流程
- 三种问题及解决方案
- 1. 缓存雪崩
- 2. 缓存穿透
- 3. 缓存击穿
- 对比总结
前情提要
为什么使用redis?
- 提高性能:缓存査询速度比数据库查询速度快(内存Vs硬盘);
- 提高井发能力:缓存分担了部分请求,支持更高的并发。
Redis 的过期键删除策略
Redis 为管理内存,对设置了过期时间的键采用了以下三种删除策略:
-
定时过期:
- 描述:为每个设置了过期时间的键创建一个定时器,到达过期时间立即清除。
- 优点:对内存很友好,过期数据能及时清除。
- 缺点:需要消耗大量的 CPU 资源来处理定时器,影响缓存响应时间和吞吐量。
-
惰性过期:
- 描述:只有在访问某个键时才判断其是否过期,过期则清除。
- 优点:最大化节省 CPU 资源。
- 缺点:可能会有大量过期键未被访问而占用内存。
-
定期删除:
- 描述:每隔一定时间扫描
expires
字典中的部分键,清除过期键。 - 优点:折中策略,通过调整扫描时间间隔和每次扫描数量,平衡 CPU 和内存资源。
- 描述:每隔一定时间扫描
默认使用惰性过期 (Lazy Expiration)和定期删除 (Periodic Deletion)两者结合的方式,能够在性能和内存使用之间找到一个良好的平衡。
数据读取流程
Redis 的数据读取通常遵循以下步骤:
- 缓存命中:如果数据在缓存中存在,直接从缓存中读取。
- 缓存未命中:如果缓存中没有数据,则查询数据库,获取数据后写入缓存供下次使用。
三种问题及解决方案
- 缓存雪崩:主要解决缓存大规模失效问题,核心在于错峰和高可用。
- 缓存穿透:主要解决请求不存在数据的问题,核心在于布隆过滤器和空值缓存。
- 缓存击穿:主要解决热点数据失效问题,核心在于互斥锁和逻辑过期。
1. 缓存雪崩
定义:在某一时间段内,缓存集中过期或 Redis 服务宕机,导致大量请求直接涌向数据库,可能引发数据库崩溃。
原因:
内存昂贵且有限,所以Redis需要给数据设置过期时间,将过期键数据删除。
- 缓存中的大量数据设置了相同的过期时间,导致集中失效。
- 缓存服务宕机或崩溃,所有缓存失效。
解决方案:
-
键值——缓存失效时间错峰:
- 给缓存的过期时间增加一个随机值,减少同一时间段的缓存同时过期。
SET key value EX 300 + random(60) # 过期时间加随机值
-
事发前——Redis 高可用架构
- 实现 Redis 主从架构或 Redis Cluster,避免单点故障。
- 使用 Sentinel(哨兵)进行故障转移,尽量避免Redis挂掉这种情况发生。
-
事发中——本地缓存和限流
- 在 Redis 宕机时,启用本地缓存(如 Ehcache)。
- 通过限流(如 Hystrix)保护数据库(起码能保证我们的服务还是能正常工作的)。
-
事发后——Redis 持久化
- 开启 RDB 或 AOF 持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
2. 缓存穿透
定义:用户请求的键在缓存和数据库中都不存在,导致每次请求都会直接访问数据库。
为什么说键在数据库中也不存在呢?
因为如果键在数据库中存在,第一次命中,就会写入缓存,下次就不会请求到数据库,不属于缓存穿透
原因:
- 恶意攻击:通过大量请求不存在的键对系统进行攻击。例如 ID 为负数或不存在的用户数据。
- 数据不全:一些正常请求因为数据未命中而直接访问数据库。
解决方案:
-
布隆过滤器:
- 在缓存前引入布隆过滤器,用于判断请求的键是否可能存在:
- 如果布隆过滤器判断不存在,直接返回,无需访问缓存和数据库。
- 如果布隆过滤器判断可能存在,再访问缓存或数据库。
# 添加数据到布隆过滤器 BF.ADD filter "valid_key" # 查询数据是否存在 BF.EXISTS filter "invalid_key" # 返回 false,直接拦截请求
- 在缓存前引入布隆过滤器,用于判断请求的键是否可能存在:
-
缓存空值:
- 当查询结果为空时,将空值存入缓存,并设置较短的过期时间。
SET key "null" EX 60 # 缓存空值 60 秒
这种情况我们一般会将空对象设置一个较短的过期时间(时间太长的话,万一数据库中已经有数据了,但是长时间命中redis发现没有数据,导致用户拿不到数据)。
-
参数校验:
- 对输入的参数进行校验,避免无效请求到达后端。
-
限流防护:
- 对频繁请求不存在键的客户端进行限制,防止恶意攻击。
3. 缓存击穿
定义:当缓存中某个高频访问的热点键过期时,大量请求同时到达数据库,造成数据库压力骤增。
场景:
- 一个热点数据被大量访问,且缓存突然失效,失效后请求全部打到数据库。
解决方案:
-
热点键永不过期:
- 热点键不设置过期时间,直接通过覆盖更新来保持数据一致性。
-
加互斥锁:
- 使用 Redis 或 Zookeeper 的分布式锁,控制只有一个线程能访问数据库并更新缓存,其余线程等待。
SET lock:key "unique_value" NX EX 10 # 设置分布式锁
-
热点键限流:
- 对热点键的请求进行限流,避免短时间内大量请求涌入。
-
热点数据分片:
- 将热点数据分散到不同的 Redis 节点或分布式服务中,减少单点压力。
对比总结
问题 | 定义 | 原因 | 解决方案 |
---|---|---|---|
缓存雪崩 | 大量缓存数据同时失效,导致请求集中打到数据库或服务 | 缓存失效时间一致,缓存服务宕机 | 错峰失效、缓存预热、高可用集群、限流降级 |
缓存穿透 | 请求的键既不在缓存中,也不在数据库中 | 恶意攻击、数据不全 | 布隆过滤器、空值缓存、参数校验、限流防护 |
缓存击穿 | 热点数据缓存失效时,大量请求同时查询该数据 | 热点数据失效,大量请求集中到数据库 | 互斥锁机制、逻辑过期、请求分流、分布式限流 |