Redis源码阅读之del命令

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);
}

逻辑梳理:

  1. 遍历所有的键,接近性删除。首先执行expireIfNeeded,进行过期删除操作;然后判断是否是惰性删除,如果是,则调用dbAsyncDelete异步删除,否则调用dbSyncDelete进行惰性删除。删除成功,则执行signalModifiedKey,通知键被修改,执行notifyKeyspaceEvent,通知键空间事件,server.dirty++,上次保存后的变更计数器+1.
  2. 返回删除的键的数量。

我们重看一下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;
    }
}

逻辑梳理:

  1. 如果过期字典中有元素,则调用dictDelete函数。
  2. 调用dictUnlink函数,将entry从全局字典中移除,并返回entry。注意,这里并没有将其释放。
  3. 如果entry存在,则调用moduleNotifyKeyUnlink函数,通知模块键已经被unlink。
  4. 如果是stream类型,则调用signalKeyAsReady函数,通知键已经准备好。
  5. 如果是异步删除,则调用freeObjAsync函数,释放键和值,并将entry的值设置为NULL。
  6. 如果开启了集群模式,则调用slotToKeyDelEntry函数。
  7. 执行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);
    }
}

主要逻辑为:

  1. 计算释放对象需要的工作量。
  2. 如果工作量大于阈值,且refcount为1,则调用bioCreateLazyFreeJob函数,创建一个异步释放任务。否则,直接调用decrRefCount函数。
  3. 否则,执行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);
}

可以看到,代码很少,逻辑为:

  1. 释放key。
  2. 释放val。
  3. 释放entry。
相关推荐
2401_882727574 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
追逐时光者5 小时前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
大梦百万秋5 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
斌斌_____5 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@6 小时前
Spring如何处理循环依赖
java·后端·spring
海绵波波1077 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
小奏技术7 小时前
RocketMQ结合源码告诉你消息量大为啥不需要手动压缩消息
后端·消息队列
AI人H哥会Java9 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
凡人的AI工具箱9 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django
奔跑草-9 小时前
【数据库】SQL应该如何针对数据倾斜问题进行优化
数据库·后端·sql·ubuntu