一、Redis内存淘汰全景图
Redis通过maxmemory-policy配置淘汰策略,核心策略分为三类:
- LRU(Least Recently Used):淘汰最久未访问的键。
- LFU(Least Frequently Used):淘汰访问频率最低的键。
- TTL(Time To Live):淘汰剩余存活时间最短的键。
设计目标:在内存不足时,以低内存开销和高效计算完成键淘汰。
二、LRU:近似算法的精妙设计
1. 传统LRU的局限性
- 理想实现:维护双向链表,移动节点至头部,淘汰尾部(时间复杂度O(1))。
- 内存问题:每个键需存储前后指针,额外占用16字节(64位系统)。
2. Redis的近似LRU实现
- 关键结构:每个Redis对象(
redisObject)的lru字段(24位)记录访问时间戳(单位:秒级精度)。typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; // LRU_BITS=24 int refcount; void *ptr; } robj; - 淘汰流程:
- 随机采样:从所有键中随机选取
maxmemory-samples(默认5)个键。 - 淘汰候选:选择采样集中
lru最小的键(即最久未访问)。
- 随机采样:从所有键中随机选取
源码入口:evict.c/evictionPoolPopulate()
void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
// 随机采样键,计算idle时间(当前时间 - lru)
for (j = 0; j < count; j++) {
// 计算idle时间
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = estimateObjectIdleTime(o);
}
// 维护淘汰池,保留idle最大的键
}
}
优化点:
- 内存节省:24位
lru仅需3字节,精度降低但满足淘汰需求。 - 可调精度:增大
maxmemory-samples可逼近真实LRU,但增加CPU开销。
三、LFU:频率统计与衰减机制
1. LFU的核心挑战
- 空间限制:精确统计频率需大量内存。
- 时间衰减:旧访问记录需降低权重。
2. Redis的LFU实现
- lru字段复用:
- 高16位:访问计数器(对数计数,范围0-255)。
- 低8位:最后一次访问的分钟级时间戳(用于衰减)。
// 获取计数器与衰减时间 #define LFU_LOG_INCR 5 // 计数器增量基数 #define LFU_DECAY_TIME 1 // 衰减周期(分钟) uint8_t LFULogIncr(uint8_t counter) { if (counter == 255) return 255; double r = (double)rand()/RAND_MAX; double baseval = counter - LFU_LOG_INCR; if (baseval < 0) baseval = 0; double p = 1.0/(baseval*server.lfu_log_factor+1); return (r < p) ? counter+1 : counter; } - 频率更新:每次访问时,计数器按概率增加(类似Morris计数器)。
- 衰减机制:若当前时间与低8位时间差超过
LFU_DECAY_TIME,计数器减半。
淘汰流程:选择采样集中lfu值最小的键(频率最低)。
四、TTL:时间优先的淘汰逻辑
1. 实现原理
- expires字典:Redis维护一个哈希表,存储所有设置TTL的键及其绝对过期时间(Unix时间戳)。
- 淘汰策略:
volatile-ttl:从expires字典中随机采样,淘汰剩余存活时间(TTL - 当前时间)最短的键。
2. 源码片段
// 计算键的剩余存活时间
long long getExpire(redisDb *db, robj *key) {
dictEntry *de = dictFind(db->expires,key->ptr);
if (de) return dictGetSignedIntegerVal(de) - mstime();
return -1;
}
// 在evictionPoolPopulate中处理TTL策略
if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
idle = ULLONG_MAX - (long)getExpire(db,key);
}
五、策略对比与记忆技巧
| 策略 | 核心指标 | 实现要点 | 类比记忆 |
|---|---|---|---|
| LRU | 最近访问时间 | 近似采样 + 时间戳比较 | 图书馆最近借阅的书籍 |
| LFU | 访问频率 | 概率计数 + 时间衰减 | 热门排行榜(热度会下降) |
| TTL | 剩余存活时间 | 过期字典 + TTL排序 | 食品保质期倒计时 |
配置建议:
- LRU:适用于突发访问场景,保留最近热点数据。
- LFU:适用于长尾访问分布,保留高频访问数据。
- TTL:适用于明确生命周期的数据(如会话信息)。