题目
设计一个支持高并发的商品详情页缓存系统
信息
- 类型:问答
- 难度:⭐⭐
考点
缓存策略选择, 缓存失效处理, 缓存穿透防护, 数据一致性, 高并发优化
快速回答
设计要点:
- 缓存策略:采用懒加载 + 主动刷新组合策略
- 缓存失效:设置阶梯过期时间(基础30分钟 + 随机偏移)
- 防穿透:布隆过滤器拦截无效请求 + 空值缓存
- 一致性:双删策略 + 延迟消息队列
- 高并发:Redis集群分片 + 本地二级缓存
1. 核心设计原理
业务场景:电商平台商品详情页访问QPS 10万+,数据库无法直接承受压力。需要设计缓存系统:
• 降低数据库负载
• 保证数据最终一致性
• 防止恶意请求穿透
2. 缓存策略设计
// 伪代码:缓存获取逻辑
public Product getProduct(String id) {
// 1. 先查本地缓存(如Caffeine)
Product product = localCache.get(id);
if (product != null) return product;
// 2. 查Redis(防止缓存穿透)
String redisKey = "product:" + id;
String json = redis.get(redisKey);
if (json != null) {
if ("NULL".equals(json)) return null; // 空值标识
return parseJson(json);
}
// 3. 使用分布式锁防击穿
if (lock.tryLock(id)) {
try {
// 双重检查
json = redis.get(redisKey);
if (json == null) {
// 4. 查数据库
product = db.query("SELECT * FROM products WHERE id=?");
if (product == null) {
redis.setex(redisKey, 300, "NULL"); // 缓存空值
} else {
redis.setex(redisKey, 1800 + random(600), toJson(product)); // 基础30分钟+随机偏移
localCache.put(id, product); // 写入本地缓存
}
}
} finally {
lock.unlock(id);
}
}
return product;
}3. 缓存失效与更新
双删策略保证一致性:
1. 更新数据库前先删除缓存
2. 执行数据库更新
3. 延迟删除缓存(通过消息队列)
// 更新商品示例
public void updateProduct(Product product) {
// 第一次删除
redis.del("product:" + product.id);
// 更新数据库
db.update(product);
// 发送延迟消息(5秒后二次删除)
messageQueue.sendDelayMsg("delete_cache", product.id, 5000);
}
// 消息消费者处理
void handleDelayMsg(String id) {
redis.del("product:" + id);
}4. 防护异常场景
| 问题 | 解决方案 |
|---|---|
| 缓存穿透 | • 布隆过滤器拦截非法ID • 空值缓存(设置短过期时间) |
| 缓存雪崩 | • 阶梯过期时间(基础值+随机偏移) • 热点数据永不过期+后台刷新 |
| 缓存击穿 | • 分布式锁控制单请求回源 • 逻辑过期时间(实际数据永不过期) |
5. 最佳实践
- 多级缓存:本地缓存(Caffeine/Guava) + 分布式缓存(Redis集群)
- 热点探测:实时监控热点Key,自动推送到本地缓存
- 容量规划:Redis内存使用不超过70%,设置淘汰策略为allkeys-lru
- 监控报警:缓存命中率 < 90% 或 穿透请求 > 100次/分钟时触发告警
6. 常见错误
- ❌ 直接设置永久缓存 → 导致脏数据长期存在
- ❌ 先更新数据库再删缓存 → 可能读到旧数据
- ❌ 单点使用布隆过滤器 → 分布式环境失效
- ❌ 本地缓存无过期时间 → 内存泄漏风险
7. 扩展知识
- 数据分片:商品ID哈希分片到不同Redis集群节点
- 读写分离:读请求走从节点,写请求走主节点
- 持久化策略:AOF每秒刷盘 + RDB每日备份
- 新架构探索:Redis6.0多线程IO / RedisCell限流模块