Memcached
Memcached 是开源的高性能内存缓存软件,用于在内存空间中临时缓存数据库中的各类数据,减少业务对数据库的直接高并发访问,提升数据库访问性能。
适用场景:数据库查询结果缓存、Session 会话共享、简单的键值对缓存。适合读多写少、数据结构简单、不要求持久化的场景。当需要丰富的数据结构或数据持久化时,应优先考虑 Redis。
核心要点
高性能内存缓存系统,包括Slab Allocation、LRU、分布式集群
核心特性
| 特性 | 说明 |
|---|---|
| 基于内存 | 完全基于内存缓存 |
| 分布式 | 节点之间相互独立 |
| C/S 架构 | C 语言编写,约 2000 行代码 |
| 异步 I/O | 使用 libevent 事件通知机制 |
| KV 存储 | key/value 键值对存储 |
| 无持久化 | 重启数据丢失 |
| LRU | 内存满时自动删除过期数据 |
| 过期机制 | 访问时检查 key 时间戳判断过期 |
数据限制
| 参数 | 限制 |
|---|---|
| key 最大长度 | 250 个字符 |
| 单个 item 最大 | 1MB |
| 过期时间最大 | 30 天 |
数据大于 1MB 时,可以考虑在客户端压缩或拆分到多个 key 中。
典型应用场景
作为数据库前端缓存
| 类型 | 说明 |
|---|---|
| 完整缓存 | 事先放入内存,如商品分类、商品信息 |
| 热点缓存 | 只缓存经常访问的数据 |
Session 会话共享
用于集群环境下的 session 共享。
工作流程
- 请求访问
- 查询 Memcached
- 命中则返回缓存数据
- 未命中则查询数据库,写入缓存后返回
缓存更新:数据更新时通知 Memcached 失效旧数据,高并发场合可预先推送更新数据到缓存。
秒杀等高并发业务需要事先预热,秒杀只是获取资格,不是瞬间完成。
内存管理
Slab Allocation 机制
问题:malloc/free 容易产生内存碎片
Slab Allocation 原理:
- 将内存分割成特定长度的内存块(chunk)
- 相同大小的 chunk 分成组(slab class)
- chunk 不会释放,可重复利用
| 术语 | 说明 |
|---|---|
| Page | 分配给 Slab 的内存空间,默认 1MB |
| Chunk | 用于缓存记录的内存空间 |
| SlabClass | 特定大小 chunk 的组 |
不同 slab 的 chunk 大小不等,如果 item 大小与 chunk 不匹配,会浪费内存。
为什么不用 malloc/free?
- 容易产生内存碎片
- 加重 OS 内存管理负担
- 最坏情况导致 OS 比 memcached 还慢
LRU 与过期机制
- 内存不够时,过期的 slabs 优先被替换
- 然后是最老的未被使用的 slabs
- 过期时间最大支持 30 天,到期后自动失效
分布式集群
分布式实现方式
程序端实现:程序加载所有 memcached IP 列表,对 key 做 hash(一致性哈希算法)
负载均衡器实现:负载均衡器做一致性哈希
一致性哈希优势:每个对象只请求一个对应服务器,节点宕机时重新分配比例最低。
两阶段哈希
- 阶段一:客户端计算 key 哈希值,选择节点
- 阶段二:节点内部哈希算法查找真正的数据
冗余与容错
Memcached 设计本身不带冗余机制。节点失效时从数据源重新获取数据,可以增加更多节点减少影响,或使用热备节点。
| 方案 | 说明 |
|---|---|
| 忽略 | 失效节点恢复前,其他节点应对影响 |
| 移除节点 | 会导致缓存不可用 |
| 热备节点 | 接管失效节点 IP,防止哈希紊乱 |
| 一致性哈希 | 添加/移除节点不影响哈希结果 |
线程模式
Memcached 1.2+ 支持多线程:充分利用多个 CPU,CPU 之间共享缓存数据,使用简单锁机制保证更新互斥。
负载较重、拥有大规模硬件的网站建议启用多线程。
身份验证
没有内置身份验证机制,设计哲学是轻量级、快速。
限制访问方式:防火墙限制、监听 unix domain socket。
原子性
单个命令完全原子,命令序列不原子。使用 gets 和 cas 保证一致性:
gets key # 获取值和 CAS 唯一标识(unique_id)
cas key 0 0 100 <unique_id> # 仅当 unique_id 未变化时才写入
valueCAS(Check and Set):获取 item 唯一标识,修改后用 cas 命令写回,如果唯一标识变化则写操作失败。
Session 共享方案
| 方案 | 说明 |
|---|---|
| 粘性 Session | Nginx 每次转发到同一服务器 |
| Session 复制 | 广播到所有集群服务器 |
| Session 共享 | 使用 Redis/Memcached 缓存 |
| Session 持久化 | 存储到数据库 |
与 Redis 对比
| 对比项 | Memcached | Redis |
|---|---|---|
| 数据类型 | 只支持简单 KV | 支持 list/set/zset/hash |
| 持久化 | 无 | 支持 RDB/AOF |
| 宕机恢复 | 缓存全失效 | 自动加载持久化数据 |
| key 长度 | 250 字符 | 512KB |
| value 大小 | 最大 1MB | 可配置更大 |
| 线程模型 | 多核支持(1.2+) | 单线程 |
| 内存管理 | Slab Allocation | 包装的 malloc/free |
| 数据一致性 | CAS 保证 | 单线程保证 |
小数据(<100KB)Redis 单核性能更高,大数据(>100KB)Memcached 多核性能更高。
批量导入导出
不推荐批量操作:Memcached 是非阻塞服务器,批量操作可能导致服务暂停,数据在导入导出期间可能变化。
适用场景:大量不变化的数据、需要快速 warm up 缓存。
不要依赖 Memcached 做灾备,宕机会导致所有缓存数据失效。
常见问题
缓存雪崩
大量 key 同时过期导致数据库压力骤增。解决:在过期时间上加随机偏移量,避免同一时刻集中失效。
内存碎片
Slab Allocation 机制可能导致内存浪费(item 大小与 chunk 不匹配)。使用 stats slabs 命令查看各 slab 的使用率,调整 slab 的增长因子(-f 参数)。
连接数耗尽
检查客户端连接池配置,确认是否正确释放连接。使用 stats 命令查看当前连接数(curr_connections)。