一、文档概述
本文档详细阐述缓存穿透的核心定义、本质特征、典型场景,分析其对系统的主要危害,并提供从基础到高级的完整解决方案,同时区分缓存穿透与其他易混淆缓存问题,为高并发系统缓存设计与问题排查提供参考依据。
二、缓存穿透核心定义与本质
2.1 核心定义
缓存穿透是指请求查询的数据既不在缓存(如Redis)中,也不在底层数据库(如MySQL)中,导致所有请求均绕过缓存层,直接穿透至数据库的现象。作为高并发系统中最常见的缓存异常问题之一,其极易被恶意利用,进而压垮数据库服务。
2.2 本质特征
缓存穿透的本质是缓存的"被动加载"机制失效。正常情况下,缓存会存储数据库中已存在的数据,后续请求可直接从缓存获取;而当请求的数据不存在时,缓存无法被加载,导致每次该类请求都必须直接访问数据库,缓存层形同虚设,即"查无此物,次次打库"。
2.3 典型应用场景
-
恶意攻击:黑客批量发送查询不存在数据的请求,如使用非法ID(id=-1、id=99999999等),试图通过大量无效请求耗尽数据库资源。
-
业务漏洞:前端接口或业务层未做请求参数校验,允许非法、无效参数进入后续链路,触发对不存在数据的查询。
-
数据操作遗漏:数据已从数据库中删除,但未同步清理对应的缓存键,导致后续针对该键的请求持续穿透至数据库,查询结果始终为空。
三、缓存穿透的主要危害
-
数据库压力骤增:大量无效查询请求直接命中数据库,会快速耗尽数据库的CPU、IO、连接池等核心资源,导致数据库处理能力下降。
-
系统服务雪崩:当数据库因压力过大出现响应变慢、卡顿甚至宕机时,会导致依赖该数据库的所有业务服务不可用,进而引发整个系统的雪崩效应。
-
正常业务受影响:数据库资源被无效请求占用后,合法的业务请求会因无法获取数据库资源而出现超时、失败等问题,影响用户体验与业务正常运转。
四、缓存穿透解决方案(从基础到高级)
4.1 基础方案:请求参数校验(源头拦截)
该方案是成本最低、最易实现的基础防护手段,核心是从请求入口拦截无效请求,避免其进入缓存与数据库链路。
-
实施做法:在API网关、负载均衡器或业务入口层,对请求参数进行严格的合法性校验,明确参数的有效范围与格式。
-
示例说明:针对用户ID查询请求,校验ID必须为正整数,且在系统预设的有效ID范围内;过滤掉id=-1、字符串类型ID等明显非法的请求。
-
方案优势:实现简单、无额外资源消耗,可直接从源头拦截大部分明显的无效请求,减轻后续链路压力。
-
方案局限:仅能拦截格式非法、范围异常的请求,无法拦截"格式合法但数据不存在"的请求(如合法ID但对应用户已删除)。
4.2 常用方案:缓存空值(Null Caching)
当数据库查询结果为空时,仍将该请求的键写入缓存,并用特定标记表示空值,同时设置较短的过期时间(TTL),避免空值缓存长期占用资源,实现对重复无效请求的拦截。
-
核心逻辑:请求到达后,先查询缓存;若缓存未命中,查询数据库;若数据库返回空,将该键与空值标记写入缓存并设置短TTL;后续相同请求可直接从缓存获取空值,无需访问数据库。
-
伪代码示例(基于Redis):
key = "user:" + user_id ``value = redis.get(key) ``# 缓存命中(包括空值标记),直接返回 ``if value is not None: `` return None if value == "NULL_MARKER" else value ``# 缓存未命中,查询数据库 ``user = db.query_user(user_id) ``if user is None: `` # 缓存空值,设置60秒过期时间(可根据业务调整) `` redis.setex(key, 60, "NULL_MARKER") `` return None ``else: `` # 缓存有效数据,设置1小时过期时间 `` redis.setex(key, 3600, user) `` return user -
方案优势:实现难度低,可有效拦截重复的无效请求,大幅减少数据库的无效查询压力。
-
方案局限:会占用一定的缓存空间,且核心缺陷是无法解决"攻击者使用海量不同非法键"的攻击场景------此类攻击会生成大量唯一的无效键,每个键首次查询都会穿透至数据库,后续虽会缓存空值,但海量不同的空值缓存仍会快速打满缓存空间,导致有效数据的缓存被挤出;此外,需合理设置TTL,避免数据库中新增该键对应的数据后,被缓存中的空值遮挡(即缓存与数据库数据不一致)。
4.3 辅助方案:分布式锁(缓解瞬时压力)
分布式锁并非缓存穿透的根本解决方案,核心作用是缓解"海量不同非法键攻击"导致的数据库瞬时压力,避免数据库被突发流量直接压垮,需配合其他方案使用。
-
核心逻辑:通过分布式锁控制同一时刻访问数据库的请求数量(实现限流),针对相同规则的无效请求(如用户ID类、商品ID类非法请求),同一时间仅允许一个请求穿透至数据库查询空值并缓存,其余同类请求等待锁释放后,直接获取缓存中的空值,从而平抑数据库的瞬时峰值压力。
-
实施示例:针对用户ID查询请求,可根据ID的哈希值分段设置分布式锁(如按哈希值取模分10段),每段对应一把锁,同一时间段内每段仅允许一个无效ID请求访问数据库,避免单把锁成为瓶颈。
-
方案优势:部署灵活,可快速缓解数据库瞬时压力,避免因突发攻击导致数据库宕机;无需大幅改造现有架构,适配性强。
-
注意事项: 1. 分布式锁仅为"缓解手段",无法从根本上阻止海量不同非法键的穿透,必须与布隆过滤器配合,才能实现全面防护; 2. 需优化锁的粒度与过期时间:锁粒度过粗会影响正常请求,过细则会增加锁管理开销;过期时间过短会导致锁频繁释放重建,过长则可能出现死锁(需配合看门狗机制);其中,若要在使用分布式锁的同时提高并发程度,核心是让锁粒度变细------避免使用全局单把锁,而是根据请求参数的规则分段拆分锁,使不同分段的请求可并行执行,互不阻塞; 3. 选择高可靠的分布式锁实现(如Redis Redlock、ZooKeeper分布式锁),避免锁本身出现故障,引发新的系统瓶颈; 4. 锁粒度细化具体实现:可基于请求参数的哈希值进行分段,例如针对用户ID类请求,将ID进行哈希取模(如取模10、20),每一个模值对应一把独立的分布式锁;此时,不同模值的无效ID请求可分别获取对应分段的锁,并行访问数据库(每段同一时间仅允许一个请求),相比全局单锁,并发度可提升至分段数量级,既缓解数据库压力,又减少锁对并发的影响;同时需控制分段数量,避免分段过多导致锁管理开销激增,通常根据系统并发量设置10-30段即可。
4.4 高级方案:布隆过滤器(Bloom Filter)
布隆过滤器是一种空间效率极高的概率型数据结构,核心作用是快速判断一个元素是否在一个集合中。将其部署在缓存层之前,可提前拦截绝大多数不存在的请求,是应对海量无效请求的高级防护手段。
-
核心原理:预先将数据库中所有存在的有效键,通过多个独立的哈希函数映射到一个巨大的二进制数组(Bitmap)中,形成一个"有效键集合"的映射;当请求到达时,先通过布隆过滤器判断该键是否在有效集合中。
-
执行流程: 1. 请求到达系统入口,先查询布隆过滤器; 2. 若布隆过滤器判定该键"不存在",则直接返回空结果,无需访问缓存与数据库; 3. 若布隆过滤器判定该键"可能存在"(存在极低误判率),则进入正常链路,查询缓存与数据库。
-
核心特性:布隆过滤器具有"不存在则一定不存在,存在则不一定存在"的特点,误判率可通过调整哈希函数数量与二进制数组大小进行优化。
-
方案优势:内存占用极小(相比缓存所有有效键),查询速度极快(时间复杂度为O(1)),适合海量数据场景与恶意攻击防护,可拦截绝大多数不存在的请求。
-
方案局限:实现复杂度高于前两种方案,需调优哈希函数数量与数组大小;不支持删除操作,当数据库中的有效键被删除时,无法同步从布隆过滤器中移除,需重建过滤器;存在极低误判率,需配合缓存空值等兜底方案。
4.5 终极方案:多级防护组合拳
单一方案难以应对所有场景,推荐采用"多级防护"组合,结合三种方案的优势,实现全方位、无死角的缓存穿透防护,是生产环境中最常用的终极解决方案。
-
推荐组合:参数校验 + 布隆过滤器 + 缓存空值 + 分布式锁(按需)
-
执行逻辑: 1. 网关层/入口层执行参数校验,拦截格式非法、范围异常的无效请求,从源头过滤; 2. 业务层通过布隆过滤器,拦截绝大多数"格式合法但数据不存在"的请求,减少后续链路压力; 3. 对于布隆过滤器误判的少量请求,通过缓存空值兜底,避免其反复穿透至数据库; 4. 若系统易遭受突发海量非法键攻击,启用分布式锁缓解数据库瞬时压力,进一步保障数据库稳定。
五、缓存穿透与其他缓存问题的区别(易混淆点)
在高并发系统中,缓存穿透、缓存击穿、缓存雪崩是三种易混淆的缓存异常问题,其核心原因、表现形式与解决方案差异较大,具体区别如下表所示:
|--------|----------------------|-----------------------------------|---------------------|
| 缓存问题类型 | 核心原因 | 典型表现 | 核心解决方案 |
| 缓存穿透 | 请求查询的数据不存在(缓存、数据库均无) | 所有请求均绕过缓存,直接访问数据库,数据库压力剧增 | 参数校验、缓存空值、布隆过滤器 |
| 缓存击穿 | 热点Key(高并发访问的有效Key)过期 | 缓存突然失效,大量高并发请求瞬间穿透至数据库 | 互斥锁、热点Key永不过期、缓存预热 |
| 缓存雪崩 | 大量缓存Key在同一时间集体过期 | 缓存层集体失效,所有请求全部穿透至数据库,导致数据库宕机、系统雪崩 | 设置随机TTL、多级缓存、服务熔断降级 |
六、总结
缓存穿透的核心风险是无效请求持续穿透至数据库,导致系统资源耗尽、服务不可用。在实际系统设计中,应根据业务场景、数据量、并发量选择合适的防护方案:中小规模系统可采用"参数校验+缓存空值"的简单组合;海量数据、高并发或易受恶意攻击的系统,需引入布隆过滤器,搭配分布式锁缓解瞬时压力,通过多级防护实现全方位的缓存穿透防护,保障系统稳定运行。