这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
加上缓存
我决定引入缓存来解决问题。
为什么选择缓存?
从上一节的分析我知道:
- 同一个城市的天气,1 小时内基本不变
- 但用户可能在 1 小时内请求多次
- 重复调用外部 API 是巨大的浪费
缓存的工作原理
技术选型
我选择使用 Redis 作为缓存:
为什么是 Redis?
- 速度快:内存存储,读写都在毫秒级
- 简单易用:支持键值对存储
- 支持过期时间:自动删除过期数据
- 免费开源:有免费版本可用
选型边界
为什么此时引入 Redis
- 触发问题
- 天气数据变化慢,但同一城市会被反复查询;继续每次都调用外部 API,会让响应时间和供应商限流一起变差。
- 候选方案
- 进程内字典、文件缓存、MySQL 缓存表、Redis。
- 选择理由
- Redis 支持 TTL、跨进程共享、读写延迟低,刚好匹配“短期缓存天气结果”的需求。
- 代价
- 系统多了一个外部依赖,后续必须处理缓存穿透、击穿、雪崩和缓存一致性问题。
- 暂不解决
- 暂不做 Redis 高可用和分片,因为当前数据量小,先验证缓存能否显著降低外部 API 调用量。
那时候的我还没想到,这个选择会在未来发挥更大的作用。
效果验证
上线后,我观察了一天的数据:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 平均响应时间 | 2000ms | 35ms |
| 缓存命中率 | - | 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:准备运行环境并启动服务
- 步骤 2:识别请求 key、缓存命中状态和回源数据对象
- 步骤 3:根据命中率、数据新鲜度和回源压力调整策略
- 步骤 4:返回缓存或回源结果,并记录命中和失效状态
关注点:命中率、回源压力、TTL 和异常 key 处理。
练习 2
缓存穿透、缓存击穿和缓存雪崩的区别是什么?
参考答案 (3 个标签)
Redis 缓存问题 系统设计
三种缓存问题的区别:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,请求直接打到外部 API | 布隆过滤器、缓存空对象 |
| 缓存击穿 | 热点 key 过期,大量请求同时访问该 key | 互斥锁、逻辑过期 |
| 缓存雪崩 | 大量 key 同时过期或 Redis 宕机 | 随机过期时间、高可用架构 |
详细解释:
缓存穿透:用户查询的数据在缓存和外部 API 中都不存在,每次请求都会访问外部 API。
缓存击穿:某个热点 key 突然过期,此时大量请求同时访问这个 key,导致请求全部打到外部 API。
缓存雪崩:大量缓存 key 在同一时间过期,或者 Redis 服务器宕机,导致所有请求都涌向外部 API。
练习 3
如何保证缓存与外部 API 的数据一致性?
参考答案 (3 个标签)
Redis 缓存一致性 数据同步
常见方案:
方案一:缓存失效策略
当外部 API 数据更新时,主动使缓存失效:
设计流程
练习 3
- 步骤 1:按命中、未命中和异常 key 处理读取与回源
- 步骤 2:按本节策略读取、写入缓存并处理过期或失效
- 步骤 3:识别请求 key、缓存命中状态和回源数据对象
- 步骤 4:根据命中率、数据新鲜度和回源压力调整策略
关注点:命中率、回源压力、TTL 和异常 key 处理。
方案二:设置缓存过期时间
- 给缓存设置合理的过期时间
- 过期后自动从外部 API 重新获取
方案三:主动更新缓存
设计流程
练习 3
- 步骤 1:读取、刷新或失效缓存数据
- 步骤 2:按本节策略读取、写入缓存并处理过期或失效
- 步骤 3:识别请求 key、缓存命中状态和回源数据对象
- 步骤 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:写入缓存值、空值标记或热点保护状态
- 步骤 2:按本节策略读取、写入缓存并处理过期或失效
- 步骤 3:校验身份、密钥或权限
关注点:命中率、回源压力、TTL 和异常 key 处理。
关键点:
- 使用
user:命名规范,便于管理 - 使用
SETEX原子操作设置值和过期时间 - 空值也缓存,防止缓存穿透
- 合理设置过期时间,平衡性能和数据新鲜度