redis版本:7.0.2
delCommand位于db.c文件,函数定义如下:
c
/* This command implements DEL and LAZYDEL. */
void delGenericCommand(client *c, int lazy)
{
int numdel = 0, j;
for (j = 1; j < c->argc; j++)
{
expireIfNeeded(c->db, c->argv[j], 0);
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);
}
void delCommand(client *c)
{
delGenericCommand(c, server.lazyfree_lazy_user_del);
}
逻辑梳理:
- 遍历所有的键,接近性删除。首先执行expireIfNeeded,进行过期删除操作;然后判断是否是惰性删除,如果是,则调用dbAsyncDelete异步删除,否则调用dbSyncDelete进行惰性删除。删除成功,则执行signalModifiedKey,通知键被修改,执行notifyKeyspaceEvent,通知键空间事件,server.dirty++,上次保存后的变更计数器+1.
- 返回删除的键的数量。
我们重看一下dbAsyncDelete和dbSyncDelete的实现。这两个函数都位于db.c文件中,源码如下:
c
/* Delete a key, value, and associated expiration entry if any, from the DB
* 从DB中删除一个键、值和相关的过期条目-entry(如果有的话)。
*/
int dbSyncDelete(redisDb *db, robj *key)
{
return dbGenericDelete(db, key, 0);
}
/* Delete a key, value, and associated expiration entry if any, from the DB. If
* the value consists of many allocations, it may be freed asynchronously.
* 从DB中删除一个键、值和相关的过期条目-entry(如果有的话)。如果值由多个分配组成,则可能会异步释放。
*/
int dbAsyncDelete(redisDb *db, robj *key)
{
return dbGenericDelete(db, key, 1);
}
可以看到,两个函数都调用了dbGenericDelete,只是传入的参数不同。
dbGenericDelete
dbGenericDelete也位于db.c中,源码如下:
c
/* Helper for sync and async delete.
* 用于同步和异步删除的辅助函数。
*/
static int dbGenericDelete(redisDb *db, robj *key, int async)
{
/* Deleting an entry from the expires dict will not free the sds of
* the key, because it is shared with the main dictionary.
* 从expires过期字典中删除一个条目不会释放键的sds,因为它与主字典共享。
*/
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.
* 告诉模块,键已经被unlink。
*/
moduleNotifyKeyUnlink(key, val, db->id);
/* We want to try to unblock any client using a blocking XREADGROUP
* 我们想尝试解除任何使用阻塞XREADGROUP(stream类型相关)的客户端。
*/
if (val->type == OBJ_STREAM)
signalKeyAsReady(db, key, val->type);
if (async)
{
freeObjAsync(key, val, db->id);
dictSetVal(db->dict, de, NULL);
}
if (server.cluster_enabled)
slotToKeyDelEntry(de, db);
dictFreeUnlinkedEntry(db->dict, de);
return 1;
}
else
{
return 0;
}
}
逻辑梳理:
- 如果过期字典中有元素,则调用dictDelete函数。
- 调用dictUnlink函数,将entry从全局字典中移除,并返回entry。注意,这里并没有将其释放。
- 如果entry存在,则调用moduleNotifyKeyUnlink函数,通知模块键已经被unlink。
- 如果是stream类型,则调用signalKeyAsReady函数,通知键已经准备好。
- 如果是异步删除,则调用freeObjAsync函数,释放键和值,并将entry的值设置为NULL。
- 如果开启了集群模式,则调用slotToKeyDelEntry函数。
- 执行dictFreeUnlinkedEntry函数,释放entry。
似乎看不出来异步删除和同步删除有什么区别??!! 我们再看仔细看一下freeObjAsync-异步释放对象和dictFreeUnlinkedEntry-释放unlink的entry的实现。
freeObjAsync 源码如下:
c
/* If there are enough allocations to free the value object asynchronously, it
* may be put into a lazy free list instead of being freed synchronously. The
* lazy free list will be reclaimed in a different bio.c thread. If the value is
* composed of a few allocations, to free in a lazy way is actually just
* slower... So under a certain limit we just free the object synchronously.
* 如果
* */
#define LAZYFREE_THRESHOLD 64
/* Free an object, if the object is huge enough, free it in async way.
* 释放一个对象,如果这个对象足够大,则使用异步方式进行释放。
*/
void freeObjAsync(robj *key, robj *obj, int dbid) {
size_t free_effort = lazyfreeGetFreeEffort(key,obj,dbid);
/* Note that if the object is shared, to reclaim it now it is not
* possible. This rarely happens, however sometimes the implementation
* of parts of the Redis core may call incrRefCount() to protect
* objects, and then call dbDelete().
* 注意,如果对象是共享的,现在无法回收它。然而,有时Redis核心的某些部分的实现可能会调用incrRefCount()来保护对象,
* 然后调用dbDelete()。
* */
if (free_effort > LAZYFREE_THRESHOLD && obj->refcount == 1) {
atomicIncr(lazyfree_objects,1);
bioCreateLazyFreeJob(lazyfreeFreeObject,1,obj);
} else {
decrRefCount(obj);
}
}
主要逻辑为:
- 计算释放对象需要的工作量。
- 如果工作量大于阈值,且refcount为1,则调用bioCreateLazyFreeJob函数,创建一个异步释放任务。否则,直接调用decrRefCount函数。
- 否则,执行decryRefcount,缩减应用计数,释放对象。
dictFreeUnlinkedEntry 源码如下:
c
/* You need to call this function to really free the entry after a call
* to dictUnlink(). It's safe to call this function with 'he' = NULL.
* 在调用dictUnlink()后,你需要调用此函数来真正释放entry。可以安全地将此函数与'he' = NULL一起调用???。
*/
void dictFreeUnlinkedEntry(dict *d, dictEntry *he)
{
if (he == NULL)
return;
dictFreeKey(d, he);
dictFreeVal(d, he);
zfree(he);
}
可以看到,代码很少,逻辑为:
- 释放key。
- 释放val。
- 释放entry。