一个天天被催的接口
503 咖啡馆的商品详情页,用户点开一杯燕麦拿铁,后端要做五件事:查商品基本信息、查 SKU 与价格、查库存、查店铺、查推荐标签——五次数据库往返串行跑下来,RT 稳定在 3 秒。平时人少还能忍,爆单一来,数据库 CPU 飘红,超时告警一条接一条。
第一反应往往是优化 SQL:加索引、合并查询。有用,但天花板明显——索引把每次查询从 200ms 压到 50ms,五次串行还是几百毫秒,而且 DB 的 QPS 一个没少,容量问题原封不动地留在原地。
// 优化前:五次串行 DB 查询,RT ≈ 3s,全走 DB
Item item = itemMapper.selectById(id); // 200ms
Sku sku = skuMapper.selectByItem(id); // 300ms
Stock stock = stockMapper.selectByItem(id); // 400ms(热点行)
Shop shop = shopMapper.selectById(item.shopId); // 250ms
List<Tag> tags = tagMapper.selectByItem(id); // 350ms
// 优化后:九成请求一次缓存命中,RT ≈ 5ms
ItemDetail d = redis.get("item:detail:" + id);
if (d == null) {
d = loadFromDb(id); // 未命中才回源
redis.set("item:detail:" + id, d, 30, MINUTES);
}读写比决定第一步往哪走
商品详情是典型的读多写少:一天几百万次浏览,商品信息可能只改几次,读写比 99:1 甚至更高。这种场景有一个天然的杠杆——同样的商品数据被读一万次,为什么要查一万次库?查一次,放进内存,剩下九千九百九十九次直接拿,这就是缓存。
反过来看写多读少的场景——计费流水、审计日志、消息投递记录——缓存帮不上忙,该分库分表就分库分表,该走异步就走异步。先看读写比,再决定动缓存还是动存储,这个顺序别反。
缓存的本质:一笔三方的交易
缓存不是免费的午餐,它是一笔交易:用额外的内存空间和一套一致性复杂度,换回数量级的读取提速。80/20 法则在数据上同样成立——不到两成的热点数据扛了八成以上的读流量,把这一小撮放进内存,收益最大。
代价是真实的:数据从此有了两个家,DB 是事实源,缓存是快照——谁先改、改了不同步怎么办?缓存一致性是缓存体系里最麻烦的部分,本系列会用两篇专门讲它。先学会用,再懂它的脾气。
为什么是 Redis
缓存放哪?本地缓存(进程内的 Caffeine)最快,但多实例之间不同步、容量受限于单机内存;Memcached 简单高效,但只有字符串结构;Redis 集中存放、数据结构丰富(String、Hash、List、Set、ZSet)、单线程模型免去锁竞争,还自带持久化与高可用——综合下来成为绝大多数系统的第一选择。
| 方案 | 速度 | 多实例共享 | 适合 |
|---|---|---|---|
| 本地缓存(Caffeine) | 纳秒级 | 否 | 极热的小数据 |
| Memcached | 微秒级 | 是 | 简单 KV |
| Redis | 微秒~毫秒 | 是 | 绝大多数缓存场景 |
本系列的舞台就定在 Redis:从最经典的读写模式讲起,穿过穿透、击穿、雪崩三只拦路虎,翻过一致性这座大山,再走进热 key、大 key 与淘汰策略的治理现场,最后把持久化、哨兵、集群这些运维世界一一走完。下一篇先解决最基础的问题:缓存到底该怎么读写,才能既快又不乱。
评论 (0)