赞
踩
我们知道缓存使用内存来保存数据,但内存大小毕竟有限,随着要缓存的数据量越来越大,有限的缓存空间不可避免地会被写满。redis是个基于内存的缓存数据库,既然是基于内存的,那肯定就会有存满的时候。如果真的存满了,再有新的数据过来肯定就存不进去了。这时候就需要缓存的淘汰策略去删除数据。
redis典型的key-value内存存储数据库,因此所有的key和value都保存在dict结构中。在database结构体中,有两个dict:一个是记录key-value,一个是记录key-ttl。
typeof struct redisDb {
dict *dict; // 存放所有key-value的地方,也被成为keyspaces
dict *expires; // 存放每一个可以对应的ttl存活时间,只包含设置的ttl的key
dict *blocking_keys; // keys with clients waiting for data(BLPOP)
dict *ready_keys; // blocked keys that received a PUSH
dict *watched_keys; // watched keys for multi/exec cas
int id; // 数据库ID,默认为:0-15
long avg_ttl; // 记录平均ttl时长
unsigned long expires_cursor; // expire检查时在dict中抽样的索引位置
list *defrag_later; // 等待碎片整理的key的列表
} redisDb;
过期数据-DB结构图:
# 检查一个key在写操作 robj *lookupKeyWriteWithFlags(redisDB *db, robj *key, int flags) { # 检查key是否过期 expireIfNeeded(db, key); return lookup(db, key, flags); } # 检查一个key的读操作 robj *lookupKeyReadWithFlags(redisDB *db, robj *key, int flags) { robj *val; # 检查key是否过期 if(expireIfNeeded(db, key) == 1) { // 略... } return lookup(db, key, flags); } # 判断key是否需要删除 int expireIfNeeded(redisDB *db, robj *key){ // 判断是否过期,若果未过期,直接返回0 if(!keysIfExpired(db, key)) return 0; // 略... // 删除过期key deleteExpiredKeyAndPropagate(db, key); return 1; }
1、redis会设置一个定时任务serverCron(),按照server.hz的频率来执行过期的key清理,模式为SLOW,规则如下:
1、执行频率受server.hz的影响,默认为10,即每秒执行10次,每次执行周期100ms;
2、执行清理耗时不超过一次执行周期的25%;
3、逐个遍历DB,逐个遍历DB中的bucket,抽取20个可以是否过期;
4、如果没有达到时间上限(25ms)并且过期key比例大于10%,再进行一次抽样,否则结束
2、redis每个事件循环前会调用beforeSleep(),执行过期key清理,模式为:FAST,过期key比例小于10%不执行,规则如下:
1、执行频率受beforeSleep()调用频率影响,但两次FAST模式间隔不低于2ms;
2、执行清理耗时不超过1ms;
3、逐个遍历DB,逐个遍历DB中的bucket,抽取20个可以是否过期;
4、如果没有达到时间上限(1ms)并且过期key比例大于10%,再进行一次抽样,否则结束
比较混淆的两种:
LRU(Least Recently Used):最少最近使用。用当前时间减去最近一次访问时间,这个值越大则淘汰的优先级越高。
LFU(Least Frequentl Used):最少频率使用。会统计每个可以的访问频率,值越小则太太的优先级越高。
Redis的数据都会封装到RedisObject结构中:
typeof struct redisObject {
unsigned type:4; // 对象类型
unsigned encoding:4; // 编码方式
unsigned lru:LRU_BITS; // LRU:以秒为单位记录最近一次访问时间,长度24bit
// LFU:高16位以分钟为单位记录最近一次访问时间,低8位记录逻辑访问次数
int rfcount; // 引用计数,计数为0表示可以回收
void *ptr; // 数据指针,指向真是数据
} robj;
LFU的访问次数之所以叫做逻辑访问次数,并不是每次的key被访问都计数,而是通过运算
- 生成0-1之间的随机数R(比如:0.5);
- 计算1/(旧次数 * lfu_log_factor + 1),记录为P,lfu_log_factor 默认为10;
- 如果R < P,则计数器 +1,且最大值不超过255;
- 访问次数会随机衰减,距离上一次访问时间每隔lfu_decay_time分钟(默认1),计数器 -1;
# 获取内存淘汰策略命令
config get maxmemory-policy
#能使用的最大内存大小
config get maxmemory
可以看到当前使用的默认的noeviction策略
如果不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存。32位的机器最大只支持 4GB 的内存,而系统本身就需要一定的内存资源来支持运行,所以 32 位机器限制最大 3 GB 的可用内存
# 设置最大内存上线 # maxmemory <bytes> maxmemory 1g # 设置内存淘汰策略 # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory # is reached. You can select among five behaviors: # # volatile-lru -> remove the key with an expire set using an LRU algorithm # allkeys-lru -> remove any key according to the LRU algorithm # volatile-random -> remove a random key with an expire set # allkeys-random -> remove a random key, any key # volatile-ttl -> remove the key with the nearest expire time (minor TTL) # noeviction -> don't expire at all, just return an error on write operations # The default is: maxmemory-policy noeviction
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。