团队把 Memcached 换成 Redis「因为功能更多」,半年后 AOF
rewrite 与 fork 延迟进了 on-call 台账,而业务其实只需要
get/set 旁路缓存——这类
工具升格 在 缓存架构
的 Cache-Aside 场景里并不少见。Memcached 与 Redis
不是同一抽象层的两个实现;前者是
多线程纯缓存,后者是
单线程执行命令的数据结构服务器(可选持久化)。
本文是「Redis / 缓存内核」系列第 14 篇(共 16 篇)。→ 系列目录
版本锚定:Redis OSS 7.4 / 8.x;Memcached 以经典 1.6.x 线程+slab 模型为对照(具体发行版以部署为准)。
一、问题定义:谁在优化什么
Nishtala et al. Scaling Memcache at Facebook(NSDI 2013)把 Memcached 定位为 大规模 Web 服务的 DRAM 聚合层:配合 MySQL 等权威库,用 leases 缓解击穿、用 Gutter 做集群部分失效降级、用 客户端一致性哈希 做水平扩展。论文 measured 的是 端到端 Web 工作负载 下的延迟与失效模式,不是「Memcached vs Redis 单线程 benchmark 谁 QPS 更高」——后者缺少论文级公开对照,本篇 不给出伪造吞吐排名。
Redis 的设计目标(官方 Overview 与 antirez 历史设计笔记)是 丰富数据结构 + 可选 durability:List/Hash/ZSet/Stream 留在内存里直接操作,RDB/AOF 把进程内状态接到 持久化语义。这是 不同产品类 的分叉,不是「Memcached 加持久化」。
二、架构对照
flowchart TB
subgraph MC["Memcached"]
W1["worker thread"]
W2["worker thread"]
S["slab class allocator"]
W1 --> S
W2 --> S
end
subgraph RD["Redis 7.4/8.x"]
M["main thread: command exec"]
IO["optional IO threads: read/write"]
E["redisObject encodings"]
P["optional RDB/AOF"]
IO --> M
M --> E
M --> P
end
| 维度 | Memcached | Redis |
|---|---|---|
| 执行模型 | 多 worker 线程处理连接 | 主线程执行命令;IO threads 仅卸载读写(第 2 篇) |
| 值表示 | opaque blob;无类型系统 | type + encoding 多态(第 3–5
篇) |
| 内存分配 | slab class 预分配固定尺寸页 | jemalloc/tcmalloc + 对象编码;紧凑结构可省内存 |
| 过期 | LRU 于 slab 内;惰性 | 全局 expires + 主动采样(第 7 篇) |
| 分片 | 客户端一致性哈希 | 单机 / Sentinel / Cluster slot(第 13 篇) |
| 持久化 | 无(重启空) | RDB / AOF / 混合 |
| 典型协议 | 文本二进制 Memcache | RESP |
三、Slab 分配器:吞吐与碎片的两面
Memcached 把内存划成 slab class(按 chunk 尺寸分级),每类一批页;分配/释放主要在固定尺寸 chunk 上完成,避免通用分配器锁争用,利于 多线程水平扩展 CPU。
代价:
- 尺寸离散:值略大于某 class 即占用更大
chunk,内部碎片
可见;
stats slabs可观察(需真实实例,本篇不粘贴伪造输出)。 - 无细粒度结构:无法把一个小 Hash 嵌在对象头里——一切皆是 blob。
- 扩容缩容:class 划分在启动参数中大致固定,调优偏运维经验。
Redis 用编码(listpack、intset 等)在 小集合 上省内存,但编码升级与 单线程执行 使 CPU 饱和点与 Memcached 多线程不同;比较二者必须声明 值大小分布、命令 mix、是否持久化,否则结论不可移植(WRITING_GUIDE 口径)。
四、Facebook 论文与 Cache-Aside 工程
architecture/17 已讲 Cache-Aside、击穿、雪崩;NSDI’13 的增量在于 工业规模下的失效语义:
| 机制 | 论文要点 | 与 Redis 实践对照 |
|---|---|---|
| Leases | 短期租约,防止陈旧回填 | Redis 无内建 lease;可用逻辑 TTL + 版本号或 Redlock 争议方案(非本篇展开) |
| Gutter | 部分 Memcached 挂掉时短期全局降级 | 需应用/代理层设计;非 Redis 内建 |
| 一致性哈希 + 副本 | 降低热点与节点抖动 | Redis Cluster 用 slot + 主从,范式不同 |
论文隐含假设:权威数据在 MySQL,缓存可随时丢弃重建。Redis 若启用 AOF/RDB 且被当作 半权威 store(计数、会话、队列),故障模型从「穿透回源」变成「恢复 + fsync + 复制滞后」——运维复杂度上一个数量级。
五、何时纯缓存(Memcached)更合理
下列场景 倾向 Memcached 或等价纯缓存,而非默认 Redis:
- 严格 Cache-Aside:值永远可回源重建;丢失缓存等于 miss,无业务副作用。
- 值形态单一:大 blob 或小字符串;不需要 Hash/ZSet/Stream 原子操作。
- 多核线性扩展读吞吐:CPU 为瓶颈、值尺寸适配 slab、无持久化硬需求。
- 希望运维面极简:无 fork、无 AOF rewrite、无复制 backlog 规划(仍要处理哈希扩缩容)。
- 团队已落地 Facebook 式多层缓存:MC 作 L2,本地 Caffeine 作 L1(见 storage/66)。
六、何时 Redis 更合理
- 数据结构即 API:排行榜
ZINCRBY、去重SET、队列LPUSH/BRPOP、Stream 消费组。 - TTL 精细 +
内存策略:
maxmemory+ 近似 LRU/LFU(第 8 篇)。 - 需可控 durability:RDB 快照、AOF everysec、混合(第 9–11 篇);接受 fork/fsync 代价。
- 原生复制 / Cluster:slot 事务边界清楚,替代自研一致性哈希客户端(第 12–13 篇)。
- 模块生态:Redis Stack / 模块(JSON、Search)——边界见 第 16 篇。
七、协议与客户端栈差异
Memcached 常见文本协议与二进制协议;Redis 统一 RESP3/RESP2。对 libevent redis-lite 一类玩具实现,RESP 入门足够;生产则要分清:
| 话题 | Memcached | Redis |
|---|---|---|
| 连接复用 | 多线程下连接亲和常见 | 单连接 pipeline 友好;注意 CLIENT PAUSE
等管理命令 |
| CAS | gets/cas 防并发写覆盖 |
WATCH/MULTI 或
Lua;语义更重 |
| TTL 精度 | 秒级过期 | 秒/毫秒(依版本与命令) |
| 集群客户端 | 一致性哈希库 | Cluster slot 表 + MOVED 处理 |
NSDI’13 还强调 invalidation daemon(McRouter 等)做跨机房一致性;Redis 侧常用 发布订阅 / keyspace notification 做失效广播,复杂度和可靠性需自行论证——两种都不是「开箱即全局一致」。
八、常见误解
| 误解 | 事实 |
|---|---|
| 「Redis 总是比 Memcached 快」 | 无统一 benchmark;多线程 Memcached 在纯
get/set、合适 slab 时可能更省 CPU;Redis
优势常在 结构与命令语义 |
| 「Memcached 不能做大缓存」 | Facebook 论文是反例;关键是 集群分片 + 失效策略,非单机上限 |
| 「上了 Redis 就可以不要 DB」 | 持久化保证的是 Redis 自身崩溃,不是业务级事务与权威建模;Cache-Aside 仍应以 DB 为源 |
| 「slab 一定比 jemalloc 省内存」 | slab 内部碎片在大值分布不均时很明显;Redis 紧凑编码在小对象场景常更省 |
| 「多线程一定优于单线程」 | Redis 用 IO threads 减轻网络栈负担,但 命令仍串行;高争用写仍会打满主线程 |
九、学术谱系、争论与开放问题
谱系:OSDI’00 前后 Web 缓存实践 → Memcached(Brad Fitzpatrick)→ NSDI’13 Facebook 工业尺度 → Redis 数据结构服务器 → 云厂商托管双轨。
争论:
- 单一缓存 vs 多模型服务器:「只用 Memcached + DB」派强调简单与故障域;「Redis 一站式」派承担持久化与集群运维,换取产品迭代速度——无 universally 正确,取决于失效代价。
- 客户端哈希 vs Cluster slot(链 algorithms/11):Memcached 传统前者;Redis Cluster 用后者——迁移与多 key 语义不同,不能互换客户端。
开放问题:
- Redis 多线程执行命令(7.0+ 部分命令路径、社区持续讨论)是否会改写与 Memcached 的 CPU 模型对比——需按版本 release note 逐特性核对,本篇不预判终局。
- 在 相同 Cache-Aside 下,leases 思想能否低成本移植到 Redis 旁路层,而不过度依赖 Redlock 类争议方案。
十、小结
三句话小结:
- Memcached 是 多线程 + slab 的纯缓存;Redis 是 单线程执行命令 + 多态编码 + 可选持久化 的数据结构服务器——产品类不同,勿用功能数量代替场景匹配。
- Facebook NSDI’13 教的是 大规模 Cache-Aside 的失效与降级,不是 Redis 替代论;权威在 DB 时 Memcached 往往更轻。
- 需要结构操作、durability、复制/Cluster 时选 Redis;只需可重建 blob 缓存、要多核线性读时认真考虑 Memcached。
下一篇收束运维信号:生产排障。
参考资料
论文(A 级)
- Nishtala, R. et al., Scaling Memcache at Facebook, NSDI 2013(leases、Gutter、一致性哈希与失效语义)。
文档与源码(A 级)
- Memcached Wiki / Documentation, Memory Allocation(slab class)。
- Redis Documentation, Overview、Persistence、IO threads。
- Redis OSS 7.4
src/object.c、src/ae.c(对照执行模型)。
辅助(B 级)
返回 系列目录 | 上一篇:Cluster | 下一篇:生产排障
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Redis / 缓存内核】缓存服务器全景:HashMap · Memcached · Redis · 嵌入 KV
定位进程内 HashMap、Memcached、Redis 与 RocksDB 嵌入 KV 在内存缓存栈中的生态位;划分与 architecture/17、storage/66、linux/redis-source 的分工,并给出本系列 16 篇阅读地图。
【Redis / 缓存内核】对象模型与 SDS:redisObject · type · encoding
拆解 Redis 7.4/8.x 的 redisObject 多态层与 SDS 二进制安全字符串:type/encoding/refcount 如何分工、字符串四种编码的升级路径、OBJECT ENCODING 能读到什么,以及紧凑表示与统一抽象之间的工程争论。
【Redis / 缓存内核】事件循环 · 对象编码 · 淘汰 · RDB/AOF · Cluster
补齐内存 KV / 缓存服务器内核层:从 ae 事件循环与 redisObject 多态编码,到 TTL/maxmemory 近似淘汰、RDB/AOF 持久化语义,再到复制、Cluster hash slot 与 Memcached 对照。
【Redis / 缓存内核】事件循环与 I/O:ae · 客户端缓冲 · IO threads
按 Redis OSS 7.4/8.x 源码拆解 aeEventLoop 文件/时间事件、beforeSleep 钩子与客户端 querybuf/reply 路径,并精确说明 IO threads 只卸载读写与解析、命令仍在主线程执行。