在一些情况下,可能 MySQL + Redis 的方案并不妥。
例如在一个高并发接口,我们受到的流量可能并非业务真正的流量。我们需要花费很小的代价来过滤真正高价值的流量,因为接口并发量极高。
这是仅仅依靠Redis缓存空值并不理想,因为内存是昂贵的。
直接打到 MySQL 会造成:
- 数据库连接池耗尽
- 不必要的磁盘 I/O
- 缓存污染(Redis 存满空值)
L1:布隆过滤器
职责:前置过滤,极小代价拦截 99% 的垃圾请求。可以借助Redis内置的来实现。
优势:
- 内存占用极小(100万元素约 1.2MB)
- 判定速度 O(1),几个 hash 操作
- 可承受 1% 的误判(坏处:极少数垃圾请求穿透,好处:不会误杀有效请求)
缺陷:
- 布隆过滤器只能判断 key 是否可能存在**,不能删除元素**
- 新增有效 key 时需要重建(不适合频繁变更的场景)
- 1% 误判率意味着仍有少量垃圾请求穿透
L2: Caffine 本地缓存
职责:JVM 内进程缓存,热数据快速命中,减轻 Redis 压力。
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(100_000) // 最多 10 万条
.expireAfterWrite(5, TimeUnit.MINUTES)
.refreshAfterWrite(3, TimeUnit.MINUTES)
.build();
public Object getWithTwoLevelCache(String key) {
// L2:本地缓存命中(内存访问,<1 微秒)
Object cached = localCache.getIfPresent(key);
if (cached != null) {
return cached;
}
// 未命中,查询 L3
Object value = getFromRedis(key);
if (value != null) {
localCache.put(key, value); // 回源到 L2
}
return value;
}
L3: Redis
职责:分布式共享缓存,一致性视图
public Object getWithFullStack(String key) {
// L1:布隆过滤
if (!bloomFilter.mightContain(key)) {
return null;
}
// L2:本地缓存
Object local = localCache.getIfPresent(key);
if (local != null) {
return local;
}
// L3:Redis(网络调用)
Object value = redis.get(key);
if (value != null) {
localCache.put(key, value);
return value;
}
// 三层都未命中,查数据库
value = queryDatabase(key);
if (value != null) {
// 链路:DB -> Redis -> LocalCache
redis.setex(key, 3600, value);
localCache.put(key, value);
}
return value;
}
```
---
## 完整架构示例
```
请求
↓
┌─────────────────────────────────────┐
│ L1: 布隆过滤器 (Bloom Filter) │ ← 1% 误判,拦截 99% 垃圾
├─────────────────────────────────────┤
│ L2: Caffeine 本地缓存 │ ← 热数据,命中率 70-90%
├─────────────────────────────────────┤
│ L3: Redis 分布式缓存 │ ← 全量数据,命中率 80-95%
├─────────────────────────────────────┤
│ MySQL 主库 │ ← 冷数据 + 写操作
└─────────────────────────────────────┘
