redis是一个内存数据库,必然就存在比较多的内存释放,然而内存释放又是一个比较重的操作,这样就会影响redis的正常运转效率。所以,redis对于部分内存的释放采取了懒释放。空间懒释放就是将内存的释放交由专门的线程进行释放操作。
redis懒释放的空间主要包括三种:
1)对象空间的释放
2)DB空间的异步释放
3)slots-keys空间释放
对象空间的释放
对象释放的场景主要有del命令,过期键删除,内存淘汰等三种。对象释放是否采用懒释放取决于相关配置:
1)del命令——server.lazyfree_lazy_server_del
2) 过期键删除——server.lazyfree_lazy_expire
3)内存淘汰——lazyfree_lazy_eviction
del操作
int dbDelete(redisDb *db, robj *key) {
return server.lazyfree_lazy_server_del ? dbAsyncDelete(db,key):dbSyncDelete(db,key);
}
过期键删除
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
long long t = dictGetSignedIntegerVal(de);
if (now > t) {
……
if (server.lazyfree_lazy_expire)
dbAsyncDelete(db,keyobj);
else
dbSyncDelete(db,keyobj);
}
}
内存淘汰
int freeMemoryIfNeeded(void) {
……
if (bestkey) {
if (server.lazyfree_lazy_eviction)
dbAsyncDelete(db,keyobj);// 异步释放
else
dbSyncDelete(db,keyobj);//同步释放
……
}
}
对象空间懒释放
#define LAZYFREE_THRESHOLD 64
int dbAsyncDelete(redisDb *db, robj *key) {
dictEntry *de = dictUnlink(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de);
size_t free_effort = lazyfreeGetFreeEffort(val);//计算释放对象的大小
//释放对象的大小大于LAZYFREE_THRESHOLD,放入异步线程组的队列,异步释放
if (free_effort > LAZYFREE_THRESHOLD) {
atomicIncr(lazyfree_objects,1,lazyfree_objects_mutex);
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
dictSetVal(db->dict,de,NULL);
}
}//释放对象小于LAZYFREE_THRESHOLD,直接同步释放
if (de) {
dictFreeUnlinkedEntry(db->dict,de);
if (server.cluster_enabled) slotToKeyDel(key);
return 1;
}
}
DB空间的懒释放
当需要异步删除DB的时候,redis会申请新的哈希表作为新的DB,原DB内存的释放操作交给异步任务处理线程执行。
//清空DB
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
int j, async = (flags & EMPTYDB_ASYNC);
……
for (j = 0; j < server.dbnum; j++) {
if (dbnum != -1 && dbnum != j) continue;
removed += dictSize(server.db[j].dict);
if (async) {
emptyDbAsync(&server.db[j]);
}
……
}
……
return removed;
}//异步清空DB
void emptyDbAsync(redisDb *db) {
dict *oldht1 = db->dict, *oldht2 = db->expires;
db->dict = dictCreate(&dbDictType,NULL);//创建新的DB
db->expires = dictCreate(&keyptrDictType,NULL);
atomicIncr(lazyfree_objects,dictSize(oldht1),
lazyfree_objects_mutex);
//放入异步任务处理线程队列,异步释放DB的内存空间
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
}
slots-keys空间释放
在redis cluster模式下,slots-keys用于记录键值对存放的slot对映关系,由跳跃表数据结构保存。当redis cluster需要清空slots-keys的时候,会先创建一个新的跳跃表作为新的slots-keys,然后将老的slots-keys释放工作交给异步任务处理线程。
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
int j, async = (flags & EMPTYDB_ASYNC);
……
if (server.cluster_enabled) {
if (async) {// 异步清空slots-key
slotToKeyFlushAsync();
} else {
slotToKeyFlush();
}
}
}
void slotToKeyFlushAsync(void) {
zskiplist *oldsl = server.cluster->slots_to_keys;
server.cluster->slots_to_keys = zslCreate();
atomicIncr(lazyfree_objects,oldsl->length,
lazyfree_objects_mutex);
//放入异步任务处理线程队列,异步释放slots_to_keys的内存空间
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,oldsl);
}