Memcached
Memcached 是开源的高性能内存缓存软件,用于在内存空间中临时缓存数据库中的各类数据,减少业务对数据库的直接高并发访问,提升数据库访问性能。
适用场景:数据库查询结果缓存、Session 会话共享、简单的键值对缓存。适合读多写少、数据结构简单、不要求持久化的场景。当需要丰富的数据结构或数据持久化时,应优先考虑 Redis。
核心要点
高性能内存缓存系统,包括Slab Allocation、LRU、分布式集群
核心特性
| 特性 | 说明 |
|---|---|
| 基于内存 | 完全基于内存缓存 |
| 分布式 | 节点之间相互独立,靠客户端哈希路由 |
| C/S 架构 | C 语言编写,轻量精悍 |
| 异步 I/O | 使用 libevent 事件通知机制(一个线程监听大量连接的事件回调,无需为每个连接开线程) |
| KV 存储 | key/value 键值对存储 |
| 无持久化 | 重启数据丢失 |
| LRU | 内存满时自动删除过期数据 |
| 过期机制 | 访问时检查 key 时间戳判断过期 |
数据限制
| 参数 | 限制 |
|---|---|
| key 最大长度 | 250 个字符 |
| 单个 item 最大 | 1MB |
| 过期时间最大 | 30 天 |
数据大于 1MB 时,可以考虑在客户端压缩或拆分到多个 key 中。
常用命令
| 命令 | 作用 | 示例 |
|---|---|---|
| set | 存储(不存在则新建,存在则覆盖) | set key 0 60 value(60 秒过期) |
| add | 仅当 key 不存在时存储 | add key 0 60 value |
| replace | 仅当 key 已存在时替换 | replace key 0 60 value |
| get / gets | 读取值 / 读取值+CAS 标识 | get key |
| delete | 删除 key | delete key |
| incr / decr | 数值自增 / 自减(要求值为整数) | incr key 1 |
| append / prepend | 尾部 / 头部追加 | append key "suffix" |
| stats | 查看运行统计(命中率、连接数等) | stats |
典型应用场景
作为数据库前端缓存
| 类型 | 说明 |
|---|---|
| 完整缓存 | 事先放入内存,如商品分类、商品信息 |
| 热点缓存 | 只缓存经常访问的数据 |
Session 会话共享
集群环境下用户请求可能落到任意一台机器,而 session 默认存在单机内存里,换一台机器就会丢失登录态。做法是把 session 统一存入 Memcached,所有节点读写同一份 session 数据,cookie 里的 session ID 成为唯一凭证。局限: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 都映射到一个 0~2^32-1 的环形空间上,key 顺时针找到的第一个节点就是存储节点。相比普通取模(hash % 节点数),一致性哈希在增减节点时只影响环上相邻的一小段 key,而不是全部 key 重新映射——这正是"节点宕机时重新分配比例最低"的原因。为均衡负载还可引入虚拟节点(一个物理节点映射多个环位置)。
一致性哈希优势:每个对象只请求一个对应服务器,节点宕机时重新分配比例最低。
两阶段哈希
- 阶段一:客户端计算 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 设置了相同的过期时间,到点同时失效,瞬间所有请求穿透缓存直达数据库,数据库压力骤增甚至被打垮(与 Redis 的缓存雪崩同因)。解法是在过期时间上加随机偏移量(基础值 ± 随机数),让失效时间点分散开,避免同一时刻集中失效。
内存碎片
Slab Allocation 把内存分成固定大小的 chunk,若 item 大小比所在 chunk 略大(如 chunk 128B、item 130B),item 只能落到 192B 的 chunk,每存一条就浪费几十字节,量大后内存利用率明显下降。用 stats slabs 查看各 slab 的使用率,调整 slab 的增长因子(-f 参数,越小分级越细、浪费越少,但 slab 种类变多管理开销也变大),让 chunk 尺寸贴近业务 item 的实际大小。
连接数耗尽
最常见的成因是客户端连接池泄漏——应用创建连接后未归还或未关闭,长期运行把服务器连接数占满,新请求无法建连。检查连接池配置(最大连接数、空闲回收),确保连接正确释放;用 stats 查看当前连接数(curr_connections)与历史峰值,判断是否异常增长。