# /
# 传统的LFU算法
传统的LFU算法,被称为最不频繁使用,根据数据被访问的次数决策是否淘汰。
注意:这种淘汰策略的问题比较明显,比如,某些数据很久未被访问,却不会被淘汰,因为它们之前被访问很多次。
# Redis的LFU算法
与传统LFU不同,Redis即统计数据被访问次数,也考虑数据被访问的时间。
//使用16bit作为访问时间、使用8bit作为访问频率
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
1
2
3
4
2
3
4
void updateLFU(robj *val) {
unsigned long counter = LFUDecrAndReturn(val); //增长算法
counter = LFULogIncr(counter); //衰退算法
val->lru = (LFUGetTimeInMinutes()<<8) | counter;//更新为当前时间
}
unsigned long LFUGetTimeInMinutes(void) {
return (server.unixtime/60) & 65535; //获取当前时间/60,转为16bit
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 访问次数衰退算法
unsigned long LFUDecrAndReturn(robj *o) {
unsigned long ldt = o->lru >> 8; //取出时间(前16bit)
unsigned long counter = o->lru & 255; //取出计数器(后8bit)
unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0; //存活时间/衰退周期,表示衰退数
if (num_periods) //如果衰退数=0,即1个周期,counter保持不变。如果衰退数>0,即1个周期,counter下降。
counter = (num_periods > counter) ? 0 : counter - num_periods; //衰退数>counter,counter归零。衰退数<=counter,counter=counter-衰退数。
return counter;
}
unsigned long LFUTimeElapsed(unsigned long ldt) {
unsigned long now = LFUGetTimeInMinutes(); //获取当前时间
if (now >= ldt) return now-ldt; //如果当前时间>ldt,取出存活时间
return 65535-ldt+now; //如果ldt>当前时间,取出存活时间(now+65535-ldt)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 访问次数增长算法
#define LFU_INIT_VAL 5
uint8_t LFULogIncr(uint8_t counter) {
if (counter == 255) return 255; //如果counter=255,保持不变。
double r = (double)rand()/RAND_MAX; //生成小于1的随机数r
double baseval = counter - LFU_INIT_VAL;
if (baseval < 0) baseval = 0; //如果counter<5,p=1。如果counter>5,p<1。
double p = 1.0/(baseval*server.lfu_log_factor+1);
if (r < p) counter++; //如果p大于r,counter+1。(即counter值越大增长越慢。)
return counter;
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 执行驱逐
int performEvictions(void) {
..
if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) //LRU、LFU、TTL的驱逐逻辑
{
struct evictionPoolEntry *pool = EvictionPoolLRU;
while(bestkey == NULL) {
unsigned long total_keys = 0, keys;
for (i = 0; i < server.dbnum; i++) {
db = server.db+i;
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
db->dict : db->expires;
if ((keys = dictSize(dict)) != 0) {
evictionPoolPopulate(i, dict, db->dict, pool); //驱逐池填充
total_keys += keys;
}
}
if (!total_keys) break; /* No keys to evict. */
/* Go backward from best to worst element to evict. */
for (k = EVPOOL_SIZE-1; k >= 0; k--) {
if (pool[k].key == NULL) continue;
bestdbid = pool[k].dbid;
if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
de = dictFind(server.db[pool[k].dbid].dict, //从驱逐池中寻找目标
pool[k].key);
} else {
de = dictFind(server.db[pool[k].dbid].expires,
pool[k].key);
}
/* Remove the entry from the pool. */
if (pool[k].key != pool[k].cached)
sdsfree(pool[k].key);
pool[k].key = NULL; //释放
pool[k].idle = 0;
if (de) {
bestkey = dictGetKey(de);
break;
} else {
/* Ghost... Iterate again. */
}
}
}
}
..
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 驱逐池填充
驱逐的基本思路:是随机采样,再根据idle排序,再驱逐。
注意:并不是全库排序,并不是驱逐最古老的数据。
# maxmemory-samples 5 //采样池大小
#define EVPOOL_SIZE 16 //驱逐池大小
struct evictionPoolEntry {
unsigned long long idle; /* Object idle time (inverse frequency for LFU) */
sds key; /* Key name. */
sds cached; /* Cached SDS object for key name. */
int dbid; /* Key DB number. */
};
void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
int j, k, count;
dictEntry *samples[server.maxmemory_samples];//采样池
count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples); //随机采样
for (j = 0; j < count; j++) {
unsigned long long idle;
sds key;
robj *o;
dictEntry *de;
de = samples[j]; //采样
key = dictGetKey(de); //采样
if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
if (sampledict != keydict) de = dictFind(keydict, key);
o = dictGetVal(de); //采样
}
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = estimateObjectIdleTime(o); //LRU的idle算法
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
idle = 255-LFUDecrAndReturn(o); //LFU的idle算法
} else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
idle = ULLONG_MAX - (long)dictGetVal(de); //TTL的idle算法
} else {
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
}
k = 0;
while (k < EVPOOL_SIZE &&
pool[k].key &&
pool[k].idle < idle) k++; //遍历pool,通过idle确定k。
..
//填充k
int klen = sdslen(key);
if (klen > EVPOOL_CACHED_SDS_SIZE) {
pool[k].key = sdsdup(key);
} else {
memcpy(pool[k].cached,key,klen+1);
sdssetlen(pool[k].cached,klen);
pool[k].key = pool[k].cached;
}
pool[k].idle = idle;
pool[k].dbid = dbid;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59