fedorthinks

速成课 · No. 15

cache 是一个又小又快的地方,用来存放你已经算出来的答案,这样下次需要它时直接读出来,而不必把活儿重做一遍。它是计算里最大的提速诀窍——同时也带来了计算里最著名的难题:判断那个记下来的答案什么时候已经过时。

20 个概念 · 每个配一幅图 · 结尾有速查表

§ 01

cache 只是一个简单的想法:把答案的一份副本放在某个快的地方,这样同一个答案不用付两次代价。懂了这一点,你以后遇到的每个 cache 都是同一个把戏,只是换了个地方。

地图:七组共 20 个概念

一张地铁图:先看到所有线路的全貌,再一站一站地坐过去。

先是三个基础——cache 是什么、hit 与 miss、以及它为什么管用。然后是六组,每章一组:cache 的层级、hit ratio、新鲜度、更新与淘汰的策略、陷阱,以及如何用好它。

后面每个概念都是同一个样子:它的英文名称(要记住——这就是术语)、一幅生活里的画面、一张图,以及一张卡片,字段有问题、思路、例子、代价、何时使用和关联。

BasicsCacheHit / missLocalityLayersCache hierarchyIn-memory (Redis)CDNHit ratioHit ratioCold / warm cacheMemory ↔ speedFreshnessStale dataTTLCache invalidationStrategiesCache-asideWrite-through / aroundEviction (LRU)TrapsCache stampedeStale readsWhat not to cacheUsing it wellHot · costly · stableStaleness budget
基础在上,然后是六组——第 2–7 章依次讲它们。

Cache — cache 记住答案

桌上放一本便笺,旁边是一个很慢的文件柜——查过一次的东西你就记下来,下次读便笺就行,不用再穿过房间。

cache 是一个又小又快的存储,保存着耗费很大的工作的结果——一次数据库查询、一个算好的页面、一个下载的文件——这样下一次请求就很便宜。昂贵的事只做一次;便宜的读取做很多次。这就是全部原理。其余都是细节:便笺放在哪里,以及什么时候该信上面写的东西。

Appneeds an answerCachesmall · fastSource of truthDB · API · computeask firstonly if missingexpensive work once → cheap reads many times
应用先问 cache。只有答案不在时,才去找慢速的数据源。
ConceptCache· 文件柜旁的便笺
问题
同样昂贵的工作被一遍又一遍地做。
思路
把结果存在某个快的地方,从那里读取。
例子
一个算好的页面、一次数据库查询的结果、一个下载的文件——都存在 cache 里。
代价
副本占用内存,并且有副本过时的风险。
关联
课程的其余部分讲的就是 cache 放在哪里,以及什么时候该信它们。

Cache hit 与 cache miss — 两种结果

你去拿便笺。要么它就在那里,你立刻读到——要么它是空白的,你终究还是得走到文件柜那里。

答案在 cache 里时,叫 cache hit(命中)——又快又便宜。不在时,叫 cache miss(未命中)——你要做缓慢的工作,通常还会把结果存起来,让下次命中。这两个词描述了 cache 的每一次交互。caching 这场游戏,就是把 miss 变成 hit。

HITRequestCachefound ✓Responsefast · cheapMISSRequestCachenot found ✗Sourceslow workstore the result → next time it's a hit
hit 立刻作答。miss 要走到数据源那里,并把结果存起来留给下次。
TermCache hit / miss· 找到 / 没找到
是什么
hit 表示答案在 cache 里。miss 表示不在。
如何运作
miss 时你做缓慢的工作,并把结果放进 cache——这样下次就是 hit。
例子

第一次访问某个页面:一次 miss,加一次数据库查询。第二次访问:毫秒级的 hit。

关联
hit 所占的比例就是 hit ratio,是 cache 的主要成绩。

Locality — 它管用是因为重复

咖啡店记住老顾客点的东西——值得这么做,只因为同样的人一次又一次点同样的东西。

当同样的答案被反复需要时,caching 才划算——而计算里几乎一切都在重复。这个性质叫 locality(局部性):访问不是随机的,而是集中在少数热门条目上。同一个热门页面、同一个用户的资料、同一次查找,被问上千次。所以,一个装着热门条目的小 cache 就能吸收极大一部分工作。没有重复,就没有理由做 cache。

requestsitems, most popular firsthot set — a small cache catches most requestslong tail — rarely asked, not worth caching
少数热门条目承接了大部分请求;长尾部分很少被问到。
ConceptLocality· 热门集合
是什么
同样的数据被一遍又一遍地请求。
为什么重要
一个装着热门条目的小 cache 能接住极大一部分请求。
例子
一个热门页面、同一个用户的资料、同一个搜索。
陷阱
没有重复——cache 几乎不命中,只是白白浪费内存。
关联
locality 让高 hit ratio 成为可能。

cache 保存一份昂贵答案的快速副本。hit 是读便笺;miss 是走去文件柜。要做的事,就是把 miss 变成 hit。

§ 02

caching 不是某个地方的某一件事——它是在用户与数据之间的每一层都重复出现的模式。副本离需要它的人越近,就越快。

Cache Hierarchy — cache 越近,hit 越快

你的用品放在桌上、房间里、楼里,以及城那头的仓库里——越近拿得越快,但能放的越少。

用户与真相之间有一级一级的 cache——一个 cache hierarchy(缓存层级):浏览器把文件存在你的设备上,CDN 把副本存在你所在城市附近,应用把热门数据放在内存里,CPU 有只隔几纳秒的微型 cache。越近的 cache 越快但越小。请求先试最近的,miss 时再向外退一层。

UserBrowseryour deviceCDNnear your cityApp cacheRedis · RAMDatabaseorigineach miss falls outward →closer: faster, smallerfarther: slower, biggerand inside every machine: CPU caches (L1 / L2 / L3), nanoseconds away
请求从最近的 cache 向外走。越近越快,但越小。
ConceptCache Hierarchy· 多层 cache
问题
一个 cache 不可能同时又近、又快、又大。
思路
一串 cache:近的小而快,远的大而慢一些。
例子
浏览器 → CDN → 应用 cache(Redis)→ 数据库。
代价
过时的副本多了更多可以藏身的地方。
关联
这些层是 CDN、内存 cache 和 CPU cache。

In-memory Cache (Redis) — 主力

一个职员把最常被要的文件放在桌上伸手可及的地方,而不是每次都跑到地下室档案库去取。

大多数应用会在应用与数据库之间放一个专门的内存 cache——像 Redis 或 Memcached 这样的工具。内存比磁盘快得多,所以把热门结果放在那里,就能把一次缓慢的数据库查询变成近乎瞬时的读取。人们说"加个 cache"来加速后端时,通常指的就是这个。

AppRedis / Memcached · in RAMuser:42 → {name, plan}page:/home → <html>top:products → [ … ]Databaseon disk · slowGETmiss
Redis 把 key → value 键值对存在内存(RAM)里。只有 miss 时,应用才去访问磁盘上的数据库。
ToolIn-memory Cache· Redis、Memcached
问题
磁盘上的数据库查询很慢,而且相同的查询会重复出现。
思路
把热门结果存在内存里,按 key 查找。
例子
key user:42 存着一份资料;应用从 Redis 而不是数据库读取它。
代价
多了一个服务;内存有限;副本可能过时。
何时使用
在慢速的真相来源前面加速后端。
关联
通常用 cache-aside 模式来填充。

CDN — 缓存在用户身边的网络

热门图书摆在各地的本地图书馆里,这样谁都不必向唯一的中央档案馆邮购。

CDN(content delivery network,内容分发网络)是把 caching 用到地理上:它把你的页面、图片和文件的副本存在世界各地的服务器(edge 边缘服务器)上,让每个用户从附近的一台获得服务。这就是为什么一个全球性的网站在各处都加载很快,而不只是在离它的源(origin)服务器近的地方。还是同一个 hit/miss 的想法——只不过副本散布在地图上,以此战胜距离。

Edge · Tokyocopy near usersEdge · Pariscopy near usersEdge · São Paulocopy near usersEdge · New Yorkcopy near usersOriginone servermiss → fetch from origin onceusers hit the nearest edge
用户从最近的 edge 拿到副本。edge 只有在 miss 时才去找 origin。
ToolCDN· 内容分发网络
问题
服务器在某一个国家,远处的用户要为距离等待。
思路
把副本存在遍布世界的 edge 服务器上。
例子
一个网站的图片从离用户近的城市提供,而不是从 origin 提供。
代价
散布在世界各地的副本同样会过时;需要有办法刷新它们。
何时使用
面向多地用户的静态文件、页面和资源。
关联
cache hierarchy 的其中一层。

caching 是在每一层重复出现的模式:浏览器、CDN、内存存储、CPU。越近越快但越小——而 miss 会落到外面的下一层。

§ 03

只有当 cache 真的接住了大多数请求,它才配得上自己带来的复杂度。一个数字——hit ratio——就能告诉你,你的 cache 有没有物有所值。

Hit Ratio — 真正重要的成绩

守门员是看他扑出的射门占多大比例来评判的。扑出 95% 的守门员能改变一切;只扑出 10% 的,几乎改变不了比赛。

hit ratio(命中率)是由 cache 而不是慢速数据源来响应的请求所占的比例。95% 的 hit ratio 意味着每二十个请求里只有一个会到达数据库——负载和延迟都大幅降低。10% 的 hit ratio 则意味着这个 cache 基本上是额外开销。判断一个 cache 是否管用,靠的就是这一个数字。

95%19 hits · only 1 request in 20 reaches the database10%2 hits · 18 misses — the cache is mostly overheadhitmiss
95%:每二十个请求里一个到达数据库。10%:cache 几乎什么也没接住。
TermHit Ratio· 命中占比
是什么
hits ÷ (hits + misses)——由 cache 响应的请求所占的比例。
例子
95%——每 20 个请求里 1 个到达数据库。10%——cache 几乎没帮上忙。
原因
判断 cache 是否管用的主要方法,也是你要调的主要旋钮。
关联

随 locality 和合理的淘汰而升高;随冷 cache 和较短的 TTL 而下降。

Cold 与 Warm Cache — 冷 cache 需要预热

一家货架空空的新店,一开始谁也服务不快——随着顾客开口要东西才慢慢进货,直到热门商品总是现成的。

刚启动之后,cache 是冷的(cold)——空的,所以每个请求都 miss,都去找慢速的数据源。随着真实流量涌入,它被热门答案填满,变得热(warm),hit ratio 随之攀升。这就是为什么刚重启或部署后性能可能看起来很差,然后又稳定下来。有些系统会有意地预热(pre-warm)cache,在用户到来之前就加载已知的热门数据。

hit ratiotime →restart / deploy: cold, all misseswarm: popular answers cacheddashed: pre-warmed before users arrive
重启之后,hit ratio 从零开始爬升。预热过的 cache 一开始就很高。
TermCold / Warm Cache· 预热
是什么
冷——空 cache,全部 miss。热——已填满,能命中。
例子
部署之后,网站会慢几分钟,直到 cache 被填满。
陷阱
同时重启所有节点,会把全部负载压到数据库上。
对策
预热:在用户到来之前加载热门数据。
关联
解释了重启之后 hit ratio 的下跌。

Memory–Speed Trade-off — cache 用内存换速度

租一张更大的桌子,让更多文件放在伸手可及的地方——占的空间更多,但你去文件柜的次数少得多。

caching 不是免费的:你花的是有限且要花钱的内存,来省下时间。cache 越大,存得越多,命中也越频繁,但你不可能什么都缓存——所以窍门在于缓存对的东西:那些热门的、昂贵的、被反复询问的答案,它们每存一个字节换来的速度最多。cache 是一次刻意的取舍(trade-off)。

Spend memorylimited · costs moneyCachethe right answersBuy timefewer slow tripsyou can't cache everything → most speed per byte storedthe hit ratio tells you if the trade is paying
你付出内存,买到时间。hit ratio 告诉你这笔交易划不划算。
ConceptMemory–Speed Trade-off· 拿内存换时间
思路
用内存付费,以减少去慢速数据源的次数。
例子
给 Redis 更多内存——能放下更多 key,更多 hit。
代价
内存有限且要花钱;你不可能什么都缓存。
何时使用
缓存每字节换来速度最多的东西:热门的和昂贵的。
关联
hit ratio 衡量回报;淘汰腾出空间。

hit ratio 是 cache 的成绩单。你花内存买速度——所以要缓存那些热门、昂贵、每字节换来最多 hit 的答案。

§ 04

一个记下来的答案,可能在你还信着它的时候就已经过时。知道什么时候该不再相信那份副本,是 caching 中核心的、真正困难的问题。

Stale Data — caching 的代价

你记在便利贴上的那个电话号码是宝贝——直到朋友搬了家,那张便条悄悄变成了错的。

记住一个答案的另一面是:真实的答案可能变了,而你的副本没变。stale(陈旧)数据就是不再与真相相符的缓存值——旧价格、已删除的帖子、一小时前的库存数。每个 cache 都有提供陈旧数据的风险,而决定你能容忍多少陈旧,是任何 cache 的第一个真正的设计问题。

10:0010:05Source: $10the truthSource: $12price changedCache: $10✓ matchesCache: $10✗ stalecopyupdatenobody told the cachestale = a cached value that no longer matches the truth
数据源里的价格变了,但没人更新缓存的副本——它变陈旧了。
TermStale Data· 过时的副本
是什么
一个不再与数据源相符的缓存值。
例子
旧价格、已删除的帖子、一小时前的库存数。
为何发生
真相变了;副本没变。
关联
受 TTL 和失效约束;能容忍多少,由 staleness budget 决定。

TTL — 让它按计时器过期

牛奶上盖着保质期——过了那天,不检查就不会相信它,哪怕它看起来还好好的。

最简单的新鲜度工具是 TTL(time to live,存活时间):每个缓存条目都有一个过期时间,过了之后该条目就被当作不存在,迫使重新获取新数据。TTL 短意味着数据更新鲜但 miss 更多;TTL 长意味着 hit 更多但更陈旧。选 TTL,就是在新鲜度与速度的权衡里选出你的确切位置——按每种数据分别选,依据是它实际变化得有多快。

fresh — served from cache (TTL = 60s)expired → refetchstored+60sEach entry carries an expiry timer.short TTL → fresher data, more misseslong TTL → more hits, more staleness
TTL 用完之前,答案来自 cache。之后,下一个请求去获取新数据。
PracticeTTL· 存活时间
问题
副本可能变陈旧,而 cache 并不知道。
思路
给每个条目一个有效期;过了之后,重新获取。
例子
汇率用 60 秒的 TTL 缓存:永远不会比一分钟更旧。
代价
TTL 短意味着更多 miss;TTL 长意味着更陈旧。
何时使用
几乎总是用,作为基本的安全网;按每种数据调整。
关联
一个热门 key 对所有人同时过期,会引发 cache stampede。

Cache Invalidation — 为什么它出了名的难

在世界某处某个事实变化的那一刻就知道,好在恰到好处的时机撕掉你的便条——说起来容易,做起来让人发疯。

有个流传已久的笑话说,计算机科学里只有两个难题,而 cache invalidation(缓存失效)——知道什么时候必须丢掉某个缓存值——就是其中之一。它之所以难,是因为 cache 往往不知道底层数据已经变了;变化发生在别的地方。下一章讲的就是人们用来对付这个难题的一套策略。

Service AwritesDatabasechangedCacheold copyService BreadsUPDATEstale? who tells the cachethe change happened elsewhere
一个服务修改了数据库,另一个服务从 cache 读到旧副本。谁来通知 cache?
ConceptCache Invalidation· 丢掉副本
问题
数据在别处变了,而 cache 不知道。
思路
真相变化时,丢掉或刷新副本。
例子
价格变动之后,从 cache 里删掉该商品的 key。
代价
你得找出副本存在的每一个地方,一个都不能漏。
关联

策略:cache-aside、write-through、write-around;安全网:TTL。

每个缓存的答案都可能变陈旧。TTL 用计时器让它过期——而确切知道何时失效,是计算里真正困难的问题之一。

§ 05

两股力量决定 cache 里有什么、它是否可信:真相变化时你如何更新它,以及它满了时你如何腾出空间。两者都有值得了解的、有名字的策略。

Cache-aside — 由应用来填充 cache

你先看便笺;如果是空白,就走到文件柜,读完文件,把答案记下来,再继续手头的事。

最常见的模式是 cache-aside(也叫 lazy loading,懒加载):读取时,应用先查 cache;miss 时,从数据源获取,存下结果并返回。cache 按需填充,存的正是实际被请求的东西。它简单而流行——它的弱点是陈旧,因为一个值会一直待在 cache 里,直到有东西让它过期或失效。

AppCacheDatabase1. GET key2. miss3. read from DB4. result5. SET key (+TTL)6. return to user — next read is a hit
cache-aside 一步一步:问 cache → miss → 读数据库 → 存入 cache → 返回。
PatternCache-aside· lazy loading
问题
你需要填充 cache,却事先不知道会被问到什么。
思路
由应用来做:查 cache,miss 时读数据库,并把结果放进 cache。
例子
GET user:42 → miss → 对数据库 SELECT → 带 TTL 地 SET user:42。
代价
一个值可能一直陈旧,直到它的 TTL 用完或有人删除它。
何时使用
数据库前面大多数 cache 的默认做法。
关联
常与 TTL 搭配;写入时用 write-through 来补充。

Write-through 与 Write-around — 让写入保持一致

一个事实变化时,你可以在更新总档案的同时更新桌上的便条——也可以跳过便条,让它下次重新获取新的。

数据被写入时,你要选择 cache 怎么跟上。Write-through 同时更新 cache 和数据源,所以 cache 永远不会陈旧(代价是写入更慢)。Write-around 只写数据源,让 cache 在 miss 时再重新加载,避免缓存没人会再读的数据。选哪个,取决于你的读写比例。

Write-throughWriteCacheupdatedDatabaseupdatedcache never stalecost: slower writesWrite-aroundWriteCacheskippedDatabaseupdatedcache reloads on the next missskips data nobody re-reads
Write-through 同时写 cache 和数据库。Write-around 只写数据库;cache 在 miss 时跟上。
PatternWrite-through / Write-around· 写入策略
问题
写入时,cache 和数据源会各走各的。
思路
Write-through——cache 和数据库一起更新。Write-around——只写数据库。
例子

马上就会被再次读取的资料——write-through。几乎没人读的日志——write-around。

代价
Write-through——写入更慢。Write-around——写入之后紧接着一次 miss。
何时使用
取决于你的读写比例。
关联
从写入一侧做 cache invalidation 的方式。

Eviction (LRU, LFU) — 满了的时候决定丢什么

一张已经放满的小桌子——要放进新文件,就得收起一个,所以你拿走那个好久没碰过的,而不是每小时都用的。

cache 的空间有限,所以满了的时候必须淘汰(evict)一些东西。最常见的策略是 LRU(least recently used,最近最少使用):丢掉最久没被碰过的,赌的是它不会很快被需要。还有别的策略——LFU(least frequently used)丢掉使用频率最低的。淘汰让一个小 cache 能自动保留热门条目、舍弃冷门条目,而不必无限增长。

F (new)cache is fullAused 1s agoBused 2h agoCused 5m agoDused 10s agoEused 1m agoB evicted — least recently used (LRU)LFU instead drops the least frequently used
cache 满了,F 来了。按 LRU,B 被淘汰——它最久没人碰。
PolicyEviction· LRU、LFU
问题
cache 空间用完了,而新数据还在不断到来。
思路
丢掉最不可能很快被需要的。
例子
LRU——丢掉最久没碰的。LFU——丢掉用得最少的。
代价
有时会丢掉马上就要被问到的东西——多一次 miss。
关联

在不增加内存的情况下保持 hit ratio。和失效不是一回事:失效针对的是错误数据,淘汰针对的是空间。

失效决定一个缓存值何时是错的;淘汰决定空间用完时丢什么。cache-aside、write-through、LRU——都是这两件事的名字。

§ 06

caching 的威力伴随着锋利的棱角。那些经典的故障并不稀奇——它们可预测、有名字,而了解它们就是避开它们的一半。

Cache Stampede — 热门 key 过期的时候

一件热门商品卖光了,就在那一刻,一百个顾客同时冲向柜台要它——把唯一负责补货的店员压垮了。

当一个热门缓存条目过期时,所有想要它的请求都会在同一时刻 miss,并一起冲向慢速的数据源——这叫 cache stampede(缓存踩踏,也叫 thundering herd,惊群)。被保护了几个小时的数据库,突然一下子要面对整个人群,可能会被压垮。解决办法是已知的——让一个请求去刷新而其他请求等待,或者错开过期时间——但你必须预先想到:stampede 恰恰出现在流量最高的时候。

requestrequestrequestrequestrequestrequestHot keyjust expiredDatabaseall at once → bucklesall miss at onceFixesOne refreshes, rest waitlock / single flightStagger expiriesTTL + random jitter
一个热门 key 过期了——所有请求同时冲向数据库。下方:两种已知的解决办法。
PitfallCache Stampede· thundering herd
问题
一个热门 key 过期,所有请求都 miss,同时冲向数据库。
例子
首页在流量高峰时过期——成千上万个请求同时重建它。
对策
一个请求去刷新,其余等待;或者给 TTL 加随机分散(jitter)。
何时要想到
每一个带 TTL 的热门 key。
关联
TTL 用在热门数据上的副作用。

Stale Reads — 又快、又自信、还是错的

从一张旧便利贴上读出一个价格,并自信满满地报出来——数字瞬间送到,却是错的数字。

cache 会乐呵呵地又快又充满自信地提供一个陈旧的值——这是一次 stale read(陈旧读取)。通常这没问题——浏览次数稍微旧一点,不会伤到谁。但对于正确性至关重要的数据——银行余额、库存、权限——一次陈旧读取可能就是真正的 bug。关键的本事是知道哪些数据能容忍陈旧、哪些必须永远新鲜,并且不要随随便便缓存后一类。

PitfallStale Read· 提供过去的数据
问题
cache 又快又自信地提供了一个旧值。
无害
一个晚了一分钟的浏览次数。
真正的 bug
银行余额、库存、权限。
对策
事先决定哪些数据能容忍陈旧,哪些不能。
关联
stale data 是如何表现出来的;用 staleness budget 来处理。

What Not to Cache — 有些东西根本不该缓存

你不会去复印一份每分钟都在改写的文件,也不会在共用的桌子上放别人的私人信件的复印件——这份副本要么是错的,要么本就不该在那里。

不是所有东西都属于 cache。不断变化的数据,hit ratio 很差,还频繁陈旧。敏感的或按用户区分的数据,如果共享 cache 把一个用户的副本交给另一个用户,就有泄露的风险。而很少被请求的数据只会浪费空间。知道不该缓存什么,和知道该缓存什么一样重要。

Fast-changingpoor hit ratio · staleSensitive / per-usercan leakRarely requestedwastes spacehow a leak happens:User AGET /accountShared cache/account → A's dataUser Bsees A's data ✗knowing what NOT to cache matters as much as what to cache
三类不属于 cache 的数据。下方:共享 cache 如何把用户 A 的数据交给用户 B。
PracticeWhat Not to Cache· 跳过清单
变化快
hit ratio 很差,而且持续陈旧。
敏感
共享 cache 里按用户区分的数据和机密数据可能到达错误的用户手里。
冷门
很少被请求——只会占空间。
关联
cache target 的另一面。

caching 经典的咬人之处都有名字:热门 key 过期时的 stampede、带着虚假自信提供的 stale read,以及缓存了根本不该缓存的东西。

§ 07

caching 是一件强力工具:用一点点内存换来巨大的速度,代价是有提供过去数据的风险。有意识地使用,它是计算里最好的交易之一。

Cache Target — 缓存热门、昂贵、变化慢的东西

你记住老顾客的常点,和一天要给十遍的路线——而不是一次性的请求,或者按分钟变化的东西。

理想的 cache target 是经常被读取、生成起来昂贵、很少变化的东西——这种组合以最小的陈旧风险换来最大的速度。一个由慢查询构建、一整天重算结果都一样的热门页面,是完美的。一个不断变化、很少被读取、又敏感的值,则正相反。

Read oftenhot · high hit ratioExpensive to makeslow query · computeRarely changeslow staleness riskIdeal cache targetmost speed · least risk
三个条件同时满足,就是理想的 cache target。
PracticeCache Target· 该缓存什么
思路
经常被读 + 生成昂贵 + 很少变化。
例子
一个由慢查询构建、一整天都一样的热门页面。
反例
一个不断变化、很少被读取、又敏感的值。
关联
what not to cache 的反面;它带来高 hit ratio。

Staleness Budget — 事先就定好

事先约定多旧是可以接受的——天气小组件可以晚几分钟;银行余额一秒都晚不得。

在缓存任何东西之前,先决定它能容忍多少陈旧——这就是它的 staleness budget(陈旧预算)。这个选择决定了其他一切——TTL、你是否需要主动失效、它到底能不能安全地缓存。要按每种数据把它明确下来。"它被允许错到什么程度,错多久?"这个问题,能把 caching 从神秘 bug 的来源,变成一笔受控的、刻意的交易。

can be stale for a whilemust be freshstatic filesview countweather: minutesinventorypermissionsbank balancethe budget sets the TTL, invalidation — or "don't cache"
左边是能容忍陈旧的数据;右边是必须永远新鲜的数据。
PracticeStaleness Budget· 多旧才算可以
问题
没有明确的决定,陈旧就会以神秘 bug 的形式出现。
思路
对每种数据,事先决定它可以有多旧。
例子
天气——几分钟没问题。银行余额——一秒都不行。
它决定什么
TTL 的长度、是否需要失效、到底要不要缓存。
关联
应对 stale data 和 stale read 风险的答案。

这些概念如何联系起来

天平的两个托盘:一边是速度,一边是新鲜度。本课的每个概念都在移动其中一个。

整个 caching 就是速度与新鲜度之间的拉锯。左边,是抬高 hit ratio 的因素:locality、热 cache、合理的淘汰。右边,是压住 stale data 的因素:staleness budget 决定 TTL,而失效丢掉错误的数据。TTL 短,更新鲜但命中更少;在热门 key 上,同样的 TTL 可能引发 stampede。

LocalityCold / warmEviction (LRU)Hit ratiospeedStampedeStale datafreshnessStaleness budgetTTLInvalidationaside · throughspeed ↔ freshnessmore hits ← cache longer · fresher ← cache shorter
左边是速度的力量,右边是新鲜度的力量。预算决定 TTL;TTL 约束陈旧,也可能引发 stampede。
加 cache 之前
  • 数据是热门的吗——被反复请求的程度,足以带来真正的 hit ratio 吗?
  • 生成起来昂贵吗,让一次 hit 确实能省下可观的工作吗?
  • 它能有多陈旧——TTL 是多少,我需要主动失效吗?
  • 热门条目过期时会怎样——会引发 stampede 吗?
  • 缓存它安全吗——共享 cache 里没有按用户区分的机密数据吧?
  • 我怎么知道它管用——我在测量 hit ratio 吗?
你缓存得好的迹象
  • 你能说出重要 cache 的 hit ratio。
  • 每个被缓存的东西都有一个经过考虑的 TTL,与它实际变化的快慢相匹配。
  • 没有任何按用户区分的或机密的东西意外地躺在共享 cache 里。
  • 你的热门 key 在过期时不会一下子踩踏数据源。
  • 你缓存的是热门、昂贵、变化慢的答案——其余的就不管。

速查表:全部 20 个概念

基础

Cache便笺

昂贵答案的快速副本,免得把活儿做两遍。

Cache hit / miss找到 / 没找到

答案在 cache 里——或者你得去数据源。

Locality热门集合

同样的数据被一遍又一遍地请求——这就是 cache 管用的原因。

层级

Cache Hierarchy多层 cache

浏览器 → CDN → 应用内存 → 数据库。越近越快,但越小。

In-memory CacheRedis、Memcached

慢速数据库前面放在内存里的热门数据。

CDN内容分发网络

文件和页面的副本,放在离用户近的服务器上。

命中率

Hit Ratio命中占比

由 cache 响应的请求所占的比例。cache 的主要成绩。

Cold / Warm Cache预热

空 cache 全部 miss;填满的就能命中。预热会提前加载它。

Memory–Speed Trade-off拿内存换时间

付出内存,买到时间。缓存每字节换来速度最多的东西。

新鲜度

Stale Data过时的副本

副本不再与真相相符。

TTL存活时间

一个条目的寿命;过了之后,重新获取。

Cache Invalidation丢掉副本

真相变化时丢掉副本。出了名的难。

策略

Cache-asidelazy loading

应用先查 cache,miss 时读数据库,并存下结果。

Write-through / Write-around写入策略

同时写 cache 和数据库——或者只写数据库。

Eviction (LRU / LFU)腾出空间

满了的时候,丢掉最久没碰的或用得最少的条目。

陷阱

Cache Stampedethundering herd

一个热门 key 过期——所有人同时冲向数据库。

Stale Read提供过去的数据

又快、又自信、还是错的。对金钱、库存、权限很危险。

What Not to Cache跳过清单

变化快的、敏感的、冷门的。

用好它

Cache Target该缓存什么

经常被读、生成昂贵、很少变化。

Staleness Budget多旧才算可以

事先决定数据可以有多旧。它决定 TTL 和失效。

caching 用一点内存换来巨大的速度,代价是有提供过去数据的风险。做得好,它就是刻意的:对的答案,配上你有意选定的 staleness budget。

速成课结束 · 7 章 · 20 个概念

接下来是练习:找出你做过的东西里最慢的重复操作——一次查询、一次 API 调用、一个算出来的页面——在它前面放一个带明确 TTL 的小 cache。然后观察 hit ratio 和延迟的变化。你一测量,这笔交易就变得真实了。但要把一个想法放在所有想法之上:caching 只是记住一个昂贵的答案,而不是把活儿重做一遍——而每个难点,其实都是同一个被诚实问出的问题:我记下的东西,什么时候不再是真的了?