Redis 缓存入门:把热数据放进内存
KV 结构、TTL 过期、缓存回写与命中率
- 理解缓存为什么快(内存 vs 磁盘)
- 掌握 KV 结构与 set/get/TTL
- 理解「先查缓存,没有再查库并回写」
- 对缓存穿透/击穿建立概念
缓存像课桌抽屉 vs 教室仓库:常用的东西(刚查过的课程数据、热门文章)放手边抽屉,伸手就拿;不常用的才去仓库(数据库)搬。Redis 就是那个超快的抽屉,因为它把数据放在内存里。
为什么缓存快?
数据库把数据存在磁盘上,每次查询都要磁头读写,毫秒级;Redis 把数据放在内存里,读写是微秒级,快上百倍。代价是内存贵、容量小、断电会丢,所以只放「热数据」——被频繁访问的那一小部分。
KV 结构与 TTL
Redis 最基础的是键值(KV)结构:一个 key 对应一个 value,可以给 key 设过期时间(TTL),到期自动删除,不用手动清理。
-- Redis 基本命令
SET course:1 "{'title':'TypeScript入门'}" -- 写缓存
GET course:1 -- 读缓存,命中返回值
EXPIRE course:1 300 -- 300 秒后自动过期
SET course:1 "..." EX 300 -- 写的时候直接带过期
DEL course:1 -- 主动删缓存套路:先查缓存,没有再查库并回写
读数据的标准流程:先问缓存有没有,有就直接返回(命中);没有就去数据库查,查出来顺手写回缓存(并设 TTL),下次再来就有了。
缓存一致性:改数据时先动哪边
写数据时矛盾来了:数据库和缓存是两份数据,先改哪个?如果你「先更新数据库,再更新缓存」,在两步之间有请求读走了旧缓存,或者两个写请求交叉,就会出现缓存和数据库不一致。业界更常用的是「先更新数据库,再删除缓存」(注意是删不是更):删完之后,下一次读请求发现缓存没了,自然回库读到最新值再回写。为什么是删而不是更?因为缓存重建是惰性的,能避免并发写时反复覆盖。残留的短暂不一致靠 TTL 兜底——就算删缓存失败,缓存到期后也会自动纠正。
如果反过来先更新缓存,再去更新数据库,而数据库更新失败了,那缓存里就是新值、库里还是旧值,两边永久对不上,还没法自动恢复。所以写路径的铁律是:数据库为准,缓存只是数据库的可失效副本。
动手:用 Python dict 模拟带 TTL 的缓存层
下面模拟这个流程,打印每次请求是命中还是回写,最后算命中率。
缓存三大坑:穿透、击穿、雪崩
缓存不是加上就万事大吉,有三种经典故障,每种都可能在流量高峰把数据库打垮。要分清它们的差别,因为防治手段完全不同。
攻击者或误操作疯狂查一个数据库里根本不存在的用户 id。因为缓存里当然也没有,每次请求都穿透到数据库,缓存形同虚设。防法:一是对查空的结果也缓存一个空值(设短 TTL),下次再来直接命中空;二是前置布隆过滤器,不存在的 id 在请求进数据库前就被拦下。
某个超热门 key(比如首页爆款商品)在缓存里到期的那一瞬间,成千上万请求同时发现缓存没了,齐刷刷扎进数据库。防法:给这个热点 key 的回加缓存加一把锁(只让一个请求去查库重建,其余等它),或者热点 key 不设固定过期、后台提前异步续期。
如果给所有缓存都设了同一个 TTL(比如统一 300 秒),300 秒一到,几十万 key 集体过期,全部请求瞬间压向数据库。防法:给 TTL 加随机抖动,比如在 300 秒基础上加减 0–60 秒的随机值,让它们错开过期,而不是整整齐齐一起失效。
还有个相关技巧叫热点 key 打散:如果某个 key(比如一个超级网红商品)被读得太夸张,单个 Redis 节点都会被它压满。做法是把它复制成 key1、key2、…、keyN 多个副本分散到不同节点,读的时候随机挑一个副本读,把压力摊开。写的时候再同步更新所有副本。入门阶段知道有这些手段即可,面试和实战常考。
什么时候别缓存
缓存不是万能药,加错了反而添麻烦。只写一次、几乎不读的数据(比如注册时一次性写的日志)缓存了纯属浪费内存。读多写少才是缓存的甜区:商品详情、课程信息、配置项这种被反复读、很少改的数据。另一个判断标准是数据能不能容忍短暂不一致——缓存 inevitably 有个时间窗口数据是旧的,股票实时价格、刚下单的余额这类要求强一致的场景,就别图快加缓存。
本课前半讲的「先查缓存,miss 再查库回写」叫 Cache-Aside(旁路缓存),是最常用、最灵活的读模式,由应用自己管缓存。简单记住:读路径旁路,写路径先更库再删缓存,剩下交给 TTL。更复杂的 Write-Through(写库同时写缓存)等模式到 s13 再展开。
最后提醒一个边界:Redis 虽快,但它是缓存不是数据库,别把唯一一份数据只放 Redis。它内存宝贵、重启要做持久化恢复、还可能被淘汰策略清掉。所有缓存数据都应该能从数据库重建——缓存挂了,系统只是变慢,不是数据丢失。这也是为什么一致性那节反复强调「数据库为准」。
缓存(如 Redis)为什么比数据库快?
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
网络侧:DNS→TCP/TLS→HTTP 请求与响应;浏览器侧:解析 HTML 建 DOM、解析 CSS 建 CSSOM、合并成渲染树,再经过布局(Layout)、绘制(Paint)、合成(Composite)上屏。前端性能优化几乎都围绕这条链路:减小资源体积、优化关键渲染路径、避免频繁触发重排重绘。
挑战任务
统计缓存命中率
缓存命中就直接返回,未命中才查库并写入缓存。实现 get(key),统计一组请求里的命中数。
课后作业
给 TTL 加抖动防雪崩
所有 key 设同一个 TTL 会集体过期、瞬间压垮数据库。实现 ttl_with_jitter(base, i):在 base 基础上加上 (i % 5) 秒,让第 i 个 key 的过期时间错开 0–4 秒。打印 6 个 key 的最终 TTL。