🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 什么是缓存穿透 缓存穿透是指**查询一个根本不存在的数据,缓存层和存储层都不会命中**,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层,整个过程分为下图3步: * 1)缓存层不命中; * 2)存储层不命中,不将空结果写回缓存; * 3)返回空结果; ![](https://img.kancloud.cn/5a/d6/5ad6acc12ecada32e425b840e4c926bd_1296x1296.png) * **缓存穿透带来的问题:** * ①缓存穿透将导致不存在的数据每次请求都要到存储层去查询,**失去了缓存保护后端存储的意义**; * ②缓存穿透问题可能会**使后端存储负载加大**,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现了缓存穿透问题; * **造成缓存穿透的基本原因有两个:** * 第一,自身业务代码或者数据出现问题; * 第二,一些恶意攻击、爬虫等造成大量空命中; ## 如何发现 1. 业务的响应时间; 2. 业务本身问题; 3. 相关指标:总调用数,缓存层命中数,存储层命中数; ## 解决方法1--缓存空对象 * **概念**:当第2步存储层不命中后,**仍然将空对象保留到缓存层中**,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源; ![](https://img.kancloud.cn/9e/19/9e19cf9ddce2233a37c7b1ef4d9a78d8_347x412.png) **缓存空对象会有两个问题:** * 第一,空值做了缓存,意味着缓存层中存了更多的键,**需要更多的内存空间**(如果是攻击,问题更严重),比较有效的方法是针对这类数据**设置一个较短的过期时间,让其自动剔除** * 第二,**缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。**例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以**利用消息系统或者其他方式清除掉缓存层中的空对象** ``` String get(String key) { // 从缓存中获取数据 String cacheValue = cache.get(key); // 缓存为空 if (StringUtils.isBlank(cacheValue)) { // 从存储中获取 String storageValue = storage.get(key); cache.set(key, storageValue); // 如果存储数据为空,需要设置一个过期时间(300秒) if (storageValue == null) { cache.expire(key, 60 * 5); } return storageValue; } else { //缓存非空 return cacheValue; } } ``` ## 解决方法2--布隆过滤器 * 如下图所示,在访问缓存层和存储层之前,**将存在的key用布隆过滤器提前保存起来,做第一层拦截** * **例如**:一个推荐系统有4亿个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以**将所有推荐数据的用户**做成布隆过滤器。如果布隆过滤器**认为该用户id不 存在,那么就不会访问存储层,在一定程度保护了存储层** ![](https://img.kancloud.cn/5d/a2/5da2948be284f1c596b8882f59b086d2_506x498.png) * 这种方法**适用于数据命中不高、数据相对固定、实时性低(通常是数据集较大)的应用场景**,代码维护较为复杂,但是缓存空间占用少 * **备注信息:** * 关于布隆过滤器的介绍可以参阅:[https://blog.csdn.net/qq\_41453285/article/details/106416470](https://blog.csdn.net/qq_41453285/article/details/106416470) * 可以参考:[https://en.wikipedia.org/wiki/Bloom\_filter](https://en.wikipedia.org/wiki/Bloom_filter)可以利用Redis的Bitmaps实现布隆过滤器,GitHub上已经开源了类似的方案,读者可以进行参考:[https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter](https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter) ## # 两种方案对比 * 前面介绍了缓存穿透问题的两种解决方法(实际上这个问题是一个开放问题,有很多解决方法),下图从适用场景和维护成本两个方面对两种方案进行分析 ![](https://img.kancloud.cn/e7/45/e745c751c54996f50e133f41fc1755b8_821x200.png)