Redis 内存管理的双重保障
"在高并发场景下,Redis 的内存管理策略直接决定了系统的稳定性和性能表现。" —— 某大型电商平台架构师
Redis 作为高性能的内存数据库,其内存管理机制是保证系统稳定运行的核心。本文将深入剖析 Redis 的淘汰策略和删除策略,帮助你在实际项目中做出最优选择。
淘汰策略:内存满了怎么办?
核心概念解析
当 Redis 内存使用达到 maxmemory 限制时,淘汰策略(Eviction Policy)决定了哪些数据应该被移除。这是 Redis 主动管理内存的重要机制。
# 设置最大内存为 2GB
redis-cli CONFIG SET maxmemory 2gb
# 查看当前内存 使用情况
redis-cli INFO memory八大淘汰策略详解
Redis 提供了 8 种淘汰策略,每种都有其适用场景:
| 策略名称 | 作用范围 | 淘汰规则 | 适用场景 |
|---|---|---|---|
| noeviction | - | 不淘汰,返回错误 | 数据不能丢失的场景 |
| allkeys-lru | 所有键 | 最近最少使用 | 缓存场景,热点数据明显 |
| allkeys-lfu | 所有键 | 最不经常使用 | 访问频率差异大的场景 |
| allkeys-random | 所有键 | 随机淘汰 | 数据访问概率相同 |
| volatile-lru | 设置过期的键 | 最近最少使用 | 临时缓存场景 |
| volatile-lfu | 设置过期的键 | 最不经常使用 | 临时数据访问频率差异大 |
| volatile-random | 设置过期的键 | 随机淘汰 | 临时数据无明显访问规律 |
| volatile-ttl | 设置过期的键 | TTL 最小优先 | 时效性数据场景 |
LRU vs LFU:算法对比
# LRU (Least Recently Used) 示例
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = {} # key -> [value, access_time]
self.access_count = 0
def get(self, key):
if key in self.cache:
# 更新访问时间
self.access_count += 1
self.cache[key][1] = self.access_count
return self.cache[key][0]
return None
def put(self, key, value):
if len(self.cache) >= self.capacity and key not in self.cache:
# 淘汰最久未访问的键
lru_key = min(self.cache.keys(),
key=lambda k: self.cache[k][1])
del self.cache[lru_key]
self.access_count += 1
self.cache[key] = [value, self.access_count]
# LFU (Least Frequently Used) 示例
class LFUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = {} # key -> [value, frequency]
def get(self, key):
if key in self.cache:
# 增加访问频率
self.cache[key][1] += 1
return self.cache[key][0]
return None
def put(self, key, value):
if len(self.cache) >= self.capacity and key not in self.cache:
# 淘汰访问频率最低的键
lfu_key = min(self.cache.keys(),
key=lambda k: self.cache[k][1])
del self.cache[lfu_key]
if key in self.cache:
self.cache[key][0] = value
self.cache[key][1] += 1
else:
self.cache[key] = [value, 1]实战配置建议
# 1. 纯缓存场景(数据可重建)
redis-cli CONFIG SET maxmemory-policy allkeys-lru
# 2. 会话存储场景(有过期时间)
redis-cli CONFIG SET maxmemory-policy volatile-lru
# 3. 排行榜场景(访问频率差异大)
redis-cli CONFIG SET maxmemory-policy allkeys-lfu
# 4. 队列场景(不允许淘汰)
redis-cli CONFIG SET maxmemory-policy noeviction删除策略:过期键的清理艺术
三种删除策略对比
Redis 采用了三种策略的组合来处理过期键:
graph TD
A[过期键删除策略] --> B[定时删除]
A --> C[惰性删除]
A --> D[定期删除]
B --> B1[CPU密集型<br/>内存友好]
C --> C1[CPU友好<br/>内存不友好]
D --> D1[折中方案<br/>Redis采用]
style D fill:#f9f,stroke:#333,stroke-width:4px
惰性删除实现原理
// Redis 源码简化版
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
// 检查键是否存在
val = lookupKey(db, key);
// 惰性删除:访问时检查是否过期
if (val != NULL) {
if (expireIfNeeded(db, key)) {
// 键已过期,返回 NULL
return NULL;
}
}
return val;
}
int expireIfNeeded(redisDb *db, robj *key) {
// 获取过期时间
long long when = getExpire(db, key);
// 没有设置过期时间
if (when < 0) return 0;
// 检查是否过期
if (mstime() <= when) return 0;
// 删除过期键
dbDelete(db, key);
return 1;
}定期删除算法详解
# Redis 定期删除算法模拟
import random
import time
class RedisExpireManager:
def __init__(self):
self.databases = [{} for _ in range(16)] # 默认16个数据库
self.hz = 10 # 服务器频率,每秒执行10次
def activeExpireCycle(self):
"""定期删除主循环"""
# 每次处理的数据库数量
dbs_per_call = 16
for db_id in range(dbs_per_call):
db = self.databases[db_id]
# 随机抽样检查
num_keys = len(db)
if num_keys == 0:
continue
# 每次最多检查 20 个键
num_to_check = min(20, num_keys)
# 记录删除的键数量
num_expired = 0
for _ in range(num_to_check):
# 随机选择一个键
key = random.choice(list(db.keys()))
# 检查是否过期
if self.is_expired(db[key]):
del db[key]
num_expired += 1
# 如果删除比例超过 25%,继续删除
if num_expired > num_to_check / 4:
# 继续处理这个数据库
self.continue_expire_cycle(db)
def is_expired(self, key_info):
"""检查键是否过期"""
if 'expire_time' not in key_info:
return False
return time.time() > key_info['expire_time']
def continue_expire_cycle(self, db):
"""继续清理过期键,但要控制时间"""
start_time = time.time()
time_limit = 0.025 # 最多占用 25ms
while time.time() - start_time < time_limit:
if not db:
break
key = random.choice(list(db.keys()))
if self.is_expired(db[key]):
del db[key]过期键对 RDB 和 AOF 的影响
# RDB 持久化时的过期键处理
# 1. 生成 RDB 文件时,过期键不会被保存
redis-cli BGSAVE
# 2. 载入 RDB 文件时
# - 主服务器:忽略过期键
# - 从服务器:全部载入(等待主服务器同步删除命令)
# AOF 持久化时的过期键处理
# 1. 写入 AOF 时,过期键正常记录
# 2. 删除过期键时,追加 DEL 命令
redis-cli CONFIG SET appendonly yes
# 查看 AOF 文件中的删除记录
tail -f /var/lib/redis/appendonly.aof | grep DEL性能优化实战
监控关键指标
import redis
import time
class RedisMonitor:
def __init__(self, host='localhost', port=6379):
self.client = redis.Redis(host=host, port=port)
def monitor_memory(self):
"""监控内存使用情况"""
info = self.client.info('memory')
metrics = {
'已用内存': info['used_memory_human'],
'内存峰值': info['used_memory_peak_human'],
'内存碎片率': info['mem_fragmentation_ratio'],
'淘汰键数量': self.client.info('stats').get('evicted_keys', 0)
}
return metrics
def monitor_expired_keys(self):
"""监 控过期键情况"""
stats = self.client.info('stats')
return {
'过期键数量': stats.get('expired_keys', 0),
'惰性删除数': stats.get('expired_stale_perc', 0),
'定期删除耗时': stats.get('expired_time_cap_reached_count', 0)
}
def optimize_suggestions(self):
"""提供优化建议"""
memory_info = self.client.info('memory')
stats_info = self.client.info('stats')
suggestions = []
# 内存使用率检查
used_memory = memory_info['used_memory']
max_memory = self.client.config_get('maxmemory')['maxmemory']
if max_memory and int(max_memory) > 0:
usage_ratio = used_memory / int(max_memory)
if usage_ratio > 0.9:
suggestions.append("内存使用率超过90%,建议增加 maxmemory 或优化淘汰策略")
# 淘汰键检查
evicted_keys = stats_info.get('evicted_keys', 0)
if evicted_keys > 1000:
suggestions.append(f"已淘汰 {evicted_keys} 个键,考虑调整内存上限或优化数据结构")
# 内存碎片检查
frag_ratio = memory_info['mem_fragmentation_ratio']
if frag_ratio > 1.5:
suggestions.append(f"内存碎片率 {frag_ratio:.2f},建议执行 MEMORY PURGE")
return suggestions
# 使用示例
monitor = RedisMonitor()
print("内存状态:", monitor.monitor_memory())
print("过期键状态:", monitor.monitor_expired_keys())
print("优化建议:", monitor.optimize_suggestions())