Redis如何清除过期key? 一篇文章带你走近源码!( 二 )


  • total:已使用的字节总数,无论是C_OK还是C_ERR都有效 。
  • logical:已使用的内存减去slave或AOF缓冲区后的大小,只有返回C_ERR时有效 。
  • tofree:需要释放的内存大小,只有返回C_ERR时有效 。
  • level:已使用内存的比例,通常是0到1之间,当超出内存限制时,就大于1 。无论是C_OK还是C_ERR都有效 。
判断完内存状态以后,如果内存没有超过使用限制就会直接返回,否则就继续向下执行 。此时我们已经知道需要释放多少内存空间了,下面就开始进行释放内存的操作了 。每次释放内存都会记录释放内存的大小,直到释放的内存不小于tofree 。
首先根据maxmemory_policy进行判断,对于不同的清除策略有不同的实现方法,我们来看LRU的具体实现 。
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;}}首先是填充“过期池”,这里遍历了每一个db(验证了我最开始的想法),调用evictionPoolPopulate函数进行填充 。
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);/* some code */if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {idle = estimateObjectIdleTime(o);}/* some code */k = 0;while (k < EVPOOL_SIZE &&pool[k].key &&pool[k].idle < idle) k++;if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {continue;} else if (k < EVPOOL_SIZE && pool[k].key == NULL) {} else {if (pool[EVPOOL_SIZE-1].key == NULL) {sds cached = pool[EVPOOL_SIZE-1].cached;memmove(pool+k+1,pool+k,sizeof(pool[0])*(EVPOOL_SIZE-k-1));pool[k].cached = cached;} else {k--;sds cached = pool[0].cached; /* Save SDS before overwriting. */if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);memmove(pool,pool+1,sizeof(pool[0])*k);pool[k].cached = cached;}}/* some code */}}由于篇幅原因,我截取了部分代码,通过这段代码我们可以看到,Redis首先是采样了一部分key,这里采样数量maxmemory_samples通常是5,我们也可以自己设置,采样数量越大,结果就越接近LRU算法的结果,带来的影响是性能随之变差 。
采样之后我们需要获得每个key的空闲时间,然后将其填充到“过期池”中的指定位置 。这里“过期池”是按照空闲时间从小到大排序的,也就是说,idle大大key排在最右边 。
填充完“过期池”之后,会从后向前获取到最适合清理的key 。
/* 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);}/* some code */if (de) {bestkey = dictGetKey(de);break;}}找到需要删除的key后,就需要根据设置清理策略进行同步/异步清理 。
if (server.lazyfree_lazy_eviction)dbAsyncDelete(db,keyobj);elsedbSyncDelete(db,keyobj)最后记下本次清理的空间大小,用来在循环条件判断是否要继续清理 。
delta -= (long long) zmalloc_used_memory();mem_freed += delta;清理策略最后我们来看一下Redis支持的几种清理策略
  • noeviction:不会继续处理写请求(DEL可以继续处理) 。
  • allkeys-lru:对所有key的近似LRU
  • volatile-lru:使用近似LRU算法淘汰设置了过期时间的key
  • allkeys-random:从所有key中随机淘汰一些key
  • volatile-random:对所有设置了过期时间的key随机淘汰
  • volatile-ttl:淘汰有效期最短的一部分key
Redis4.0开始支持了LFU策略,和LRU类似,它分为两种:
  • volatile-lfu:使用LFU算法淘汰设置了过期时间的key
  • allkeys-lfu:从全部key中进行淘汰,使用LFU
写在最后现在我知道了Redis在内存达到上限时做了哪些事了 。以后出问题时也就不会只检查自己的db了 。
关于这次事故的后续处理,我首先是让业务同学回滚了代码,然后让他们使用一个单独的Redis,这样业务再出现类似问题就不会影响到我们的帐号服务了,整体的影响范围也会变得更加可控 。
原文链接:https://www.cnblogs.com/Jackeyzhe/p/12616624.html

【Redis如何清除过期key? 一篇文章带你走近源码!】


推荐阅读