缓存策略
新增多个 API 后,缓存策略需要优化。
问题分析:
- 新闻数据:10 分钟更新一次
- 股票数据:实时变化,需要 5 秒缓存
- IP 数据:几乎不变,可以缓存 24 小时
- 天气数据:每小时变化明显
当前问题:
- 所有 API 都使用相同的缓存时间
- 有些数据更新太慢
- 有些数据缓存时间浪费
按更新频率分类
实时数据(秒级):
- 股票价格
- 汇率
准实时数据(分钟级):
- 新闻列表
- 天气温度
静态数据(小时级):
- IP 归属地
- 城市信息
不变数据(永久):
- 国家列表
- 货币代码
L1: 应用内存缓存(最快)
L1 缓存跟着应用进程走,读一次就是一次内存查找,没有网络开销,是整条缓存链路里最快的一层。但它天生是每台机器各存一份,所以只能当加速层,不能当唯一数据源。
L1: 应用内存缓存(最快)
- 步骤 1:在应用进程里用带 TTL 的内存字典存放序列化后的响应
- 步骤 2:设置容量上限与淘汰策略,优先淘汰即将过期的条目
- 步骤 3:命中时直接返回,未命中则回源并把结果写回内存缓存
- 步骤 4:多副本部署时明确它只做单机加速,不承担跨实例的一致性
关注点:进程内缓存的淘汰策略,以及多副本部署下的一致性。
L2: Redis 缓存(共享)
L2 是所有应用实例共享的一层 Redis,它解决了「每台机器各存一份」带来的重复回源问题,也是真正决定整体回源压力的地方。TTL 在这一层按数据类型分开设置,是整份缓存策略的核心。
L2: Redis 缓存(共享)
- 步骤 1:用接口名与请求参数拼出缓存 key,统一序列化格式后写入 Redis
- 步骤 2:写入时设置 TTL,并按数据类型给不同接口配上不同的过期时间
- 步骤 3:命中直接返回,未命中时回源并回填 Redis,同时回填 L1 内存缓存
- 步骤 4:对访问量极高的 key 单独统计,避免集中过期或单点压力
关注点:共享缓存的序列化方式、过期策略与热点 key 压力。
策略 1: 热点数据预热
热点数据的第一个请求总是最慢,因为缓存里根本没有它。预热就是让这个代价提前付掉——用定时任务在低峰期把热点名单先跑一遍,用户上线后就不用再等第一次回源。
策略 1: 热点数据预热
- 步骤 1:从访问日志里统计近期访问量最高的 key,形成热点名单
- 步骤 2:用定时任务在低峰期按名单提前回源并写入缓存,给热点 key 设更长的 TTL
- 步骤 3:观察预热后首访延迟是否下降,确认热点名单仍然准确
关注点:热点 key 的识别方式,以及预热流量与真实流量的冲突。
策略 2: 缓存穿透保护
穿透的本质是请求了根本不存在的 key,缓存永远不命中,数据库被反复打穿。最省事的办法是缓存「空」这个事实本身——但空值的 TTL 必须短,否则新数据就再也上不来了。
策略 2: 缓存穿透保护
- 步骤 1:对查询结果为空的 key 也写入短 TTL 的空值标记
- 步骤 2:请求命中空值标记时直接返回空结果,不再回源查询
- 步骤 3:统计空值命中的比例,异常升高时收紧参数并排查扫描型请求
关注点:不存在的 key 被反复请求时,如何把回源挡在缓存层之外。
策略 3: 缓存雪崩防护
雪崩是很多 key 在同一时刻集体过期,回源请求在同一秒涌向数据源。防护思路有两条:把过期时间打散,把同一 key 的并发回源合并。两条一起做才稳。
策略 3: 缓存雪崩防护
- 步骤 1:给 TTL 叠加随机抖动,并把过期时间按数据类型分成几档,避免大批 key 同时失效
- 步骤 2:回源时加分布式锁或单飞保护,同一 key 只放一个请求去查询
- 步骤 3:观察过期瞬间的回源曲线,确认尖峰已经被削平
关注点:大量 key 同时过期时,如何避免回源压力集中爆发。
股票数据(短期缓存)
股票是典型的秒级数据:慢一点用户就有意见,但真做到毫秒级实时,成本又完全不成比例。5 秒 TTL 是我在这两者之间取的折中。
股票数据(短期缓存)
- 步骤 1:按行情接口设定 5 秒左右的 TTL,容忍一两个 tick 的延迟
- 步骤 2:缓存 value 里同时存最新价与更新时间戳,回源失败时返回上一次的价格并标记为陈旧
- 步骤 3:在高频时段观察命中率与延迟,确认 TTL 取值合理
关注点:秒级行情对新鲜度的要求,以及缓存与实时推送的边界。
新闻数据(中期缓存)
新闻比行情慢,但比天气快,10 分钟的 TTL 比较贴合它的更新节奏。列表接口还有个坑:分页和筛选条件会组合出大量 key,缓存粒度设计不好,命中率会被稀释。
新闻数据(中期缓存)
- 步骤 1:把列表接口按页码和筛选条件拆成独立的缓存 key
- 步骤 2:给新闻列表设 10 分钟左右的 TTL,抓到新批次后主动失效对应缓存
- 步骤 3:观察列表类接口的命中率,确认没有大量分页 key 重复回源
关注点:列表类接口的分页缓存粒度,以及更新之后的失效时机。
IP 数据(长期缓存)
IP 归属地几乎不变,是最适合长缓存的一类数据:24 小时 TTL 换来的命中率提升非常可观。唯一要注意的是 IP 库更新时得主动失效,否则新分配的 IP 会查不到归属地。
IP 数据(长期缓存)
- 步骤 1:给 IP 归属地接口设 24 小时 TTL,与数据的几乎不变特性对齐
- 步骤 2:缓存命中后直接返回,IP 库版本更新时按版本号批量失效对应的缓存
- 步骤 3:统计长 TTL 接口的命中率,验证它们确实是流量的大头
关注点:几乎不变的数据如何避免重复回源,以及 IP 库更新的可见性。
缓存命中率统计
命中率是衡量缓存策略的第一指标,但整体平均值会骗人:一个高频接口就能把数字拉得很漂亮。所以统计一定要分接口做,并且把回源次数和耗时一起看。
缓存命中率统计
- 步骤 1:在缓存读写两处埋点,分别累加命中次数与请求总次数
- 步骤 2:按接口维度分别计算命中率,避免高频接口掩盖低命中接口
- 步骤 3:同时统计回源耗时与回源次数,命中率之外还要看回源成本
- 步骤 4:定期对比不同 TTL 下的命中率,为调整过期时间提供依据
关注点:命中率怎么算才不骗人,以及分接口统计的必要性。
发现内容有问题?
告诉我们哪里需要改进。
