利志分享
view_headline
开发工具箱
go教程
clickhouse教程
kafka教程
python教程
shell教程
原创杂文
开发工具箱
go教程
clickhouse教程
kafka教程
python教程
shell教程
原创杂文
深入理解redis的一个del和unlink的命令的执行过程-1
原创杂文 / 时间:2021-10-07 17:43:48 / 阅读:302 / 分享:0
很多同学都用过redis的del,但是unlink这个命令相对来说应该比较陌生一些,del在redis刚开始的第一个版本1.0.0就有了,unlink则不是,unlink是从4.0.0开始有的这个命令。两个命令都是一样的功能,表示删除key。但是它们有什么区别呢?使用需要注意什么问题呢?下面通过了解源码来讲解(redis版本源码是4.0.13)。 从redis/src/server.c中我们找到del和unlink命令的执行命令。 ``` // del这里会调用delCommand方法,是一个普通的写 {"del",delCommand,-2, "write @keyspace", 0,NULL,1,-1,1,0,0,0}, // unlink 这里会调用unlinkCommand方法,write fast,表示是一个快速的写 {"unlink",unlinkCommand,-2, "write fast @keyspace", 0,NULL,1,-1,1,0,0,0}, ``` 这里是定义的命令,分别到redis/src/db.c里面进行执行。这里发现del和unlink是调用的同一个方法delGenericCommand,只是传入的参数不一样,del传入的第二个参数是0,unlink则是1。 ``` void delCommand(client *c) { delGenericCommand(c,server.lazyfree_lazy_user_del); } void unlinkCommand(client *c) { delGenericCommand(c,1); } ``` 下面我们来看下redis/src/db.c里面的delGenericCommand方法。 ``` void delGenericCommand(client *c, int lazy) { int numdel = 0, j; for (j = 1; j < c->argc; j++) { // 自动过期数据清理 expireIfNeeded(c->db,c->argv[j]); // 此处分同步删除和异步删除, 主要差别在于对于复杂数据类型的删除方面,如hash,list,set...当然,这个结论是通过后面的源码分析出来的,这里先写一下结果。针对 string 的删除是完全一样的 int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) : dbSyncDelete(c->db,c->argv[j]); // 命令执行结果记录下来 if (deleted) { signalModifiedKey(c,c->db,c->argv[j]); notifyKeyspaceEvent(NOTIFY_GENERIC, "del",c->argv[j],c->db->id); server.dirty++; numdel++; } } // 响应删除数据量 addReplyLongLong(c,numdel); } ``` del命令执行的是dbSyncDelete方法,unlink命令执行的是dbAsyncDelete方法。 下面我们先看dbSyncDelete删除,方法在redis/src/db.c里面。 ``` int dbSyncDelete(redisDb *db, robj *key) { /* Deleting an entry from the expires dict will not free the sds of * the key, because it is shared with the main dictionary. */ // 首先从 expires 队列删除,然后再从 db->dict 中删除 if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); dictEntry *de = dictUnlink(db->dict,key->ptr); if (de) { robj *val = dictGetVal(de); /* Tells the module that the key has been unlinked from the database. */ moduleNotifyKeyUnlink(key,val); dictFreeUnlinkedEntry(db->dict,de); if (server.cluster_enabled) slotToKeyDel(key->ptr); return 1; } else { return 0; } } ``` 上面我们看到会执行dictDelete,此方法在redis/src/dict.c里面 ``` /* 删除方法,成功返回DICK_OK,否则返回DICK_ERR */ int dictDelete(dict *ht, const void *key) { // nofree: 0, 即要求释放内存 return dictGenericDelete(ht,key,0) ? DICT_OK : DICT_ERR; } /* 查找并删除一个元素,是dictDelete()和dictUnlink()的辅助函数。*/ static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) { uint64_t h, idx; dictEntry *he, *prevHe; int table; if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL; if (dictIsRehashing(d)) _dictRehashStep(d); h = dictHashKey(d, key); // 这里也是需要考虑到rehash的情况,ht[0]和ht[1]中的数据都要删除掉 for (table = 0; table <= 1; table++) { idx = h & d->ht[table].sizemask; he = d->ht[table].table[idx]; prevHe = NULL; while(he) { if (key==he->key || dictCompareKeys(d, key, he->key)) { /* 从列表中unlink掉元素 */ if (prevHe) prevHe->next = he->next; else d->ht[table].table[idx] = he->next; // 如果nofree是0,需要释放k和v对应的内存空间 if (!nofree) { dictFreeKey(d, he); dictFreeVal(d, he); zfree(he); } d->ht[table].used--; return he; } prevHe = he; he = he->next; } if (!dictIsRehashing(d)) break; } return NULL; /* 没找到key对应的数据 */ } ``` 对于有GC收集器的语言来说,根本不用关注内存的释放问题,自有后台工具处理,然而对于 c 语言这种级别语言,则是需要自行关注内存的。所以从上面看数据已经删除了,会涉及到回收。我们来看下回收的代码。 ``` // redis/src/dict.h, 释放key, value,释放依赖于 keyDestructor, valDestructor #define dictFreeKey(d, entry) \ if ((d)->type->keyDestructor) \ (d)->type->keyDestructor((d)->privdata, (entry)->key) #define dictFreeVal(d, entry) \ if ((d)->type->valDestructor) \ (d)->type->valDestructor((d)->privdata, (entry)->v.val) /* 所以,我们有必要回去看看 key,value 的析构方法 而这,又依赖于具体的数据类型,也就是你在 setXXX 的时候用到的数据类型 我们看一下这个 keyDestructor,valDestructor 初始化的样子 redis/src/server.c kv的析构函数定义 */ /* Db->dict, keys are sds strings, vals are Redis objects. */ dictType dbDictType = { dictSdsHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCompare, /* key compare */ dictSdsDestructor, /* key destructor */ dictObjectDestructor, /* val destructor */ dictExpandAllowed /* allow to expand */ }; // key destructor, key 的释放,直接调用 sds 提供的服务即可,redis/src/server.c void dictSdsDestructor(void *privdata, void *val) { DICT_NOTUSED(privdata); // sds 直接释放key就行了 sdsfree(val); } /* 释放sds的占用的空间,redis/src/sds.c */ void sdsfree(sds s) { if (s == NULL) return; // zfree, 确实很简单, 因为 sds 是连续的内存空间,直接使用系统提供的方法即可删除 s_free((char*)s-sdsHdrSize(s[-1])); } // 关于value的释放,如果说 key 一定是string格式的话,value可不一定了,因为 redis提供丰富的数据类型,下面的方式是redis/src/server.c void dictObjectDestructor(void *privdata, void *val) { DICT_NOTUSED(privdata); if (val == NULL) return; /* Lazy freeing will set value to NULL. */ decrRefCount(val); } /* 减少引用计数,如果没有引用了就释放内存空间 在redis/src/object.c文件中 */ void decrRefCount(robj *o) { if (o->refcount == 1) { switch(o->type) { // string 类型 case OBJ_STRING: freeStringObject(o); break; // list 类型 case OBJ_LIST: freeListObject(o); break; // set 类型 case OBJ_SET: freeSetObject(o); break; // zset 类型 case OBJ_ZSET: freeZsetObject(o); break; // hash 类型 case OBJ_HASH: freeHashObject(o); break; case OBJ_MODULE: freeModuleObject(o); break; case OBJ_STREAM: freeStreamObject(o); break; default: serverPanic("Unknown object type"); break; } zfree(o); } else { if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0"); if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--; } } // 下面是释放的方法,也是在redis/src/objec.c文件里面。 void freeStringObject(robj *o) { if (o->encoding == OBJ_ENCODING_RAW) { sdsfree(o->ptr); } } // redis/src/objec.c文件里面。 void freeListObject(robj *o) { if (o->encoding == OBJ_ENCODING_QUICKLIST) { quicklistRelease(o->ptr); } else { serverPanic("Unknown list encoding type"); } } // redis/src/objec.c文件里面。 void freeSetObject(robj *o) { switch (o->encoding) { case OBJ_ENCODING_HT: dictRelease((dict*) o->ptr); break; case OBJ_ENCODING_INTSET: zfree(o->ptr); break; default: serverPanic("Unknown set encoding type"); } } // redis/src/objec.c文件里面。 void freeZsetObject(robj *o) { zset *zs; switch (o->encoding) { case OBJ_ENCODING_SKIPLIST: zs = o->ptr; dictRelease(zs->dict); zslFree(zs->zsl); zfree(zs); break; case OBJ_ENCODING_ZIPLIST: zfree(o->ptr); break; default: serverPanic("Unknown sorted set encoding"); } } // redis/src/objec.c文件里面。 void freeHashObject(robj *o) { switch (o->encoding) { case OBJ_ENCODING_HT: dictRelease((dict*) o->ptr); break; case OBJ_ENCODING_ZIPLIST: zfree(o->ptr); break; default: serverPanic("Unknown hash encoding type"); break; } } /* 在redis/src/dict.c Clear & Release the hash table */ void dictRelease(dict *d) { // ht[0],ht[1] 依次清理 _dictClear(d,&d->ht[0],NULL); _dictClear(d,&d->ht[1],NULL); zfree(d); } /* 在redis/src/dict.c 清理到整个dict */ int _dictClear(dict *d, dictht *ht, void(callback)(void *)) { unsigned long i; /* Free all the elements */ for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; if (callback && (i & 65535) == 0) callback(d->privdata); // 元素为空,hash未命中,但只要 used > 0, 代表就还有需要删除的元素存在 if ((he = ht->table[i]) == NULL) continue; while(he) { nextHe = he->next; // 这里的释放 kv 逻辑和前面是一致的,看起来像是递归,其实不然,因为redis不存在数据类型嵌套问题,比如 hash下存储hash, 所以不会存在递归 dictFreeKey(d, he); dictFreeVal(d, he); zfree(he); ht->used--; he = nextHe; } } /* Free the table and the allocated cache structure */ zfree(ht->table); /* Re-initialize the table */ _dictReset(ht); return DICT_OK; /* never fails */ } ``` 好了上面看了redis的同步删除key的整个执行流程,已经差不多了。下面我们总结一下。 **总结: 1:del和unlink的最大区别是del是同步删除,unlink是异步删除(目前异步删除的还没有讲解,下一篇文件讲) 2:对于线上使用删除的尽量不要使用del,因为同步删除可能会造成本身服务停顿,特别是业务量特别依赖redis的服务。 3:redis的value删除之后的内存回收使用的引用计算器算法。**
按时间分类
2022-04-22
2022-04-10
2022-03-26
2022-03-16
2022-03-08
2022-03-04
2022-02-27
2022-02-26
2022-02-19
2022-01-30
2022-01-23
2021-11-14
2021-11-06
2021-10-07
2021-09-21
2021-09-20
2021-09-15
2021-08-22
2021-08-13
2021-07-28
2021-06-17
2021-05-22
2021-04-23
2021-04-03
2021-03-07
2021-03-05
2021-03-04
2021-02-28
2021-02-21
2021-02-20