# 《深入分布式缓存:从原理到实践》知识点全解 > AI生成|2026年10月29日|codebuddy|原书:于君泽《深入分布式缓存:从原理到实践》,机械工业出版社,2018-01-01 > **我的阅读**:进度 100%|累计 5小时31分钟|开始 2020-04-13|最近 2020-09-02|读完 2020-04-20 **一句话总结**:把"性能不够就加个 Redis"这种口头禅,变成一套能推理、能验证的工程决策——先讲清缓存为什么有效、该放在哪一层,再拆开 Memcached / Redis / Tair / EVCache / Aerospike 的实现取舍,最后落到社交关系与 Feed 流这类真实业务里,缓存架构到底怎么长出来。 **适合谁 / 前置知识**:适合已经写过后端业务、被"数据库扛不住"咬过一口的工程师,以及需要做缓存选型、容量规划和故障复盘的架构师。前置要求不高但要真:懂 HTTP 与浏览器缓存的基本机制、会写多线程代码、知道 TCP 与哈希表怎么回事、听说过 CAP。不适合零基础读者——书里对 Redis 命令、JVM、Linux 内存分配都不做铺垫;也不适合只想找"Redis 命令手册"的人,本书重心在设计与权衡,不在 API 大全。 ## 一、全书地图 | 板块 | 章节 | 回答的问题 | | --- | --- | --- | | 建立判断 | 第1章 缓存为王 | 缓存是什么、为什么有效、按位置能分成哪几层 | | 补理论 | 第2章 分布式系统理论 | CAP/BASE、一致性、共识与设计策略,给后面的架构讨论提供语言 | | 拆黑盒 | 第3章 动手写缓存 | 缓存规范长什么样,一个缓存框架必须解决哪些问题 | | 单机缓存 | 第4章 Ehcache与Guava Cache | 进程内缓存的能力边界与集群化尝试 | | 集中式缓存 | 第5–6章 Memcached 及周边 | 内存怎么管、客户端怎么分片、Twemcache/Twemproxy/Mcrouter 各补哪块短板 | | 集中式进阶 | 第7–8章 Redis、分布式Redis | 数据结构、单机处理逻辑、持久化;分片、复制、故障转移、Cluster | | 工业化实现 | 第9–11章 Tair、EVCache、Aerospike | 大规模场景下的架构、负载均衡、高可用与选型对比 | | 业务落地 | 第12–13章 社交与Feed | 关系、帖子、时间线如何从数据库迁到缓存;Feed 缓存模型与扩展 | 逻辑是一条"降维再升维"的线:第 1 章建立价值判断,第 2 章补齐理论词汇,第 3 章把缓存从"现成中间件"还原成"可自己构造的组件",第 4–11 章按"进程内 → 集中式 → 分布式 → 工业化"逐级放大规模与复杂度,第 12–13 章再把这些零件装回业务。读的时候可以跳过第 9–11 章的源码细节,但不要跳第 1、2、8 章——它们是后面所有取舍的依据。 ## 二、核心知识点 ### 1. 缓存是用空间换时间,赌的是局部性 **是什么**:把访问概率高的数据放到更快的介质上,用额外的存储与一致性成本,换取更低的延迟和更少的后端压力。 **为什么重要**:局部性(时间局部性、空间局部性)是缓存成立的前提;访问越随机、越一次性,缓存收益越趋近于零,甚至会因为维护成本变成负收益。 **怎么用**:先看访问分布——热点是否集中、重复率多高、数据多大、能容忍多旧。命中率从 90% 提到 95%,后端压力是减半而不是加 5%,这类非线性要提前算。 **常见误区**:默认"加了缓存一定更快"。冷数据、写多读少、强一致场景,缓存只是多了一跳网络和一份不一致风险。 ### 2. 缓存按位置分层,不是只有 Redis **是什么**:书中把缓存分为客户端缓存(浏览器、本地存储)、网络中的缓存(CDN、反向代理)、服务端缓存(进程内、集中式、分布式)。 **为什么重要**:越靠近用户的缓存,命中一次省掉的链路越长;但越靠近用户,可控性越差、失效越难。真正的架构设计是"多层同时用",而不是选一层。 **怎么用**:静态资源用 CDN + 强缓存/协商缓存;热点配置用进程内缓存挡第一波;跨节点共享的数据用集中式缓存;极端热 key 再考虑本地副本。 **常见误区**:把"分布式缓存"当成唯一答案,忽略了 HTTP 缓存头、CDN 规则这些成本更低的一层。 ### 3. 进程内缓存与集中式缓存,是两种不同的动物 **是什么**:Ehcache、Guava Cache 把数据放在 JVM 堆内,纳秒到微秒级;Memcached、Redis 走网络,百微秒到毫秒级,但多节点共享。 **为什么重要**:进程内缓存没有网络开销,却有容量上限、GC 压力和"各节点各一份、天生不一致"的问题;集中式缓存解决共享,却引入网络分区与单点瓶颈。 **怎么用**:变化极少、体量小的字典类数据放本地(配短 TTL 或版本号刷新);需要强共享的会话、库存、计数器放集中式。Ehcache 的集群方案在书中有介绍,但复杂度高,今天更常见的是"本地 + 集中"两级。 **常见误区**:以为本地缓存"没有一致性问题"。它只是把不一致藏在了每台机器的堆里。 ### 4. 一个缓存框架必须回答的六个问题 **是什么**:第 3 章把缓存拆成规范与实现:存储结构、过期与淘汰、并发控制、统计监控、序列化、失效通知。业界常引用的规范是 JSR-107(JCache)。 **为什么重要**:能自己写出最小可用版本,才看得懂中间件的取舍。所有缓存产品的差异,最终都落在这几个问题的不同答案上。 **怎么用**:评审自研缓存时逐项过:淘汰是 LRU/LFU/FIFO?过期是惰性删除还是定时清理?并发下如何避免重复回源?命中率、加载耗时有没有暴露? **常见误区**:只关注 get/put 接口,忽略统计与失效传播——上线后既不知道效果,也没法主动清理。 ### 5. 更新策略决定一致性下限 **是什么**:常见模式有 Cache Aside(读时回填、写时删缓存)、Read/Write Through(缓存层代理后端)、Write Behind(异步批量写回)、Refresh Ahead(提前刷新)。 **为什么重要**:缓存与数据库双写没有原子性,选哪种模式等于选择"接受哪种不一致、接受多久"。 **怎么用**:绝大多数业务用 Cache Aside:先更新库、再删缓存,删除失败要有补偿(消息队列/重试表);删两次(更新前后各一次)可缓解并发读写导致的旧值回填。强一致要求高时,宁可读库或加分布式锁。 **常见误区**:"更新库再更新缓存"看似对称,实际在并发下更容易留下脏数据;以及以为设了 TTL 就万事大吉。 ### 6. 穿透、击穿、雪崩,是三件不同的事 **是什么**:穿透=查不存在的数据,请求全部落到后端;击穿=单个热点 key 失效瞬间大量并发回源;雪崩=大批 key 同时过期或缓存层整体不可用。 **为什么重要**:三者成因不同,药方也不同,混为一谈就会用错手段。 **怎么用**:穿透用布隆过滤器或缓存空值(短 TTL);击穿用互斥重建(只放一个请求回源)或逻辑过期(异步刷新,旧值兜底);雪崩用 TTL 加随机抖动、多级缓存、熔断限流与集群高可用。 **常见误区**:用"给所有 key 加长 TTL"应对雪崩——只是把爆炸推迟,且放大了不一致窗口。书中那句"解决雪崩最好的办法是不发生雪崩",说的是容量评估与预案要在事前做。 ### 7. CAP 与 BASE 是缓存取舍的语法 **是什么**:分布式系统在一致性、可用性、分区容忍之间无法同时满足;BASE 用最终一致换取可用。第 2 章还覆盖了一致性模型、2PC/3PC、Paxos 等共识相关内容。 **为什么重要**:缓存天然是"用一致性换性能"的产物,说不清放弃的是哪一种一致性,就无法评审方案。 **怎么用**:先给数据分级:钱、库存走强一致或近实时;昵称、头像、计数允许最终一致。再据此决定缓存 TTL、是否双删、是否需要版本号。 **常见误区**:把 CAP 当成"三选二"的静态公式。网络分区是常态,真正要决定的是分区发生时保 A 还是保 C。 ### 8. 分片决定扩容成本 **是什么**:数据按规则分布到多节点:取模简单但扩容几乎全量搬迁;一致性哈希把节点和数据映射到环上,只影响相邻区间;预分片/槽位(如 Redis Cluster 的 16384 槽)把"数据归属"和"物理节点"解耦。 **为什么重要**:分片方案一旦上线,迁移成本极高;选型时就要想清楚未来扩几倍。 **怎么用**:中小规模用客户端一致性哈希(如 Twemproxy/Mcrouter 提供的代理分片);大规模用带槽位的集群方案;务必预留虚拟节点解决倾斜。 **常见误区**:忽略热点 key——哈希再均匀,也救不了单条爆款数据;此时需要 key 拆分或本地副本。 ### 9. 复制与故障转移,是可用性的另一半 **是什么**:主备复制提供冗余,故障转移(failover)负责在主节点挂掉后选主并切换;Redis 侧有主从复制、哨兵、Cluster 三种递进形态。 **为什么重要**:只有分片没有复制,单节点故障就是数据丢失;只有复制没有自动转移,恢复靠人肉,RTO 不可控。 **怎么用**:异步复制接受少量丢失,同步/半同步降低丢失但抬高写延迟;哨兵负责监控与选主,Cluster 自带故障转移;切换要防脑裂(多数派确认)。 **常见误区**:把"从节点有数据"当成"数据安全"。异步复制下,主节点宕机前未同步的写就是丢了。 ### 10. Memcached:简单的极致与它的边界 **是什么**:多线程、无持久化、value 为不透明字节的纯内存 KV;用 slab allocator 预分配内存块减少碎片,淘汰近似 LRU。 **为什么重要**:它把"缓存"这件事做到了最小面:没有数据结构、没有事务、没有落盘,因此稳定、可预测,也因此在复杂场景让位给 Redis。 **怎么用**:适合纯 KV、大流量、可丢失的页面片段与对象缓存;分片逻辑放在客户端(一致性哈希),或用 Twemproxy/Mcrouter 做代理层,后者还提供连接复用、失败重试、跨集群路由。 **常见误区**:slab 按 size class 分配,不同尺寸的 value 会各自占坑,更新后尺寸变化可能导致内存利用率下降;以及误以为它支持持久化。 ### 11. Redis:单线程 + 多数据结构 + 两种持久化 **是什么**:单线程事件循环处理命令,避免锁竞争;提供 string/list/hash/set/zset 等结构;持久化有 RDB(快照)与 AOF(追加日志),可混合使用。 **为什么重要**:单线程意味着单条慢命令(大 key、KEYS、全量 zset 操作)会阻塞所有请求;数据结构选错,复杂度会从 O(1) 退化到 O(n)。 **怎么用**:按访问模式选结构:排行榜用 zset,去重用 set,对象用 hash 控制字段粒度;持久化按"能丢多少"选 RDB/AOF 策略;批量用 pipeline 降 RTT。 **常见误区**:把 Redis 当数据库用而忽视内存成本;以及用单条超大 hash 存百万字段,导致删除、迁移、过期都变慢。 ### 12. Redis Cluster:槽位、重定向与运维代价 **是什么**:16384 个哈希槽分布在多个主节点,节点间用 gossip 交换状态,客户端收到 MOVED/ASK 后重定向。 **为什么重要**:它把分片、复制、故障转移打包成内置能力,但也带来多 key 操作受限、事务与 Lua 的跨槽问题。 **怎么用**:设计 key 时用 hash tag 把需要一起操作的 key 固定到同一槽;客户端要支持重定向缓存;扩容时按槽迁移,观察迁移期间的延迟。 **常见误区**:以为 Cluster 等于"无限扩展"。槽迁移、gossip 开销、跨槽限制都会在规模上来后变成真问题。 ### 13. Tair / EVCache / Aerospike:三条工业化路线 **是什么**:Tair 用 Config Server + Data Server 两层,把路由、负载均衡、高可用从客户端抽到控制面;EVCache 基于 Memcached 做多副本、跨可用区与写扩散,服务高吞吐读;Aerospike 采用索引在内存、数据在 SSD 的混合架构,强调强一致与低延迟,书中以广告业务为例。 **为什么重要**:同一道题有三种解法——控制面托管、副本冗余、存储介质分层。理解它们,比记住某个产品更有用。 **怎么用**:需要弹性与统一运维看控制面方案;读多写少、可容忍短暂不一致看多副本缓存;数据量超过内存、又要求稳定低延迟看混合存储。 **常见误区**:把 EVCache 当成"另一个 Redis"。它是特定公司在特定约束下的解法,脱离场景照搬会付出很高代价。 ### 14. 社交与 Feed:缓存模型跟着业务模型走 **是什么**:第 12 章拆解关系(关注/粉丝)、帖子、时间线三类数据的存储演进;第 13 章给出 Feed 的缓存模型:推模式(写时扩散到收件箱)、拉模式(读时聚合)、推拉结合。 **为什么重要**:Feed 的核心矛盾是"写扩散成本"与"读延迟"的对抗;粉丝量分布极不均,单一模式必然在头部用户或冷用户上失效。 **怎么用**:普通用户走推、大 V 走拉(读时合并);收件箱只存 id 列表,正文另存并做多级缓存;时间线分页用游标而非偏移量。 **常见误区**:一上来就全量推,头部账号一条动态写爆存储;或全量拉,读路径聚合几十个关注源导致尾延迟飙升。 ## 三、关键概念速查表 | 概念 | 一句话解释 | 用在哪儿 | | --- | --- | --- | | 命中率 | 命中请求 / 总请求 | 判断缓存是否值得继续投入 | | 局部性 | 刚访问过的数据很可能再被访问 | 解释缓存为什么有效 | | Cache Aside | 读时回填,写时删缓存 | 大多数业务系统的默认模式 | | Write Behind | 先写缓存,异步批量落库 | 写密集、可容忍丢少量数据 | | 缓存穿透 | 查不存在的数据,绕过缓存打库 | 用布隆过滤器/空值缓存 | | 缓存击穿 | 热点 key 失效瞬间并发回源 | 互斥重建、逻辑过期 | | 缓存雪崩 | 大量 key 同时失效或缓存层宕机 | TTL 打散、多级缓存、限流 | | 热 key | 单条数据承载远超均值流量 | 本地副本、key 拆分 | | 大 key | 体积或元素数过大的 value | 拆分、避免全量操作 | | 一致性哈希 | 节点与数据映射到环,减少迁移 | 客户端分片、代理分片 | | 虚拟节点 | 一个物理节点映射多个环上位置 | 缓解分片倾斜 | | 槽(slot) | 固定数量的分片单元 | Redis Cluster 扩容与路由 | | 脑裂 | 分区后出现两个主节点 | 选主需多数派确认 | | RDB / AOF | 快照 / 命令日志两种持久化 | 按可丢失窗口选择 | | slab allocator | 按尺寸分类预分配内存块 | Memcached 内存管理 | | 写扩散 | 发布时把内容写入粉丝收件箱 | 推模式 Feed | | 读聚合 | 读时合并多个关注源 | 拉模式 Feed | | 最终一致 | 短窗口内允许不一致 | 计数、Feed、画像类数据 | ## 四、可复用的方法/框架 **A. 缓存选型五问** 1. 数据能不能丢?能丢多久?(决定持久化与复制形态) 2. 读多还是写多?读写比多少?(决定更新模式) 3. 单条多大、总量多大?内存放得下吗?(决定进程内/集中式/混合存储) 4. 允许多旧?(决定 TTL 与一致性手段) 5. 谁来运维、故障时谁兜底?(决定自建还是托管) **B. 容量与收益估算四步** 1. 用访问日志算真实重复率,而不是拍脑袋估命中率; 2. 按"热点集中度"切分:Top N 数据占多少流量; 3. 用 `(1 - 命中率) × 总 QPS` 估算回源压力,确认后端扛得住; 4. 预留 30% 以上内存余量,给 value 变长和副本留空间。 **C. 上线前检查清单** - 每个缓存 key 都有 TTL,且过期时间加了随机抖动; - 回源路径有互斥或队列保护,数据库有连接池上限; - 缓存不可用时能降级(读库、返回兜底值、限流); - 双写失败有补偿,删除缓存有重试; - 监控覆盖命中率、回源 QPS、大 key、慢命令、内存使用率; - 有明确的预热方案与灰度顺序。 **D. 故障排查顺序** 先看命中率是否突降 → 再看是否有大 key/慢命令 → 再看是否集中过期 → 再看网络与主从延迟 → 最后看是否业务流量本身突变。顺序反了,容易在错误的方向上加机器。 ## 五、我的阅读轨迹 | 进度 | 累计时长 | 开始阅读 | 最近阅读 | 读完时间 | | --- | --- | --- | --- | --- | | 100% | 5小时31分钟 | 2020-04-13 | 2020-09-02 | 2020-04-20 | 5 个半小时读完全书,属于"通读一遍、建立地图"的强度,细节留存必然有限;且读完时间(2020-04-20)早于最近阅读(2020-09-02),说明中途有过回翻。复习建议: - **优先重读第 8 章(分布式 Redis)与第 2 章(分布式理论)**:这两章是"能不能做架构判断"的分水岭,且当年读得最快。 - **第 9–11 章按需查阅**:Tair、EVCache、Aerospike 的架构图与高可用设计适合当案例库,不必逐页重读;重点看"它们各自解决了什么约束"。 - **第 12–13 章结合当下业务重看**:Feed 的推拉结合模型对今天的推荐流、动态流仍然成立。 - 复习方式建议"以题带读":拿一个自己手上的缓存场景,按第四节 A/B/C 三张清单走一遍,再回书里找对应章节,比顺序重读效率高得多。 ## 六、行动清单 1. 给自己负责的一个缓存 Key 补上 TTL 随机抖动,并记录改动前后的回源 QPS。 2. 用一周访问日志算出真实命中率与 Top 100 热 key,和主观印象对一遍。 3. 给核心读路径加互斥重建或逻辑过期,做一次"热点 key 突然失效"的演练。 4. 梳理现有缓存的一致性策略,写清"哪些数据允许脏、最多脏多久"。 5. 检查是否存在大 key,列出拆分方案与迁移步骤。 6. 补全缓存监控面板:命中率、回源量、慢命令、内存水位、主从延迟。 7. 选一个业务场景,按"推/拉/推拉结合"三种 Feed 模型各画一版架构,比较写放大与读延迟。 8. 把本文第四节的四张清单固化进团队的设计评审模板。 ## 七、延伸阅读 1. **《Redis 设计与实现》(黄健宏)**——先读这本。它把 Redis 的内部结构、持久化、集群讲透,正好补上本书第 7–8 章"点到为止"的实现细节。 2. **《数据密集型应用系统设计》(Martin Kleppmann)**——第二本。在掌握具体中间件后,用它建立复制、分区、一致性与共识的系统框架,反过来审视本书第 2 章的理论。 3. **《大规模分布式存储系统:原理解析与架构实战》(杨传辉)**——第三本。当缓存不再是"旁路",而是要承担存储职责时,用它补齐存储引擎、一致性与运维的知识。 顺序逻辑:先"一个产品的深度"(Redis),再"一类系统的广度"(DDIA),最后"存储系统的工程厚度"(大规模分布式存储)。倒过来读容易空对空。