一、Memcached 概述
Memcached 是一个高性能的分布式内存对象缓存系统,用于加速动态 Web 应用程序通过减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。
核心特性
- 纯内存存储:所有数据存储在内存中,读写速度极快
- 分布式架构:支持多服务器部署,无中心节点
- 简单键值存储:使用简单的 key-value 数据结构
- 协议支持:基于文本协议和二进制协议
- LRU 淘汰机制:当内存不足时自动淘汰最近最少使用的数据
二、Memcached 架构与工作原理
1. 系统架构
[Client App] ←→ [Memcached Server 1]
↑ [Memcached Server 2]
| [Memcached Server 3]
↓
[Database]
2. 核心组件
- 内存分配:采用 Slab Allocation 机制管理内存
- 哈希表:使用高效的哈希算法快速定位数据
- LRU 算法:管理内存空间回收
3. 工作流程
- 应用程序首先检查 Memcached 中是否存在所需数据
- 如果存在(命中),直接返回缓存数据
- 如果不存在(未命中),从数据库读取数据
- 将数据库返回的数据写入 Memcached 供后续使用
三、Memcached 常见问题及解决方案
1. 缓存雪崩问题
问题描述:大量缓存同时失效,导致请求直接打到数据库,可能使数据库崩溃
解决方案:
-
随机过期时间:为缓存设置不同的过期时间
// 设置基础过期时间 + 随机偏移量
int expireTime = 3600 + new Random().nextInt(600); // 3600-4200秒随机
memcachedClient.set("user:123", expireTime, userData); -
多级缓存:结合本地缓存和分布式缓存
-
熔断机制:当数据库压力过大时暂时拒绝部分请求
2. 缓存穿透问题
问题描述:查询不存在的数据,导致每次请求都穿透到数据库
解决方案:
-
布隆过滤器:预先过滤掉不存在的 key
// 使用Guava的布隆过滤器
BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1000000,
0.01);// 查询前先检查
if(!filter.mightContain(key)) {
return null; // 直接返回,不查询缓存和DB
} -
缓存空值:对不存在的 key 也进行缓存,设置较短过期时间
if(data == null) {
memcachedClient.set("null_key:"+key, 300, ""); // 缓存5分钟
}
3. 缓存击穿问题
问题描述:热点 key 失效瞬间,大量请求直接打到数据库
解决方案:
-
互斥锁:只允许一个请求重建缓存
// 使用Redis/Memcached的原子操作实现分布式锁
String value = memcachedClient.get(key);
if(value == null) {
if(memcachedClient.add("lock:"+key, 60, "locked")) {
try {
// 从数据库加载数据
value = db.query(key);
memcachedClient.set(key, 3600, value);
} finally {
memcachedClient.delete("lock:"+key);
}
} else {
Thread.sleep(100); // 稍后重试
return getFromCache(key);
}
} -
永不过期策略:后台异步更新缓存
-
热点数据特殊处理:识别热点数据并设置更长过期时间
4. 数据一致性问题
问题描述:数据库更新后缓存未同步更新
解决方案:
-
双写策略:更新数据库后立即更新缓存
@Transactional
public void updateUser(User user) {
// 先更新数据库
userDao.update(user);
// 再更新缓存
memcachedClient.set("user:"+user.getId(), 3600, user);
} -
延迟双删:更新数据库后先删除缓存,延迟一定时间再删一次
public void updateProduct(Product product) {
// 第一次删除
memcachedClient.delete("product:"+product.getId());
// 更新数据库
productDao.update(product);
// 延迟1秒后再次删除
executor.schedule(() -> {
memcachedClient.delete("product:"+product.getId());
}, 1, TimeUnit.SECONDS);
} -
订阅数据库变更:通过 binlog 监听数据库变化并更新缓存
5. 内存管理问题
问题描述:内存碎片、内存不足导致性能下降
解决方案:
-
合理配置 Slab:调整增长因子和初始 chunk 大小
memcached启动参数
memcached -m 64 -f 1.2 -n 72
-
监控内存使用:定期检查 slab 分配情况
使用stats slabs命令监控
echo "stats slabs" | nc localhost 11211
-
LRU 调优:根据业务特点调整淘汰策略
6. 集群扩展问题
问题描述:节点增减导致哈希重分布,缓存大量失效
解决方案:
-
一致性哈希:减少节点变化带来的影响
// 使用一致性哈希客户端
KetamaConnectionFactory factory = new KetamaConnectionFactory();
MemcachedClientBuilder builder = new XMemcachedClientBuilder(factory,
AddrUtil.getAddresses("server1:11211 server2:11211 server3:11211")); -
虚拟节点:增加节点分布的均匀性
-
预热机制:新节点加入时预先加载热点数据
四、Memcached 最佳实践
1. 键设计规范
- 使用统一命名空间:
业务:子业务:ID
(如user:profile:123
) - 控制 key 长度(不超过 250 字节)
- 避免特殊字符
2. 值优化建议
- 单个 item 不超过 1MB
- 复杂对象先序列化
- 考虑压缩大值数据
3. 监控指标
指标 | 说明 | 健康值 |
---|---|---|
get_hits | 缓存命中次数 | 越高越好 |
get_misses | 缓存未命中次数 | 越低越好 |
bytes | 已用内存 | 不超过80% |
evictions | 淘汰数 | 接近0 |
4. 常用监控命令
# 基础统计
echo "stats" | nc localhost 11211
# 内存统计
echo "stats slabs" | nc localhost 11211
# 查看设置项
echo "stats settings" | nc localhost 11211
五、Memcached 与其他缓存对比
特性 | Memcached | Redis |
---|---|---|
数据类型 | 简单键值 | 丰富数据结构 |
持久化 | 不支持 | 支持 |
集群 | 客户端分片 | 原生集群 |
线程模型 | 多线程 | 单线程 |
协议 | 文本/二进制 | 自定义 |
适用场景 | 简单缓存 | 缓存+数据库 |
六、Memcached 客户端示例(Java)
1. 使用 XMemcached 客户端
// 初始化客户端
MemcachedClientBuilder builder = new XMemcachedClientBuilder(
AddrUtil.getAddresses("localhost:11211"));
MemcachedClient memcachedClient = builder.build();
// 设置缓存
memcachedClient.set("key", 3600, "value");
// 获取缓存
String value = memcachedClient.get("key");
// 删除缓存
memcachedClient.delete("key");
// 原子递增
long newValue = memcachedClient.incr("counter", 1, 0);
2. 使用 Spring Cache 集成
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
MemcachedCacheManager cacheManager = new MemcachedCacheManager();
cacheManager.setServers("localhost:11211");
cacheManager.setProtocol(ConnectionFactoryBuilder.Protocol.BINARY);
return cacheManager;
}
}
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(String id) {
// 数据库查询逻辑
}
@CacheEvict(value = "users", key = "#user.id")
public void updateUser(User user) {
// 更新逻辑
}
}
七、Memcached 调优参数
1. 关键启动参数
-m <num> 最大内存分配(MB)
-f <factor> Slab增长因子(默认1.25)
-n <size> 初始chunk大小(字节)
-l <ip> 监听IP地址
-d 以守护进程运行
-c <num> 最大并发连接数(默认1024)
-t <num> 线程数(默认4)
2. 生产环境推荐配置
# 8GB内存,4线程,1024最大连接数
memcached -m 8192 -t 4 -c 1024 -f 1.2 -n 72 -d
总结
Memcached 作为高性能分布式内存缓存系统,能够显著提升应用性能,但也面临缓存雪崩、穿透、一致性等问题。通过合理设计缓存策略、使用多级缓存、实现原子操作和一致性哈希等技术,可以有效解决这些问题。在生产环境中,需要结合监控和调优,才能充分发挥 Memcached 的优势。