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淘汰 内存满时淘汰最少使用
后台清理 可选,主动清理过期数据

最佳实践:

  1. 合理设置过期时间
  2. 添加随机值避免雪崩
  3. 监控淘汰情况
  4. 启用LRU爬虫
  5. 热点数据永不过期

下一步,让我们学习Memcached的性能优化!

最后更新:2026-03-27