Memcached过期策略 #
一、过期策略概述 #
1.1 为什么需要过期策略 #
text
过期策略的作用:
1. 自动清理过期数据
- 释放内存空间
- 保证数据新鲜度
2. 防止内存溢出
- 限制内存使用
- 淘汰旧数据
3. 数据一致性
- 定期更新缓存
- 避免脏数据
1.2 Memcached过期策略 #
text
Memcached过期策略:
1. 惰性删除(Lazy Expiration)
- 访问时检查是否过期
- 过期则删除
2. LRU淘汰(Least Recently Used)
- 内存满时淘汰最近最少使用的数据
- 不区分是否过期
3. 后台清理(可选)
- 不主动清理过期数据
- 依赖惰性删除和LRU
二、惰性删除 #
2.1 原理说明 #
text
惰性删除机制:
1. 数据过期时不立即删除
2. 访问时检查是否过期
3. 过期则删除并返回不存在
4. 未过期则返回数据
流程图:
┌─────────────────────────────────────┐
│ 客户端请求 │
│ ↓ │
│ 查找数据 │
│ ↓ │
│ 数据存在? │
│ ├── 否 → 返回不存在 │
│ └── 是 → 检查过期时间 │
│ ├── 过期 → 删除,返回不存在 │
│ └── 未过期 → 返回数据 │
└─────────────────────────────────────┘
2.2 优点与缺点 #
text
优点:
1. 减少CPU开销
- 不需要定时扫描
- 只在访问时检查
2. 提高性能
- 减少删除操作
- 降低延迟
缺点:
1. 占用内存
- 过期数据可能长期存在
- 浪费内存空间
2. 依赖LRU
- 需要LRU清理过期数据
- 可能影响缓存命中率
2.3 示例演示 #
bash
# 存储数据,1秒后过期
set key 0 1 5
hello
STORED
# 立即获取(未过期)
get key
VALUE key 0 5
hello
END
# 等待2秒后获取(已过期)
sleep 2
get key
END # 数据被删除,返回不存在
# 过期数据占用内存直到被访问或LRU淘汰
三、LRU算法 #
3.1 LRU原理 #
text
LRU(Least Recently Used)算法:
1. 最近最少使用的数据优先淘汰
2. 维护访问顺序链表
3. 访问时移动到链表头部
4. 淘汰时从链表尾部开始
结构图:
┌─────────────────────────────────────┐
│ LRU链表 │
│ │
│ Head ← → Item1 ← → Item2 ← → Tail │
│ ↑ │
│ 最近访问 │
│ ↑ │
│ 最少访问 │
└─────────────────────────────────────┘
淘汰顺序:Tail → Head
3.2 LRU实现 #
text
Memcached LRU实现:
1. 每个Slab Class独立的LRU
- 不同大小的数据分开管理
- 避免大小差异影响淘汰
2. 双向链表
- 快速移动节点
- O(1)时间复杂度
3. 哈希表索引
- 快速查找数据
- O(1)时间复杂度
3.3 LRU淘汰过程 #
text
内存满时的淘汰过程:
1. 新数据需要存储
2. 检查是否有空闲Chunk
3. 如果没有空闲Chunk:
a. 找到最近最少使用的数据
b. 删除该数据
c. 使用释放的Chunk存储新数据
4. 如果有空闲Chunk:
a. 直接使用空闲Chunk
示例:
Slab Class 1 (96B):
Chunk: [Item1] [Item2] [Item3] [Item4] [Item5]
LRU: Item5 → Item4 → Item3 → Item2 → Item1
存储新数据Item6:
淘汰Item1,存储Item6
LRU: Item6 → Item5 → Item4 → Item3 → Item2
3.4 查看LRU信息 #
bash
# 查看淘汰统计
stats items
STAT items:1:evicted 10 # 被淘汰的数据数量
STAT items:1:evicted_nonzero 5 # 非零过期时间的淘汰数量
STAT items:1:evicted_time 3600 # 最后一次淘汰的时间
STAT items:1:outofmemory 0 # 内存不足次数
END
# 查看Slab信息
stats slabs
STAT 1:total_chunks 10922
STAT 1:used_chunks 10922
STAT 1:free_chunks 0
END
四、过期时间设置 #
4.1 过期时间格式 #
text
过期时间格式:
1. exptime = 0
- 永不过期
- 只能通过LRU淘汰
2. exptime > 0 且 < 2592000(30天)
- 相对时间(秒)
- 从当前时间开始计算
3. exptime >= 2592000(30天)
- 绝对时间(Unix时间戳)
- 指定具体的过期日期
示例:
# 1小时后过期
set key 0 3600 5
hello
# 永不过期
set key 0 0 5
hello
# Unix时间戳过期(2024-01-01 00:00:00)
set key 0 1704067200 5
hello
4.2 过期时间计算 #
python
import time
def calculate_exptime(seconds):
"""
计算过期时间
"""
if seconds == 0:
return 0 # 永不过期
elif seconds < 2592000: # 30天
return seconds # 相对时间
else:
return int(time.time()) + seconds # 绝对时间
# 示例
exptime = calculate_exptime(3600) # 1小时
4.3 过期时间最佳实践 #
bash
# 1. 根据数据特性设置过期时间
# 静态数据:较长过期时间
set config:app 0 86400 100
{"theme":"dark","lang":"zh"}
# 动态数据:较短过期时间
set api:weather:beijing 0 1800 128
{"temp":25,"humidity":60}
# 临时数据:很短的过期时间
set code:verify:abc123 0 300 6
123456
# 2. 避免缓存雪崩
# 添加随机值
# 不好的做法
set key1 0 3600 5
hello
set key2 0 3600 5
world
# 两个键同时过期
# 好的做法
set key1 0 3600 5
hello
set key2 0 3700 5
world
# 错开过期时间
4.4 touch命令更新过期时间 #
bash
# 存储数据
set session:abc123 0 1800 64
{"user_id":1001,"login_time":1704067200}
STORED
# 延长过期时间
touch session:abc123 3600
TOUCHED
# 提前过期
touch session:abc123 1
TOUCHED
五、数据淘汰 #
5.1 淘汰触发条件 #
text
数据淘汰触发条件:
1. 内存已满
- 所有Chunk都被使用
- 需要存储新数据
2. Slab已满
- 特定Slab Class的Chunk用完
- 需要存储该大小的数据
3. 创建新Slab失败
- 内存不足以创建新Slab
- 需要淘汰其他Slab的数据
5.2 淘汰策略 #
text
Memcached淘汰策略:
1. LRU淘汰(默认)
- 淘汰最近最少使用的数据
- 不区分是否过期
2. 禁用淘汰(-M选项)
- 内存满时返回错误
- 不淘汰任何数据
示例:
# 启用LRU淘汰(默认)
memcached -m 64
# 禁用淘汰
memcached -m 64 -M
5.3 查看淘汰统计 #
bash
# 查看淘汰统计
stats items
STAT items:1:evicted 100 # 总淘汰数
STAT items:1:evicted_nonzero 50 # 非零过期时间的淘汰数
STAT items:1:evicted_time 3600 # 最后淘汰时间
END
# 查看内存不足次数
stats items
STAT items:1:outofmemory 5
END
# 监控淘汰情况
watch -n 1 'echo "stats items" | nc 127.0.0.1 11211 | grep evicted'
六、过期策略优化 #
6.1 合理设置过期时间 #
python
import random
def set_with_jitter(key, value, base_expire=3600, jitter=300):
"""
设置缓存并添加随机过期时间
避免缓存雪崩
"""
expire = base_expire + random.randint(0, jitter)
client.set(key, value, expire=expire)
# 示例
for user in users:
key = f"user:{user.id}"
value = json.dumps(user)
set_with_jitter(key, value, base_expire=3600, jitter=300)
6.2 热点数据永不过期 #
bash
# 热点数据设置永不过期
set hot:product:1001 0 0 128
{"id":1001,"name":"iPhone","price":999}
# 通过代码逻辑更新
# 1. 检查数据是否需要更新
# 2. 如果需要,异步更新缓存
6.3 使用LRU淘汰策略 #
bash
# 启用LRU淘汰
memcached -m 2048 -o lru_crawler
# LRU爬虫(后台清理过期数据)
memcached -o lru_crawler,lru_crawler_sleep=100,lru_maintainer_cycle=10
# 参数说明:
# lru_crawler - 启用LRU爬虫
# lru_crawler_sleep - 爬虫休眠时间(微秒)
# lru_maintainer_cycle - 维护周期
七、过期策略监控 #
7.1 监控指标 #
bash
# 关键监控指标
# 1. 淘汰数量
stats items | grep evicted
# 2. 内存使用
stats | grep bytes
# 3. 命中率
stats | grep -E "get_hits|get_misses"
# 4. 过期数据数量(估算)
# 通过访问时检查过期来估算
7.2 监控脚本 #
bash
#!/bin/bash
# monitor_expiration.sh
HOST="127.0.0.1"
PORT="11211"
# 获取统计信息
stats=$(echo "stats" | nc $HOST $PORT)
items=$(echo "stats items" | nc $HOST $PORT)
# 解析数据
get_hits=$(echo "$stats" | grep "STAT get_hits" | awk '{print $3}')
get_misses=$(echo "$stats" | grep "STAT get_misses" | awk '{print $3}')
bytes=$(echo "$stats" | grep "STAT bytes " | awk '{print $3}')
maxbytes=$(echo "$stats" | grep "STAT limit_maxbytes" | awk '{print $3}')
# 计算命中率
if [ $((get_hits + get_misses)) -gt 0 ]; then
hit_rate=$(echo "scale=2; $get_hits * 100 / ($get_hits + $get_misses)" | bc)
else
hit_rate=0
fi
# 计算内存使用率
mem_usage=$(echo "scale=2; $bytes * 100 / $maxbytes" | bc)
# 获取淘汰数量
evicted=$(echo "$items" | grep "evicted" | awk '{sum+=$3} END {print sum}')
# 输出结果
echo "Memcached过期策略监控"
echo "====================="
echo "缓存命中率: ${hit_rate}%"
echo "内存使用率: ${mem_usage}%"
echo "淘汰数据量: $evicted"
7.3 告警设置 #
bash
# 告警条件
# 1. 淘汰数量快速增长
if [ $evicted -gt 1000 ]; then
echo "警告:淘汰数量过多"
fi
# 2. 内存使用率过高
if [ $(echo "$mem_usage > 90" | bc) -eq 1 ]; then
echo "警告:内存使用率超过90%"
fi
# 3. 命中率过低
if [ $(echo "$hit_rate < 80" | bc) -eq 1 ]; then
echo "警告:缓存命中率低于80%"
fi
八、过期策略最佳实践 #
8.1 开发环境 #
bash
# 开发环境配置
memcached -d -m 64 -p 11211
# 说明:
# - 使用默认过期策略
# - 小内存便于测试淘汰
8.2 生产环境 #
bash
# 生产环境配置
memcached -d -m 2048 -p 11211 -o lru_crawler,lru_maintainer_cycle=10
# 说明:
# - 启用LRU爬虫
# - 后台清理过期数据
# - 提高内存利用率
8.3 高性能环境 #
bash
# 高性能环境配置
memcached -d -m 4096 -p 11211 -M -o lru_crawler
# 说明:
# - 禁用淘汰(-M)
# - 内存满时返回错误
# - 需要监控内存使用
九、常见问题 #
9.1 数据过早淘汰 #
bash
# 问题:数据未过期就被淘汰
# 原因:
# 1. 内存不足
# 2. LRU淘汰策略
# 解决方案:
# 1. 增加内存
memcached -m 256
# 2. 调整过期时间
set key 0 0 5 # 永不过期
# 3. 监控淘汰情况
stats items | grep evicted
9.2 数据不过期 #
bash
# 问题:数据过期后仍然存在
# 原因:
# 1. 惰性删除,未被访问
# 2. LRU未淘汰
# 解决方案:
# 1. 手动删除
delete key
# 2. 使用touch提前过期
touch key 1
# 3. 启用LRU爬虫
memcached -o lru_crawler
9.3 缓存雪崩 #
bash
# 问题:大量数据同时过期
# 原因:
# 1. 过期时间相同
# 2. 系统启动时批量加载
# 解决方案:
# 1. 添加随机过期时间
expire = base_time + random(0, 300)
# 2. 缓存预热
# 系统启动时加载热点数据
# 3. 使用永不过期
# 通过代码逻辑更新
十、总结 #
过期策略要点:
| 策略 | 说明 |
|---|---|
| 惰性删除 | 访问时检查过期 |
| LRU淘汰 | 内存满时淘汰最少使用 |
| 后台清理 | 可选,主动清理过期数据 |
最佳实践:
- 合理设置过期时间
- 添加随机值避免雪崩
- 监控淘汰情况
- 启用LRU爬虫
- 热点数据永不过期
下一步,让我们学习Memcached的性能优化!
最后更新:2026-03-27