缓存空对象的使用

缓存空对象作为一种性能优化策略,可以防止数据库被击穿并减少恶意攻击。当数据库未命中时,将空对象存入缓存,避免后续重复访问。然而,大量分散的无效访问可能导致缓存容量膨胀,因此应谨慎使用。该策略适用于无效数据高频度访问的内部服务应用。
     缓存空对象是一种用空间换其他资源(时间、IO、CPU)的技术,可以有效的防止数据库被击穿,并一度程序上减少恶意攻击(特别是将Cache做到client本地)。
     普通使用缓存的场景是,将请求从数据库中读出数据返回的时候,将读出的正常数据写到缓存。而空缓存的意思是,如果请求在数据库中没有命中,依然在缓存中放入一个空缓存对象,这样的话,以后请求过来时访问缓存就会命中这个空缓存,它也就明白了自己的要查的数据不存在,从而避免访问数据库。
     
普通使用缓存的代码,例:
public class UserManager {
     UserDAO userDAO;
     LocalCache localCache;

     public UserDO getUser(String userNick) {
          UserDO user = (UserDO)localCache.get(userNick);
          if(user == null) {
               user = userDAO.getUser(userNick);
               if(user != null) {
                    localCache.put(userNick,user);
               }
          }
          return user;
     }          
}

使用空缓存对象的示例:
public class NullValueResultDO implements Serializable{
     private static final long serialVersionUID = -6550539547145486005L;
}

public class UserManager {
     UserDAO userDAO;
     LocalCache localCache;

     public UserDO getUser(String userNick) {
          Object object = localCache.get(userNick);
          if(object != null) {
               if(object instanceof NullValueResultDO) {
                    return null;
               }
               return (UserDO)object;
          } else {
               User user = userDAO.getUser(userNick);
               if(user != null) {
                    localCache.put(userNick,user);
               } else {
                    localCache.put(userNick, new NullValueResultDO());
               }
               return user;
          }
     }          
}

     缓存空对象可没想象中的那么尽善尽美,对于大量分散的失效的访问会造成缓存容量的膨胀,这可算是一种内存攻击,即使缓存采用LRU实现,也会挤压其他正常数据的空间,所以对于缓存空对象要慎用。缓存空对象最适用于集中地无效数据高频度访问的状况,这种状况可能最适用于内部服务的应用,这种服务应用的请求无效数据的较少,但频度较高。


<think>嗯,用户问的是Redis的缓存穿透,以及缓存空对象是否属于布隆过滤机制。首先我需要确认缓存穿透的定义,然后解释缓存空对象和布隆过滤器的区别和联系。 缓存穿透指的是查询一个不存在的数据,导致每次请求都绕过缓存直接访问数据库,这可能会压垮数据库。常见的解决办法有两种:缓存空对象和布隆过滤器。用户可能混淆了这两种方法,或者想了解它们之间的关系。 缓存空对象是指在查询数据库未找到数据时,将空结果缓存起来,并设置较短的过期时间,这样后续的请求会命中缓存,避免直接访问数据库。而布隆过滤器是一种概率型数据结构,用来快速判断一个元素是否存在于集合中,可以预先将所有可能存在的数据哈希到布隆过滤器中,查询时如果布隆过滤器认为不存在,就直接返回,避免查询数据库。 用户可能误解缓存空对象是布隆过滤器的一种,但实际上它们是不同的解决方案。我需要明确区分两者的原理、优缺点以及适用场景,并举例说明如何结合使用来增强系统性能。此外,用户可能在实际应用中遇到了相关问题,需要具体的配置示例或代码片段,因此提供实际案例会更有帮助。最后,需要总结两者的区别,并给出最佳实践建议,帮助用户根据具体情况选择合适的方法。</think>--- ### 1. **核心结论** **缓存穿透**和**布隆过滤器**是不同概念,**缓存空对象**与**布隆过滤器**是解决缓存穿透的两种不同方案。两者核心区别如下: - **缓存空对象**:直接缓存空结果,但需设置较短过期时间(如30秒)。 - **布隆过滤器**:通过概率性数据结构提前过滤无效请求,避免缓存层和数据库层同时被穿透。 --- ### 2. **缓存穿透的定义与风险** #### **什么是缓存穿透?** - **场景**:请求查询一个**数据库中不存在的数据**,导致缓存未命中(Cache Miss),请求直接穿透到数据库。 - **风险**: - 高频无效查询压垮数据库。 - 攻击者可能利用此漏洞发起恶意请求(如随机ID探测)。 #### **示例** ```plaintext 请求:查询用户ID=10000的信息(数据库中不存在此ID) 流程:Redis无缓存 → 查询数据库 → 数据库返回空 → 下次请求继续重复此过程 ``` --- ### 3. **缓存空对象方案** #### **原理** - **缓存空结果**:即使数据库返回空值,仍将空结果(如`null`或特殊标记)写入缓存,并设置较短的过期时间(例如5-30秒)。 - **后续请求直接命中缓存**:避免重复穿透到数据库。 #### **实现示例** ```java public User getUserById(String id) { // 1. 先查缓存 User user = redis.get(id); if (user != null) { if (user.isEmptyObject()) { // 判断是否为空对象标记 return null; // 直接返回空,避免查库 } return user; } // 2. 缓存未命中,查数据库 user = db.query("SELECT * FROM users WHERE id = ?", id); if (user == null) { // 缓存空对象,过期时间30秒 redis.setex(id, 30, "empty"); // 使用特殊标记(如"empty") return null; } else { redis.setex(id, 3600, user); // 缓存真实数据 return user; } } ``` #### **优缺点** | 优点 | 缺点 | |------|------| | 实现简单 | 可能缓存大量无效Key,占用内存 | | 对代码侵入性低 | 恶意攻击者可能构造不同Key耗尽内存 | | 短期缓解穿透问题 | 数据一致性需处理(如新用户注册后需清理空标记) | --- ### 4. **布隆过滤器方案** #### **原理** - **预加载有效数据**:将所有**可能存在的数据**的Key哈希到布隆过滤器中。 - **请求过滤**:查询前先通过布隆过滤器判断Key是否存在: - **若不存在**:直接返回空,避免访问缓存和数据库。 - **若存在**:继续查询缓存/数据库。 #### **实现示例** ```java // 初始化布隆过滤器(例如启动时加载数据库所有有效用户ID) BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 1000000, 0.01); List<String> allUserIds = db.queryAllUserIds(); bloomFilter.putAll(allUserIds); public User getUserById(String id) { // 1. 先查布隆过滤器 if (!bloomFilter.mightContain(id)) { return null; // 直接返回,避免后续查询 } // 2. 查缓存 User user = redis.get(id); if (user != null) { return user; } // 3. 查数据库并回填缓存 user = db.query("SELECT * FROM users WHERE id = ?", id); if (user != null) { redis.setex(id, 3600, user); } return user; } ``` #### **优缺点** | 优点 | 缺点 | |------|------| | 内存占用低(基于位数组) | 存在误判率(可能将有效Key误判为不存在) | | 彻底避免无效请求穿透到存储层 | 需提前预热数据,不适合动态频繁更新的场景 | | 适合固定数据集(如商品ID列表) | 无法删除已添加的Key(需结合计数布隆过滤器) | --- ### 5. **两种方案对比** | 维度 | 缓存空对象 | 布隆过滤器 | |------|------------|------------| | **内存占用** | 可能较高(存储大量空值) | 较低(位数组存储) | | **数据一致性** | 需主动清理空标记 | 需定期同步布隆过滤器数据 | | **适用场景** | Key空间有限或动态变化 | Key空间固定或低频更新 | | **防御能力** | 仅缓解短期穿透 | 彻底拦截无效请求 | --- ### 6. **组合使用方案** 实际项目中可结合两种方案: 1. **布隆过滤器**拦截绝大多数无效请求。 2. **缓存空对象**处理布隆过滤器误判的请求(即布隆过滤器认为可能存在但实际不存在的Key)。 3. **定期更新布隆过滤器**以同步最新数据(如每天全量同步一次)。 --- ### 7. **其他优化措施** - **限流熔断**:对高频无效请求的客户端实施限流(如Nginx或Sentinel)。 - **异步预热**:热点数据提前加载到缓存。 - **动态布隆过滤器**:使用可删除的布隆过滤器变种(如Cuckoo Filter)。 --- ### 8. **总结** - **缓存空对象**是“事后处理”,通过存储空值减少穿透。 - **布隆过滤器**是“事前拦截”,通过预判过滤无效请求。 - **选择建议**: - 若数据ID范围有限且变动少(如商品ID),优先用布隆过滤器。 - 若数据动态性强或无法预加载,用缓存空对象+短期过期策略。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值