TT Lab
开始
学习 学习路径 课程

Redis 与缓存

实现 cache-aside 并测命中率

在 TT Lab 中继续学习

目标

亲手实现 cache-aside,用数字确认未命中与命中的成本差异,并进一步加上负缓存和版本键失效。

为什么重要

cache-aside 是最常见的模式,所以代码在哪里都能复制到。真正难的是细节:写入时是更新缓存还是删除缓存,不存在的数据要不要缓存,以及需要一次性让全部数据失效时该怎么办。第一点——答案是删除而不是更新,原因在于两次写入交错时,更新可能留下旧值,而删除不会。第二点——反复查询不存在的键的流量,会穿过缓存,原样打在源数据上。这叫缓存穿透,用短 TTL 的负缓存来防范。第三点——商品类目整体变化时,把相关的键全部找出来删掉,会掉进追踪依赖关系的地狱。版本键把它变成了只需把一个版本数字加一。

步骤

  1. 在 127.0.0.1:8151 上启动 /opt/app/slowdb.py。GET /item/1 返回 200,GET /stats 返回 {"queries":<n>}。
  2. 在 /root/ca/cache.py 中编写 get_item(id)。缓存未命中时,回源查询,并以 300 秒存入 item:<id> 键。在 /root/ca/miss.out 中写入 source=origin ms=<정수>(占位符为整数),ms 必须大于等于 100。
  3. 再次查询同一个条目。在 /root/ca/hit.out 中写入 source=cache ms=<정수> origin_queries_delta=0(占位符为整数),ms 必须小于 50。
  4. /root/ca/update.py 在更新源数据之后,删除缓存键。源码中不能出现把更新后的值重新写进缓存的代码。运行之后 EXISTS item:1 必须是 0。
  5. 查询不存在的 item:9999 时,源数据返回 404,并把这个结果以 60 秒的生存时间缓存起来。第二次查询时回源查询次数不能增加,且 TTL item:9999 必须大于等于 1 且小于等于 60。
  6. 用 /root/ca/bench.py 执行 100 次查询(20 个不同的键),并在 /root/ca/hitratio.txt 中写入 hits=<n> misses=<n> ratio=<소수>(占位符依次为命中次数、未命中次数与小数)。ratio 必须大于等于 0.75。
  7. 把缓存键改成 v<버전>:item:<id>(占位符依次为版本号与 id)的形式,并用 ver:item 键管理版本。提升版本后再查询,回源查询次数必须增加。在 /root/ca/version.out 中写入 old_ver=<n> new_ver=<n> refetched=true。

参考

启动慢速源数据服务

在 127.0.0.1:8151 上启动 /opt/app/slowdb.py。GET /item/1 返回 200,GET /stats 返回 {"queries":<n>}。

/opt/app/slowdb.py 每次查询都会故意耗费时间。它还会告知自己收到了多少次查询。

构造缓存未命中的路径

在 /root/ca/cache.py 中编写 get_item(id)。缓存未命中时,回源查询,并以 300 秒存入 item:<id> 键。在 /root/ca/miss.out 中写入 source=origin ms=<정수>(占位符为整数),ms 必须大于等于 100。

缓存里没有,就去源端走一趟,放进缓存再返回。第一次查询必然是慢的。

让第二次查询由缓存处理

再次查询同一个条目。在 /root/ca/hit.out 中写入 source=cache ms=<정수> origin_queries_delta=0(占位符为整数),ms 必须小于 50。

再次查询同一个键时,不应再回源。确认回源查询次数没有增加。

更新时删除缓存

/root/ca/update.py 在更新源数据之后,删除缓存键。源码中不能出现把更新后的值重新写进缓存的代码。运行之后 EXISTS item:1 必须是 0。

是删除,而不是更新。原因在阅读资料中。下一次查询会自行补上最新的值。

对不存在的键做负缓存

查询不存在的 item:9999 时,源数据返回 404,并把这个结果以 60 秒的生存时间缓存起来。第二次查询时回源查询次数不能增加,且 TTL item:9999 必须大于等于 1 且小于等于 60。

反复查询不存在的键,每次都会回源。用较短的生存时间,把“不存在”也缓存起来。

测量命中率

用 /root/ca/bench.py 执行 100 次查询(20 个不同的键),并在 /root/ca/hitratio.txt 中写入 hits=<n> misses=<n> ratio=<소수>(占位符依次为命中次数、未命中次数与小数)。ratio 必须大于等于 0.75。

分别统计命中和未命中,得出比例。数值会随请求分布而不同。

用版本键让整体失效

把缓存键改成 v<버전>:item:<id>(占位符依次为版本号与 id)的形式,并用 ver:item 键管理版本。提升版本后再查询,回源查询次数必须增加。在 /root/ca/version.out 中写入 old_ver=<n> new_ver=<n> refetched=true。

把版本埋进键名,只要提升版本,就再也没有人去找旧键。不必亲自去删除。