数据持久化与缓存 / 技术笔记

Redis 缓存策略

从一次商品查询开始,理解缓存怎样减少数据库压力,以及数据过期时该如何处理。

内容章节
6 节
阅读重点
失效与一致性
示例语言
TypeScript

数据库可以保存完整事实,但有些数据会被高频读取。每次都执行相同查询,会消耗连接、CPU 和磁盘资源。

文本示意

text

商品详情被读取 10000 次商品信息只修改 2 次

Redis 可以把常用结果放在更快的位置。数据库继续保存完整事实,缓存保存可以重新生成的数据副本。

缓存是在速度、实时性和复杂度之间做交换。

1. 缓存解决的是什么问题

缓存适合读取频繁、生成成本较高,并且允许短时间旧数据的场景。

文本示意

text

请求先查 Redis↓ 命中直接返回

如果数据每次都必须绝对最新,或者读取本身已经很快,引入缓存可能只会增加维护成本。

2. Cache Aside 是怎样工作的

Cache Aside 由应用自己管理缓存。读取时先查缓存,未命中再查数据库并回填。

TypeScript 示例

typescript

async function getProduct(id: string) {    const key = `product:${id}`;    const cached = await redis.get(key);    if (cached) return JSON.parse(cached);
    const product = await database.product.findUnique({ where: { id } });    await redis.set(key, JSON.stringify(product), { EX: 300 });    return product;}

数据库仍然是真实来源,Redis 保存的是可以重新生成的副本。

3. 过期时间应该怎样设置

TTL 决定缓存可以保留多久。太短会频繁回源,太长会让旧数据停留太久。

文本示意

text

库存数据:短 TTL文章详情:中等 TTL固定配置:较长 TTL

大量键同时过期会瞬间把压力推回数据库。可以在基础 TTL 上增加少量随机时间,让过期分散发生。

4. 穿透、击穿和雪崩有什么区别

三种问题的触发方式不同:

文本示意

text

穿透:反复查询根本不存在的数据击穿:一个热点键失效,大量请求同时回源雪崩:许多键集中失效,数据库压力突然升高

空值短缓存、互斥锁、请求合并和随机 TTL,分别适合不同问题。先确认现象,再选择手段。

5. 数据更新时怎样保持一致

常见做法是先更新数据库,再删除缓存。

TypeScript 示例

typescript

await database.product.update({ where: { id }, data: input });await redis.del(`product:${id}`);

下一次读取会从数据库取得新数据并重新写入缓存。相比同时更新两份数据,删除缓存更容易保持逻辑简单。

严格一致仍然很难保证。需要根据业务能够接受多长时间的不一致来设计补偿和重试。

6. 一个缓存请求的完整过程

一次商品详情读取可以这样运行:

文本示意

text

收到商品 ID读取 product:{id}命中则返回缓存↓ 未命中读取数据库写入带 TTL 的缓存返回商品数据

监控时至少要关注命中率、响应时间、内存、过期数量和数据库回源量。只有看见这些数据,才能判断缓存是否真的产生了价值。