一、缓存穿透:*“查无此物”的攻击*
** **
**缓存穿透是指客户端请求的**数据在缓存中和数据库中都不存在**,这样缓存永远不会生效,*这些请求都会打到数据库*。
** **
** **
1.1 原因分析
- 恶意请求:黑客暴力扫描不存在的ID(如-1、0等非法值)
- 逻辑缺陷:业务未校验参数有效性,直接透传至数据库
** **
1.2 解决方案
方案一:布隆过滤器(Bloom Filter)
原理:预加载所有可能存在的数据哈希值到布隆过滤器中,查询时先判断数据是否存在。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| BloomFilter<Long> bloomFilter = BloomFilter.create( Funnels.longFunnel(), 1000000, 0.01 );
for (long id : validIds) { bloomFilter.put(id); }
public String getProduct(Long id) { if (!bloomFilter.mightContain(id)) { return "非法ID"; } }
|
****✅ 优点**:内存占用少(1百万数据仅需1MB)
*❌ 缺点*:存在误判率(可配置)
方案二:空值缓存
原理:将查询结果为空的键也存入缓存,设置较短过期时间(如5分钟)。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public String getProduct(Long id) { String key = "product:" + id; String value = redis.get(key); if ("NULL".equals(value)) { return "商品不存在"; } if (value == null) { value = db.query(id); if (value == null) { redis.setex(key, 300, "NULL"); } else { redis.setex(key, 3600, value); } } return value; }
|
✅ 优点:实现简单
❌ 缺点:可能缓存大量无效Key
1.3 适用场景
二、缓存雪崩:*“集体罢工”的灾难*
2.1 原因分析
- 批量过期:大量Key设置相同TTL,同时失效
- Redis宕机:集群故障导致所有请求压到数据库
2.2 解决方案
方案一:随机过期时间
1 2 3 4 5 6 7
| public void setProductCache(Long id, Product product) { int baseTTL = 3600; int randomTTL = baseTTL + new Random().nextInt(600); redis.setex("product:" + id, randomTTL, product.toString()); }
|
✅ 优点:分散压力
❌ 缺点:无法应对Redis宕机
方案二:多级缓存架构
2.3 适用场景
三、缓存击穿:*“顶流明星”的暴击*
3.1 原因分析
- 热点Key失效:高并发访问的Key突然过期
- 无保护机制:所有请求直接穿透到数据库
3.2 解决方案
方案一:分布式锁(Redisson实现)
原理:当缓存失效时,通过分布式锁控制仅一个线程重建缓存。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public String getHotNews(String newsId) { String key = "news:" + newsId; String value = redis.get(key); if (value == null) { RLock lock = redisson.getLock(key + ":lock"); try { if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { value = redis.get(key); if (value == null) { value = db.query(newsId); redis.setex(key, 600, value); } } } finally { lock.unlock(); } } return value; }
|
✅ 优点:强一致性
❌ 缺点:性能损耗约10%
**
**
方案二:逻辑过期时间
原理:缓存永不过期,但存储数据时附加逻辑过期时间。
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Datapublic class CacheWrapper { private String data; private Long expireTime; public String getHotNews(String newsId) { CacheWrapper wrapper = redis.get(newsId); if (wrapper == null) { return loadAndCache(newsId); } if (wrapper.getExpireTime() < System.currentTimeMillis()) { threadPool.submit(() -> loadAndCache(newsId)); } return wrapper.getData(); }
|
✅ 优点:零等待时间
❌ 缺点:数据短暂不一致
3.3 适用场景
四、总结对比表
| 问题类型 |
触发条件 |
核心方案 |
性能影响 |
适用场景 |
| 穿透 |
查询不存在数据 |
布隆过滤器 |
低 |
防恶意攻击 |
| 雪崩 |
批量Key失效/Redis宕机 |
随机TTL+多级缓存 |
中 |
大促活动 |
| 击穿 |
单个热点Key失效 |
分布式锁+逻辑过期 |
高 |
秒杀/顶流事件 |
综合应用