目录
- 为什么要有过期策略?
- 什么是Redis key过期策略?
- key的惰性过期策略
- key的定期过期策略
为什么要有过期策略?
Redis是一个内存型的数据库,数据是放在内存里的,但是内存也是有大小的,所以,需要配置redis占用的最大内存,主要通过maxmemory
配置
maxmomory <bytes> # redis占用的最大内存
官网:https://redis.io/docs/manual/eviction/ 介绍
For example, to configure a memory limit of 100 megabytes, you can use the following directive inside the redis.conf file:
maxmomory 100mb
Setting maxmemory to zero results into no memory limits. This is the default behavior for 64 bit systems, while 32 bit systems use an implicit memory limit of 3GB.
翻译一下,大致意思是如果配置为0,那么模式最大内存大小就是电脑的内存,如果是32bit隐式大小就是3G。
如果我们不淘汰过期的key数据,堆积到一定程度,就会占满内存,满了,就不能再放数据,所以我们需要key过期机制,去删除过期的数据,保证redis的高可用。
什么是Redis key过期策略?
我们知道redis有一个特性,redis中的数据,我们都是可以设置过期时间的,如果时间到了,这个数据就会从Redis中移除。那么redis key的过期策略就是我们怎么将redis中的过期数据移除。
key的惰性过期策略
惰性过期,就是在redis里面,在每次访问操作key的时候,才判断这个key是否过期了,如果过期了就删除数据。redis中主要是通过db.c的expireIfNeeded
方法去判断,调用到相关命令时才会去调用,平时不会去判断是否过期
查看一下源码,expireIfNeeded方法,在db.c源码,基于Redis6.0
int expireIfNeeded(redisDb *db, robj *key) {
if (!keyIsExpired(db,key)) return 0;
// 如果有配置masterhost,说明是从节点,那么不执行key删除操作
if (server.masterhost != NULL) return 1;
server.stat_expiredkeys++;
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
// 判断lazyfree_lazy_expire是否开启,开启执行异步删除,不开启执行同步删除,4.0之后新增的功能,默认是关闭
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
if (retval) signalModifiedKey(NULL,db,key);
return retval;
}
惰性删除策略可以节省CPU资源,因为只需要访问key的时候才去判断是否过期,所以平时是没啥CPU损耗的,但是如果没有再次访问,改过期的key就一直堆积在内存里面,不会被清除,从而占用大量内存空间,所以我们需要另外一种策略来配合使用,解决内存占用问题,就是下面说的key定时过期策略。
key的定期过期策略
Redis中也提供了定期清除过期key的策略,在redis源码里的server.c,里面有个serverCron
方法,这个方法除了做Rehash
以外,还会做很多其他的操作,比如
- 清理数据库中的过期键值对
- 关闭和清理连接失效的客户端
- 尝试进行持久化操作
- 更新服务器的各类统计信息(时间、内存占用、数据库占用情况等)
Redis多久去清除过期的数据,执行频率根据redis.conf
里的配置hz
然后实现流程大概是咋样的?具体实现流程如下:
serverCron
方法去执行定时清理,执行频率redis.conf
的hz
参数配置,默认是10,也就是1s执行10次,100ms执行1次执行清理的时候,去扫描所有设置了过期时间的key,不会去扫描所有的key
根据桶的维度去扫描key,直到扫到20个key(可配)且最多取400个桶。假如第一个桶是15个key,没有达到20个key,所以会继续扫描第二个桶,第二个桶20个key,由于是以桶为维度进行扫描的,第二个桶会被全部扫描,所以总共扫描了35个key
找到扫描的key里面过期的key,进行删除操作
判断扫描的过期数据跟扫描总数的比例是否超过10%,是,继续执行3、4步;否,删除完成。
执行过程,画一个流程图:
查看源码,验证一下,在redis源码里的server.c有一个serverCron
方法,里面有个databasesCron
函数
databasesCron();
同个类里,查看databasesCron
函数
void databasesCron(void) {
if (server.active_expire_enabled) {
if (iAmMaster()) { // 是否主服务器
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
} else { // 从服务器
expireSlaveKeys();
}
}
activeDefragCycle();
if (!hasActiveChildProcess()) {
static unsigned int resize_db = 0;
static unsigned int rehash_db = 0;
int dbs_per_call = CRON_DBS_PER_CALL;
int j;
if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;
for (j = 0; j < dbs_per_call; j++) {
tryResizeHashtables(resize_db % server.dbnum);
resize_db++;
}
if (server.activerehashing) {
for (j = 0; j < dbs_per_call; j++) {
int work_done = incrementallyRehash(rehash_db);
if (work_done) {
break;
} else {
rehash_db++;
rehash_db %= server.dbnum;
}
}
}
}
}
查看activeExpireCycle
方法,在expire.c
void activeExpireCycle(int type) {
unsigned long
effort = server.active_expire_effort-1,
config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
2*effort,
config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
effort;
static unsigned int current_db = 0;
static int timelimit_exit = 0;
static long long last_fast_cycle = 0;
int j, iteration = 0;
int dbs_per_call = CRON_DBS_PER_CALL;
long long start = ustime(), timelimit, elapsed;
if (clientsArePaused()) return;
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
if (!timelimit_exit &&
server.stat_expired_stale_perc < config_cycle_acceptable_stale)
return;
if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
return;
last_fast_cycle = start;
}
if (dbs_per_call > server.dbnum || timelimit_exit)
dbs_per_call = server.dbnum;
timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
timelimit_exit = 0;
if (timelimit <= 0) timelimit = 1;
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
timelimit = config_cycle_fast_duration;
long total_sampled = 0;
long total_expired = 0;
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
unsigned long expired, sampled;
redisDb *db = server.db+(current_db % server.dbnum);
current_db++;
do {
unsigned long num, slots;
long long now, ttl_sum;
int ttl_samples;
iteration++;
if ((num = dictSize(db->expires)) == 0) {
db->avg_ttl = 0;
break;
}
slots = dictSlots(db->expires);
now = mstime();
if (num && slots > DICT_HT_INITIAL_SIZE &&
(num*100/slots < 1)) break;
expired = 0;
sampled = 0;
ttl_sum = 0;
ttl_samples = 0;
// 最多那20个
if (num > config_keys_per_loop)
num = config_keys_per_loop;
long max_buckets = num*20;
long checked_buckets = 0;
// 如果拿到的key数量大于20 或者 checked_buckets大于400,跳出循环
while (sampled < num && checked_buckets < max_buckets) {
for (int table = 0; table < 2; table++) {
if (table == 1 && !dictIsRehashing(db->expires)) break;
unsigned long idx = db->expires_cursor;
idx &= db->expires->ht[table].sizemask;
// 根据index拿到hash桶
dictEntry *de = db->expires->ht[table].table[idx];
long long ttl;
checked_buckets++;
// 循环hash桶里的key
while(de) {
dictEntry *e = de;
de = de->next;
ttl = dictGetSignedIntegerVal(e)-now;
if (activeExpireCycleTryExpire(db,e,now)) expired++;
if (ttl > 0) {
ttl_sum += ttl;
ttl_samples++;
}
sampled++;
}
}
db->expires_cursor++;
}
total_expired += expired;
total_sampled += sampled;
if (ttl_samples) {
long long avg_ttl = ttl_sum/ttl_samples;
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
}
if ((iteration & 0xf) == 0) {
elapsed = ustime()-start;
if (elapsed > timelimit) {
timelimit_exit = 1;
server.stat_expired_time_cap_reached_count++;
break;
}
}
// 比例超过10%,expired过期的key数量,sampled总的扫描数量
} while (sampled == 0 ||
(expired*100/sampled) > config_cycle_acceptable_stale);
}
elapsed = ustime()-start;
server.stat_expire_cycle_time_used += elapsed;
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
double current_perc;
if (total_sampled) {
current_perc = (double)total_expired/total_sampled;
} else
current_perc = 0;
server.stat_expired_stale_perc = (current_perc*0.05)+
(server.stat_expired_stale_perc*0.95);
}
到此这篇关于Redis中key过期策略的实现的文章就介绍到这了,更多相关Redis key过期内容请搜索编程网(www.lsjlt.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.lsjlt.com)!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
软考中级精品资料免费领
- 历年真题答案解析
- 备考技巧名师总结
- 高频考点精准押题
- 资料下载
- 历年真题
193.9 KB下载数265
191.63 KB下载数245
143.91 KB下载数1148
183.71 KB下载数642
644.84 KB下载数2756