Memcached 深度解析
一、工作原理与内部机制
-
数据模型
- 纯内存键值存储(Key-Value),键最大 250250250 字节,值最大 111 MB
- 数据结构
item包含元数据(过期时间、标志位)和实际数据
-
内存管理(Slab Allocator)
- 内存预分割为多个 Slab Class(如 64B/128B/256B64B/128B/256B64B/128B/256B)
- 每个 Slab Class 包含固定大小的 Chunk
- 分配流程:
- 计算数据大小 size=key_len+value_len+32Bsize = key\_len + value\_len + 32Bsize=key_len+value_len+32B(元数据开销)
- 选择最小满足 sizesizesize 的 Slab Class
- 从对应 LRU 链表分配 Chunk
-
淘汰机制
- LRU 算法:每个 Slab Class 维护独立链表
- 惰性删除:访问时检查过期时间
- LRU Crawler:后台线程扫描过期项(需启用
-o lru_crawler)
-
哈希索引
- 链式哈希表解决冲突
- 默认哈希函数:CRC32CRC32CRC32,可配置为 MurmurHash3MurmurHash3MurmurHash3
- 时间复杂度 O(1)O(1)O(1)
二、配置方法(Linux 示例)
# 1. 安装依赖
sudo apt-get install libevent-dev
# 2. 编译安装
wget https://memcached.org/latest
tar -zxvf memcached-*.tar.gz
cd memcached-*
./configure --prefix=/usr/local/memcached
make && sudo make install
# 3. 启动参数详解
/usr/local/memcached/bin/memcached \
-m 2048 # 分配2GB内存 \
-p 11211 # 监听端口 \
-c 4096 # 最大连接数 \
-o lru_crawler # 启用LRU扫描器 \
-U 11211 # 启用UDP协议 \
-d # 守护进程模式
三、性能优化策略
-
降低剔除率(Eviction)
- 监控
evicted_items指标:echo "stats" | nc localhost 11211 | grep evicted - 解决方案:
- 扩容内存(增加
-m参数) - 调整 Slab 分布:
-o slab_reassign - 预热缓存:重启后主动加载热点数据
- 扩容内存(增加
- 监控
-
网络优化
- 启用 UDP 协议减少连接开销
- 调整连接池大小(Java 客户端如
spymemcached)ConnectionFactoryBuilder() .setOpTimeout(1000) .setMaxConnections(500)
-
键设计规范
- 命名空间分层:
user:123:profile - 避免大 Key:超过 111 KB 的数据需拆分
- 命名空间分层:
四、与 Redis 核心对比
| 维度 | Memcached | Redis |
|---|---|---|
| 数据结构 | 仅 String | 支持 555 种复杂结构 |
| 持久化 | 不支持 | RDB/AOF 双机制 |
| 线程模型 | 多线程(I/O 与工作线程分离) | 单线程(6.0+ I/O 多线程) |
| 内存管理 | Slab 预分配 | 动态分配 + 淘汰策略 |
| 分布式 | 客户端分片 | 原生 Cluster 支持 |
| 适用场景 | 高频读写的简单缓存 | 缓存/队列/实时统计等复杂场景 |
选型决策树:
{纯缓存 & 高吞吐→Memcached需持久化/复杂结构→Redis \begin{cases} \text{纯缓存 \& 高吞吐} & \rightarrow \text{Memcached} \\ \text{需持久化/复杂结构} & \rightarrow \text{Redis} \end{cases} {纯缓存 & 高吞吐需持久化/复杂结构→Memcached→Redis
五、分布式场景实践
-
一致性哈希算法
- 虚拟节点解决数据倾斜
- Java 定位逻辑:
// FNV1_32_HASH 算法 int hash = (key.hashCode() >>> 16) ^ key.hashCode(); int index = Math.abs(hash % serverList.size());
-
故障恢复
- 进程级:通过 watchdog 自动重启
- 数据级:
- 冷启动异步回填
executor.submit(() -> loadHotDataToCache());
-
数据一致性
- 双删策略:
1. 删除缓存 → 写数据库 → 延时删除缓存 - 设置较短过期时间(如 303030 秒)
- 双删策略:
总结
Memcached 通过 Slab 内存预分配 和 多线程架构 实现百万级 QPS 吞吐,适用于简单键值缓存场景。关键优化点包括:
- 监控
evicted_items及时扩容内存 - 启用 LRU Crawler 减少内存碎片
- 客户端一致性哈希分片保障分布式扩展性
- 与 Redis 形成互补架构(Memcached 作一级缓存)
思维导图

Memcached 技术原理详解
1. 技术原理
Memcached 基于分布式内存键值存储模型,核心逻辑如下:
- 数据缓存流程
应用先查询 Memcached,命中则返回数据;未命中时查询数据库,并将结果写入缓存(含空值缓存) - 内存管理
使用 Slab Allocator 内存分配器预防碎片:- 将内存划分为不同大小的 Chunk(如 64B/128B/256B)
- 数据按大小存入匹配的 Slab Class
- 内存满时按 LRU(最近最少使用)算法剔除旧数据
2. 核心算法
-
分布式定位:一致性哈希
- 节点通过哈希函数映射到哈希环(如 hash(serverip)hash(server_ip)hash(serverip))
- 数据键哈希值 hash(key)hash(key)hash(key) 顺时针定位到最近节点
- 节点增减时仅影响相邻数据,最小化数据迁移
// Java 伪代码:一致性哈希定位 int serverIndex = consistentHash(key.hashCode(), serverList.size()); -
哈希算法
默认使用 CRC32 或 FNV1 计算键的哈希值,确保均匀分布:
hash(key)=CRC32(key)mod server_count \text{hash}(key) = \text{CRC32}(key) \mod \text{server\_count} hash(key)=CRC32(key)modserver_count
3. 数据结构
- 存储单元:Item 结构体
struct item { uint32_t flags; // 数据类型标识 time_t exptime; // 过期时间 size_t nbytes; // 数据长度 char key[]; // 变长键 char data[]; // 变长值 }; - 内存组织
- Slab Class 数组:管理不同尺寸的 Chunk
- LRU 链表数组:按访问时间排序的 Item 链表
- 哈希表:O(1)O(1)O(1) 时间复杂度定位键(分离链表法解决冲突)
4. 架构功能图
graph TB
Client[客户端] -->|Set/Get| Proxy[代理层]
subgraph Memcached 集群
Proxy --> Node1[节点1]
Proxy --> Node2[节点2]
subgraph Node1
Slab1[Slab Class 64B] --> LRU1[LRU链表]
Slab2[Slab Class 128B] --> LRU2[LRU链表]
HashTable[哈希表]
end
end
Node1 --> DB[(数据库)]
5. 核心组件功能
| 组件 | 功能说明 |
|---|---|
| 内存分配器 | 通过 Slab 机制高效管理内存,减少碎片 |
| LRU 驱逐器 | 自动淘汰过期或低频数据(统计显示剔除过高需优化策略) |
| 哈希索引 | 快速定位键值对位置,哈希冲突时使用链表解决 |
| 网络模块 | 基于事件驱动(如 libevent)处理高并发请求 |
| 分布式代理 | 客户端或中间件实现一致性哈希路由(如采用 K8s Service 负载均衡) |
6. 优缺点分析
| 优点 | 缺点 |
|---|---|
| 高速读写(内存操作) | 无持久化(重启数据丢失) |
| 水平扩展能力强 | 不支持复杂数据结构(对比 Redis) |
| 协议简单,多语言客户端支持 | 内存满时高频剔除影响性能 |
| 减轻数据库压力 | 无内置安全机制(需网络隔离) |
Java 客户端实现示例
import net.spy.memcached.MemcachedClient;
import java.net.InetSocketAddress;
public class MemcachedDemo {
public static void main(String[] args) throws Exception {
// 1. 连接 Memcached 服务器
MemcachedClient client = new MemcachedClient(
new InetSocketAddress("localhost", 11211)
);
// 2. 存储数据(过期时间 60 秒)
String key = "user:1001";
String value = "{\"name\":\"Alice\",\"age\":28}";
client.set(key, 60, value); // 参数:键, 过期秒数, 值
// 3. 查询数据
Object cached = client.get(key);
if (cached != null) {
System.out.println("命中缓存: " + cached);
} else {
System.out.println("缓存未命中,从数据库加载...");
// 模拟数据库查询
String dbValue = fetchFromDB(key);
client.set(key, 60, dbValue); // 回填缓存
}
client.shutdown(); // 关闭连接
}
private static String fetchFromDB(String key) {
// 实际项目此处访问数据库
return "{\"name\":\"Bob\",\"age\":32}";
}
}
代码注释说明
- 连接管理:通过
InetSocketAddress指定服务器地址 - 数据写入:
set(key, expireTime, value)设置键值对及过期时间 - 缓存查询:
get(key)返回Object需判空处理未命中场景 - 回填策略:数据库查询后调用
set()回填缓存(含空值) - 资源释放:结束时调用
shutdown()释放网络连接
性能优化建议
- 防缓存穿透:对空值设置短过期时间(如 5 秒)
- 内存配置:监控剔除率,若过高需增加内存或优化 Slab 分布
- 键设计:采用命名空间(如
product:123)避免冲突 - 客户端分片:使用一致性哈希减少节点变更时的数据迁移
思维导图

Memcached 深度解析
一、工作原理与内部机制
-
数据模型
- 纯内存键值存储(Key-Value),键最大 250字节,值最大 1MB
- 数据存储单位
item结构:struct item { uint32_t flags; // 数据类型标识 time_t exptime; // 绝对过期时间戳 size_t nbytes; // 数据长度 char key[]; // 变长键(以\0结尾) char data[]; // 变长值 struct item* next; // LRU链表指针 };
-
内存管理(Slab Allocator)
- 内存池划分:
- 将内存预分割为多个 Slab Class(如 64B/128B/256B)
- 每个 Slab Class 包含多个相同大小的 Chunk
- 分配流程:
- 计算数据大小 size=sizeof(key)+sizeof(value)+32Bsize = sizeof(key) + sizeof(value) + 32Bsize=sizeof(key)+sizeof(value)+32B(元数据开销)
- 选择最小满足 sizesizesize 的 Slab Class
- 从对应 LRU 链表分配 Chunk
- 内存池划分:
-
淘汰机制
- LRU 算法:每个 Slab Class 维护独立 LRU 链表
- 惰性删除:访问时检查过期时间,写入新数据时触发淘汰
- LRU Crawler:后台线程定期扫描过期项(需开启
-o lru_crawler)
-
哈希索引
- 使用 链式哈希表(分离链表法解决冲突)
- 哈希函数:默认 CRC32,可配置为 MurmurHash3
- O(1)O(1)O(1) 时间复杂度定位数据
二、安装配置(Linux 示例)
# 1. 安装依赖
sudo apt-get install libevent-dev
# 2. 编译安装(最新版1.6.22)
wget https://memcached.org/latest
tar -zxvf memcached-1.6.22.tar.gz
cd memcached-1.6.22
./configure --prefix=/usr/local/memcached
make && sudo make install
# 3. 启动服务(分配2GB内存,启用LRU Crawler)
/usr/local/memcached/bin/memcached -m 2048 -p 11211 -o lru_crawler,lru_maintainer -d
三、与 Redis 核心对比
| 维度 | Memcached | Redis |
|---|---|---|
| 数据结构 | 仅 String | String/Hash/List/Set/SortedSet 等 |
| 持久化 | 不支持 | RDB/AOF 两种机制 |
| 线程模型 | 多线程(网络I/O与工作线程分离) | 单线程(6.0+支持I/O多线程) |
| 内存管理 | Slab 预分配 | 动态分配 + 淘汰策略 |
| 分布式 | 客户端分片 | 原生 Cluster 支持 |
| 适用场景 | 高频读写的简单键值缓存 | 缓存/消息队列/实时统计等复杂场景 |
选型建议:
- 纯缓存场景用 Memcached(性能更高)
- 需复杂数据结构选 Redis(功能更丰富)
四、性能优化实践
-
解决高剔除率(Eviction)
- 扩容内存:监控
evicted_items指标,超过阈值时增加-m参数 - 优化 Slab 分布:调整
slab_automove参数自动平衡 Chunk 分布 - 预热缓存:重启后主动加载热点数据
# 查看Slab统计 echo "stats slabs" | nc localhost 11211 - 扩容内存:监控
-
网络优化
- 启用 UDP 协议:
-U 11211(减少连接开销) - 调整最大连接数:
-c 4096(默认1024)
- 启用 UDP 协议:
-
键设计规范
- 使用命名空间:
product:123:detail - 避免大Key:拆分超过 1KB 的值
- 使用命名空间:
五、分布式数据一致性
-
客户端分片方案
- 采用 一致性哈希算法 定位节点
- Java 实现代码:
import com.google.common.hash.Hashing; public class ConsistentHash { // 虚拟节点数(解决分布不均) private static final int VIRTUAL_NODES = 200; public static String getServer(String key, List<String> servers) { // 使用Guava的MurmurHash int hash = Hashing.murmur3_32().hashUnencodedChars(key).asInt(); int index = (hash & Integer.MAX_VALUE) % servers.size(); return servers.get(index); } }
-
数据同步问题
- 最终一致性:Memcached 本身不保证,需业务层解决
- 解决方案:
- 写数据库后双删缓存
- 设置缓存较短过期时间(如 30秒)
六、故障恢复机制
-
进程崩溃恢复
- 通过 watchdog 脚本 自动重启:
while true; do /usr/local/memcached/bin/memcached -m 2048 -p 11211 sleep 10 done
- 通过 watchdog 脚本 自动重启:
-
数据恢复策略
- 冷启动方案:
- 空缓存期降级读数据库
- 异步线程回填热点数据
// Java 回填示例 ExecutorService executor = Executors.newFixedThreadPool(4); executor.submit(() -> warmUpCache());
- 冷启动方案:
七、Java 项目选型标准
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单商品详情页缓存 | Memcached | 吞吐量 >150K QPS,延迟 <1ms |
| 购物车数据存储 | Redis | 需 Hash 结构修改单个字段 |
| 分布式Session | Redis | 需持久化防止丢失 |
| 热点数据缓存 | Memcached | 纯内存操作性能极致 |
八、一致性哈希算法实现细节
-
算法流程
- 物理节点映射:
nodePos=hash(serverip)%232 nodePos = hash(server_ip) \% 2^{32} nodePos=hash(serverip)%232 - 虚拟节点:每个物理节点生成 NNN 个虚拟节点(如
192.168.1.1#1) - 数据定位:
server=min{nodePosi∣nodePosi≥hash(key)%232} server = \min\left\{ nodePos_i \mid nodePos_i \geq hash(key) \% 2^{32} \right\} server=min{nodePosi∣nodePosi≥hash(key)%232}
- 物理节点映射:
-
Java 完整实现
import java.util.SortedMap;
import java.util.TreeMap;
public class ConsistentHash {
private final SortedMap<Integer, String> ring = new TreeMap<>();
private final int virtualNodes;
public ConsistentHash(int virtualNodes) {
this.virtualNodes = virtualNodes;
}
// 添加节点
public void addNode(String node) {
for (int i = 0; i < virtualNodes; i++) {
int hash = hash(node + "#" + i);
ring.put(hash, node);
}
}
// 定位节点
public String getNode(String key) {
if (ring.isEmpty()) return null;
int hash = hash(key);
SortedMap<Integer, String> tailMap = ring.tailMap(hash);
int nodeHash = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey();
return ring.get(nodeHash);
}
// FNV1_32_HASH 算法
private int hash(String key) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < key.length(); i++) {
hash = (hash ^ key.charAt(i)) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return hash < 0 ? Math.abs(hash) : hash;
}
}
关键优化:
- 虚拟节点解决数据倾斜(推荐 virtualNodes=160virtualNodes=160virtualNodes=160)
- 采用 FNV 哈希算法保证均匀性
总结
Memcached 作为高性能分布式缓存的基石,其核心价值在于 简单场景下的极致性能。通过 Slab 内存管理、LRU 淘汰机制和一致性哈希分片,可实现百万级 QPS 的吞吐能力。在 Java 项目中,建议:
- 纯缓存场景优先选择 Memcached
- 需复杂操作时结合 Redis 使用
- 通过客户端分片和冷启动方案保障高可用
思维导图


844

被折叠的 条评论
为什么被折叠?



