缓存02:Memcached
从简单 KV 缓存、内存管理到与 Redis 选型对比,拆解 Memcached 在简单缓存场景下的优势和局限。
Memcached 不是”功能丰富的缓存”,而是”极致简单的 KV 缓存”。它的核心场景是简单 Key-Value 缓存、Session 共享、跨语言共享数据,支持内存级读写、LRU 淘汰和 slab 内存管理,适合简单缓存、跨语言 Session、临时数据共享。
这篇文章围绕三个核心点:简单 KV 缓存、内存管理、与 Redis 选型,把 Memcached 的设计逻辑和适用场景讲清楚。
为什么选 Memcached,而不是 Redis
先定位清楚:Memcached 的核心场景是简单 KV 缓存,只支持 String 类型,不支持复杂数据结构,适合 Session 共享、跨语言缓存、临时数据存储。
与 Redis 的核心差异:
| 维度 | Memcached | Redis |
|---|---|---|
| 数据结构 | 只支持 String | 5 种基础 + 4 种高级 |
| 持久化 | 不支持,重启丢数据 | RDB + AOF,支持数据恢复 |
| 集群 | 不原生支持,需客户端分片 | 主从、哨兵、Cluster 三种模式 |
| 分布式锁 | 不支持 | 支持(SETNX) |
| 内存管理 | slab 分配,内存碎片少 | jemalloc,内存碎片较多 |
| 线程模型 | 多线程,性能稳定 | 单线程,高并发时性能下降 |
| 协议 | 自定义协议,客户端较少 | RESP 协议,客户端丰富 |
| 跨语言 | 客户端跨语言(C、Java、Python、PHP) | 客户端丰富,但以 Java/Go 为主 |
简单判断:简单 KV 缓存、跨语言 Session、不需要持久化 → Memcached 合适。复杂数据结构、分布式锁、持久化、集群 → Redis 合适。
核心架构:多线程 + slab 内存管理
Memcached 的架构简单,但有两个设计点值得讲:多线程和 slab 内存管理。
多线程模型
Memcached 是多线程模型:
- 主线程:监听端口,接收连接,分配给工作线程
- 工作线程:处理请求,读写内存,响应客户端
多线程的好处:高并发时性能稳定,不会像 Redis 单线程那样因某个慢命令阻塞整个服务。代价:锁竞争,多线程访问共享数据需要加锁。
Memcached 的锁粒度:
- 全局锁:早期版本,全局锁,性能差
- 分段锁:新版本,每个 slab 类一个锁,减少锁竞争
多线程模型适合简单 KV 操作,不适合复杂数据结构(如 Redis 的 ZSet、Hash)。
Slab 内存管理
Memcached 的内存管理采用 slab 分配器:
- slab class:按固定大小划分内存块,如 slab class 1 存储 96B 的 item,slab class 2 存储 120B 的 item
- page:每个 slab class 分配多个 page(默认 1MB),page 内切分成多个 chunk
- chunk:存储实际数据,大小固定,根据 item 大小选择合适 chunk
slab 的好处:减少内存碎片,分配和释放内存效率高。代价:内存浪费,chunk 大小固定,item 大小不足 chunk 时浪费空间(如 item 50B,chunk 96B,浪费 46B)。
内存分配策略:
item 大小 → 选择最接近的 slab class → 分配 chunk 存储
如果某个 slab class 的内存用完,会从其他 slab class 的 LRU 尾部淘汰 item,释放 chunk。
LRU 淘汰策略
Memcached 使用 LRU(Least Recently Used)淘汰策略:
- 每个 slab class 维护一个 LRU 链表
- 新 item 插入链表头部,访问 item 时移到头部
- 内存不足时,淘汰链表尾部的 item
LRU 淘汰的坑:缓存污染。大 Value 占用大量 chunk,可能挤掉小 Value 的热点数据。建议控制 Value 大小,避免单个 item 过大。
简单 KV 缓存:Memcached 的唯一能力
Memcached 只支持 String 类型,不支持复杂数据结构。核心命令:
| 命令 | 说明 |
|---|---|
set key value | 存储 KV,覆盖已存在的 Key |
get key | 获取 Value |
add key value | 存储 KV,Key 已存在则失败 |
replace key value | 更新 Value,Key 不存在则失败 |
delete key | 删除 Key |
incr key value | 数值递增,适合计数器 |
decr key value | 数值递减 |
Memcached 不支持事务、Lua 脚本、发布订阅,只做简单 KV 缓存。
与 Redis String 的差异
| 维度 | Memcached String | Redis String |
|---|---|---|
| 最大 Value | 1MB(可配置) | 512MB |
| 数值操作 | INCR/DECR,简单 | INCR/DECR/INCRBY/DECRBY |
| 过期时间 | 支持,精确到秒 | 支持,精确到毫秒 |
| 序列化 | 客户端负责 | 客户端负责 |
Memcached 适合存储简单字符串、序列化后的对象、Session 数据。
Session 共享:Memcached 的典型场景
Memcached 的典型场景是跨语言 Session 共享:
- 用户登录:Web 服务器将 Session 写入 Memcached
- 请求路由:负载均衡将请求路由到任意 Web 服务器
- Session 读取:Web 服务器从 Memcached 读取 Session
Memcached 的跨语言客户端(C、Java、Python、PHP、Ruby)让它在多语言环境下成为 Session 共享的选择。
配置示例(Java + Spring Session + Memcached):
<bean class="org.springframework.session.data.memcached.config.annotation.web.http.MemcachedHttpSessionConfiguration">
<property name="memcachedClient" ref="memcachedClient"/>
</bean>
Redis 也支持 Session 共享,但 Memcached 更轻量、多语言客户端更成熟。
与 Redis 选型对比
| 场景 | Memcached | Redis |
|---|---|---|
| 简单 KV 缓存 | 合适,性能稳定 | 合适,功能更多 |
| Session 共享 | 合适,跨语言客户端成熟 | 合适,功能更多 |
| 复杂数据结构 | 不支持 | 支持(Hash、ZSet、List) |
| 分布式锁 | 不支持 | 支持(SETNX) |
| 持久化 | 不支持 | 支持(RDB + AOF) |
| 集群 | 不原生支持,需客户端分片 | 支持(主从、哨兵、Cluster) |
| 计数器 | 支持,但功能简单 | 支持,INCRBY/DECRBY |
简单判断:只做简单 KV 缓存、不需要持久化、不需要复杂数据结构 → Memcached 合适。需要复杂数据结构、分布式锁、持久化、集群 → Redis 合适。
常见误区
| 误区 | 真实情况 |
|---|---|
| Memcached 比 Redis 性能高 | 性能接近,Memcached 多线程在高并发时更稳定,Redis 单线程在简单命令时更快 |
| Memcached 不支持过期 | 支持,精确到秒 |
| Memcached 可以存储复杂对象 | 只支持 String,复杂对象需要序列化后存储 |
| Memcached 可以做分布式锁 | 不支持,只能用 add 做简单锁,不推荐 |
项目判断
引入 Memcached 时,先回答三个问题:
- 缓存场景:是否只需要简单 KV 缓存,不需要复杂数据结构?
- 持久化:是否不需要持久化,重启丢数据可接受?
- 跨语言:是否需要跨语言客户端(如 Java + PHP 共享 Session)?
如果只需要简单 KV 缓存、不需要持久化、需要跨语言共享 → Memcached 合适。如果需要复杂数据结构、分布式锁、持久化、集群 → Redis 合适。
线上排查 Memcached 问题:
- 内存不足:检查 slab 内存分配,调整
maxbytes参数,控制 Value 大小 - 命中率低:检查缓存策略,是否 Value 过大导致淘汰,增加内存或调整 LRU
- 连接数满:检查连接数限制
maxconns,增加连接数或优化客户端连接池