加上缓存

我决定引入缓存来解决问题。

为什么选择缓存?

从上一节的分析我知道:

  • 同一个城市的天气,1 小时内基本不变
  • 但用户可能在 1 小时内请求多次
  • 重复调用外部 API 是巨大的浪费

缓存的工作原理

技术选型

我选择使用 Redis 作为缓存:

为什么是 Redis?

  • 速度快:内存存储,读写都在毫秒级
  • 简单易用:支持键值对存储
  • 支持过期时间:自动删除过期数据
  • 免费开源:有免费版本可用
选型边界
为什么此时引入 Redis
触发问题
天气数据变化慢,但同一城市会被反复查询;继续每次都调用外部 API,会让响应时间和供应商限流一起变差。
候选方案
进程内字典、文件缓存、MySQL 缓存表、Redis。
选择理由
Redis 支持 TTL、跨进程共享、读写延迟低,刚好匹配“短期缓存天气结果”的需求。
代价
系统多了一个外部依赖,后续必须处理缓存穿透、击穿、雪崩和缓存一致性问题。
暂不解决
暂不做 Redis 高可用和分片,因为当前数据量小,先验证缓存能否显著降低外部 API 调用量。

那时候的我还没想到,这个选择会在未来发挥更大的作用。

效果验证

上线后,我观察了一天的数据:

指标优化前优化后
平均响应时间2000ms35ms
缓存命中率-95%
外部 API 调用量10 万次/天5000 次/天

问题解决

  • 响应时间从 2 秒降到 35ms,用户不再抱怨
  • 外部 API 调用量降低 95%,不再担心限流
  • 成本几乎没增加(Redis 资源占用很小)

那一刻,我觉得所有的纠结和尝试都是值得的。

当前架构

练习

练习 1

Redis 中设置键值对过期时间的命令是?

参考答案 (3 个标签)
Redis 过期时间 基本命令

答案EXPIRE key seconds

扩展知识

  • EXPIRE key seconds - 设置键的过期时间(秒)
  • PEXPIRE key milliseconds - 设置键的过期时间(毫秒)
  • EXPIREAT key timestamp - 设置键在指定时间戳过期
  • TTL key - 查看键的剩余生存时间(秒)

示例

设计流程
练习 1:部署操作
  1. 步骤 1:准备运行环境并启动服务
  2. 步骤 2:识别请求 key、缓存命中状态和回源数据对象
  3. 步骤 3:根据命中率、数据新鲜度和回源压力调整策略
  4. 步骤 4:返回缓存或回源结果,并记录命中和失效状态
关注点:命中率、回源压力、TTL 和异常 key 处理。

练习 2

缓存穿透、缓存击穿和缓存雪崩的区别是什么?

参考答案 (3 个标签)
Redis 缓存问题 系统设计

三种缓存问题的区别

问题原因解决方案
缓存穿透查询不存在的数据,请求直接打到外部 API布隆过滤器、缓存空对象
缓存击穿热点 key 过期,大量请求同时访问该 key互斥锁、逻辑过期
缓存雪崩大量 key 同时过期或 Redis 宕机随机过期时间、高可用架构

详细解释

  1. 缓存穿透:用户查询的数据在缓存和外部 API 中都不存在,每次请求都会访问外部 API。

  2. 缓存击穿:某个热点 key 突然过期,此时大量请求同时访问这个 key,导致请求全部打到外部 API。

  3. 缓存雪崩:大量缓存 key 在同一时间过期,或者 Redis 服务器宕机,导致所有请求都涌向外部 API。

练习 3

如何保证缓存与外部 API 的数据一致性?

参考答案 (3 个标签)
Redis 缓存一致性 数据同步

常见方案

方案一:缓存失效策略

当外部 API 数据更新时,主动使缓存失效:

设计流程
练习 3
  1. 步骤 1:按命中、未命中和异常 key 处理读取与回源
  2. 步骤 2:按本节策略读取、写入缓存并处理过期或失效
  3. 步骤 3:识别请求 key、缓存命中状态和回源数据对象
  4. 步骤 4:根据命中率、数据新鲜度和回源压力调整策略
关注点:命中率、回源压力、TTL 和异常 key 处理。

方案二:设置缓存过期时间

  • 给缓存设置合理的过期时间
  • 过期后自动从外部 API 重新获取

方案三:主动更新缓存

设计流程
练习 3
  1. 步骤 1:读取、刷新或失效缓存数据
  2. 步骤 2:按本节策略读取、写入缓存并处理过期或失效
  3. 步骤 3:识别请求 key、缓存命中状态和回源数据对象
  4. 步骤 4:根据命中率、数据新鲜度和回源压力调整策略
关注点:命中率、回源压力、TTL 和异常 key 处理。

各种方案对比

方案优点缺点
缓存失效简单高效下次请求会变慢
设置过期时间自动恢复过期前数据可能不一致
主动更新一致性最好需要额外逻辑维护

练习 4

Redis 有哪些常用的数据结构?分别适用于什么场景?

参考答案 (3 个标签)
Redis 数据结构 应用场景

Redis 五大基本数据结构

数据结构能力示例适用场景
String键值缓存缓存、计数器、分布式锁
List有序队列消息队列、最新列表
Hash对象字段存储对象、购物车
Set无序集合去重、好友关系、抽奖
ZSet带分数排序排行榜、带权重的排序

扩展数据结构

  • Bitmap - 签到统计、用户在线状态
  • HyperLogLog - UV 统计
  • Geospatial - 附近的人、地理位置

练习 5

设计一个 Redis 缓存策略,要求:

  • 用户信息查询先查缓存,缓存未命中再调用外部 API
  • 缓存过期时间设置为 30 分钟
  • 需要处理缓存未命中的情况

请用流程说明核心逻辑。

参考答案 (3 个标签)
Redis 缓存策略 方案设计

参考答案

设计流程
练习 5
  1. 步骤 1:写入缓存值、空值标记或热点保护状态
  2. 步骤 2:按本节策略读取、写入缓存并处理过期或失效
  3. 步骤 3:校验身份、密钥或权限
关注点:命中率、回源压力、TTL 和异常 key 处理。

关键点

  1. 使用 user: 命名规范,便于管理
  2. 使用 SETEX 原子操作设置值和过期时间
  3. 空值也缓存,防止缓存穿透
  4. 合理设置过期时间,平衡性能和数据新鲜度