以前对异步删除几个参数的作用比较模糊,包括网上的很多资料都是一笔带过,语焉不详。

所以这次从源码(基于 Redis 7.0.5)的角度来深入分析下这几个参数的具体作用:

  • lazyfree-lazy-user-del
  • lazyfree-lazy-user-flush
  • lazyfree-lazy-server-del
  • lazyfree-lazy-expire
  • lazyfree-lazy-eviction
  • slave-lazy-flush

lazyfree-lazy-user-del

在 Redis 4.0 之前,通常不建议直接使用 DEL 命令删除一个 KEY。这是因为,如果这个 KEY 是一个包含大量数据的大 KEY,那么这个删除操作就会阻塞主线程,导致 Redis 无法处理其他请求。这种情况下,一般是建议分而治之,即批量删除 KEY 中的元素。

在 Redis 4.0 中,引入了异步删除机制,包括一个新的命令 -UNLINK。该命令的作用同DEL一样,都用来删除 KEY。只不过DEL命令是在主线程中同步执行删除操作。而UNLINK命令则是通过后台线程异步执行删除操作,即使碰到一个大 KEY,也不会导致主线程被阻塞。

如果应用之前用的是DEL,要使用UNLINK,就意味着代码需要改造,而代码改造显然是个费时费力的事情。

为了解决这个痛点,在 Redis 6.0 中,引入了参数 lazyfree-lazy-user-del。将该参数设置为 yes(默认为 no),则通过DEL命令删除 KEY,效果同UNLINK一样,都是执行异步删除操作。

以下是DEL命令和UNLINK命令的实现代码。

// DEL 命令调用的函数
void delCommand(client *c) {
    delGenericCommand(c,server.lazyfree_lazy_user_del);
}

// UNLINK 命令调用的函数
void unlinkCommand(client *c) {
    delGenericCommand(c,1);
}

可以看到,当 server.lazyfree_lazy_user_del 设置为 yes 时,DEL命令实际上调用的就是 delGenericCommand(c,1),与UNLINK命令一样。

lazyfree-lazy-user-flush

在 Redis 中,如果要清除整个数据库的数据,可使用FLUSHALL(清除所有数据库的数据)或 FLUSHDB(清除当前数据库的数据)。

在 Redis 4.0 之前,这两个命令都是在主线程中执行的。如果要清除的 KEY 比较多,同样会导致主线程被阻塞。

如果使用的是 Redis Cluster,在执行此类操作时,很容易会触发主从切换。

主要原因是在删除期间,主节点无法响应集群其它节点的心跳请求。如果没有响应持续的时间超过 cluster-node-timeout(默认15 秒),主节点就会被集群其它节点判定为故障,进而触发故障切换流程,将从节点提升为主节点。

这个时候,原主节点会降级为从节点,降级后,原主节点又会重新从新的主节点上同步数据。所以,虽然原主节点上执行了 FLUSH 操作,但发生故障切换后,数据又同步过来了。如果再对新的主节点执行 FLUSH 操作,同样会触发主从切换。

所以,在这种情况下,建议将参数 cluster-node-timeout 调整为一个比较大的值(默认是 15 秒),这样就可以确保主节点有充足的时间来执行 FLUSH 操作而不会触发切换流程。

在 Redis 4.0 中,FLUSHALL 和 FLUSHDB 命令新增了一个 ASYNC 修饰符,可用来进行异步删除操作。如果不加 ASYNC,则还是主线程同步删除。

FLUSHALL ASYNC
FLUSHDB ASYNC

在 Redis 6.2.0 中,FLUSHALLFLUSHDB命令又新增了一个 SYNC 修饰符,它的效果与之前的FLUSHALLFLUSHDB命令一样,都是用来进行同步删除操作。

既然效果一样,为什么要引入这个修饰符呢?这实际上与 Redis 6.2.0 中引入的 lazyfree-lazy-user-flush 参数有关。该参数控制了没有加修饰符的FLUSHALLFLUSHDB命令的行为。

默认情况下,lazyfree-lazy-user-flush 的值为 no,这意味着 FLUSHALL/FLUSHDB 将执行同步删除操作。如果将 lazyfree-lazy-user-flush 设置为 yes,即使不加 ASYNC 修饰符,FLUSHALL/FLUSHDB 也会进行异步删除。

以下是 lazyfree-lazy-user-flush 参数的相关代码:

/* Return the set of flags to use for the emptyDb() call for FLUSHALL
 * and FLUSHDB commands.
 *
 * sync: flushes the database in an sync manner.
 * async: flushes the database in an async manner.
 * no option: determine sync or async according to the value of lazyfree-lazy-user-flush.
 *
 * On success C_OK is returned and the flags are stored in *flags, otherwise
 * C_ERR is returned and the function sends an error to the client. */
int getFlushCommandFlags(client *c, int *flags) {
    /* Parse the optional ASYNC option. */
    if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"sync")) {
        *flags = EMPTYDB_NO_FLAGS;
    } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"async")) {
        *flags = EMPTYDB_ASYNC;
    } else if (c->argc == 1) {
        *flags = server.lazyfree_lazy_user_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS;
    } else {
        addReplyErrorObject(c,shared.syntaxerr);
        return C_ERR;
    }
    return C_OK;
}

可以看到,在不指定任何修饰符的情况下(c->argc == 1),修饰符的取值由 server.lazyfree_lazy_user_flush 决定。

lazyfree-lazy-server-del

lazyfree-lazy-server-del 主要用在两个函数中:dbDeletedbOverwrite。这两个函数的实现代码如下:

/* This is a wrapper whose behavior depends on the Redis lazy free
 * configuration. Deletes the key synchronously or asynchronously. */
int dbDelete(redisDb *db, robj *key) {
    return dbGenericDelete(db, key, server.lazyfree_lazy_server_del);
}

/* Overwrite an existing key with a new value. Incrementing the reference
 * count of the new value is up to the caller.
 * This function does not modify the expire time of the existing key.
 *
 * The program is aborted if the key was not already present. */
void dbOverwrite(redisDb *db, robj *key, robj *val) {
    dictEntry *de = dictFind(db->dict,key->ptr);
    ...
    if (server.lazyfree_lazy_server_del) {
        freeObjAsync(key,old,db->id);
        dictSetVal(db->dict, &auxentry, NULL);
    }

    dictFreeVal(db->dict, &auxentry);
}

下面我们分别看看这两个函数的使用场景。

dbDelete

dbDelete 函数主要用于 Server 端的一些内部删除操作,常用于以下场景:

  1. 执行MIGRATE命令时,删除源端实例的 KEY。

  2. RESTORE命令中,如果指定了 REPLACE 选项,当指定的 KEY 存在时,会调用 dbDelete 删除这个 KEY。

  3. 通过POPTRIM之类的命令从列表(List),集合(Set),有序集合(Sorted Set)中弹出或者移除元素时,当 KEY 为空时,会调用 dbDelete 删除这个 KEY。

  4. SINTERSTOREZINTERSTORE等 STORE 命令中。这些命令会计算多个集合(有序集合)的交集、并集、差集,并将结果存储在一个新的 KEY 中。如果交集、并集、差集的结果为空,当用来存储的 KEY 存在时,会调用 dbDelete 删除这个 KEY。

dbOverwrite

dbOverwrite 主要是用于 KEY 存在的场景,新值覆盖旧值。主要用于以下场景:

  1. SET 相关的命令。如 SETSETNXSETEXHSETMSET
  2. SINTERSTOREZINTERSTORE 等 STORE 命令中。如果交集、并集、差集的结果不为空,且用来存储的 KEY 存在,则该 KEY 的值会通过 dbOverwrite 覆盖。

lazyfree-lazy-expire

lazyfree-lazy-expire 主要用于以下四种场景:

1. 删除过期 KEY,包括主动访问时删除和 Redis 定期删除。

不仅如此,该参数还决定了删除操作传播给从库及写到 AOF 文件中是用DEL还是UNLINK

/* Delete the specified expired key and propagate expire. */
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
    mstime_t expire_latency;
    latencyStartMonitor(expire_latency);
    // 删除过期 KEY
    if (server.lazyfree_lazy_expire)
        dbAsyncDelete(db,keyobj);
    else
        dbSyncDelete(db,keyobj);
    latencyEndMonitor(expire_latency);
    latencyAddSampleIfNeeded("expire-del",expire_latency);
    notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",keyobj,db->id);
    signalModifiedKey(NULL, db, keyobj);
    // 将删除操作传播给从库及写到 AOF 文件中
    propagateDeletion(db,keyobj,server.lazyfree_lazy_expire);
    server.stat_expiredkeys++;
}

2. 主库启动,加载 RDB 的时候,当碰到过期 KEY 时,该参数决定了删除操作传播给从库是用DEL还是UNLINK

if (iAmMaster() &&
    !(rdbflags & RDBFLAGS_AOF_PREAMBLE) &&
    expiretime != -1 && expiretime < now)
{
    if (rdbflags & RDBFLAGS_FEED_REPL) {
        /* Caller should have created replication backlog,
         * and now this path only works when rebooting,
         * so we don't have replicas yet. */
        serverAssert(server.repl_backlog != NULL && listLength(server.slaves) == 0);
        robj keyobj;
        initStaticStringObject(keyobj, key);
        robj *argv[2];
        argv[0] = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
        argv[1] = &keyobj;
        replicationFeedSlaves(server.slaves, dbid, argv, 2);
    }
    sdsfree(key);
    decrRefCount(val);
    server.rdb_last_load_keys_expired++;
}

3. EXPIRE,PEXPIREEXPIREATPEXPIREAT命令中,当设置的时间过期时(譬如 EXPIRE/PEXPIRE 中指定了负值或者 EXPIREAT/PEXPIREAT指定了过去的时间戳),将导致 KEY 被删除而不是过期。

4. GETEX命令中,如果通过 EXAT unix-time-seconds 或者 PXAT unix-time-milliseconds 指定了过期时间,当指定的时间戳过期时,将导致 KEY 被删除而不是过期。

lazyfree-lazy-eviction

当 Redis 内存不足时,会删除部分 KEY 来释放内存。

lazyfree-lazy-eviction 决定了KEY 删除的方式及删除操作传播给从库和写到 AOF 文件中是用DEL还是UNLINK

/* Finally remove the selected key. */
if (bestkey) {
    db = server.db + bestdbid;
    robj *keyobj = createStringObject(bestkey, sdslen(bestkey));
    delta = (long long) zmalloc_used_memory();
    latencyStartMonitor(eviction_latency);
    // 删除 KEY 
    if (server.lazyfree_lazy_eviction)
        dbAsyncDelete(db, keyobj);
    else
        dbSyncDelete(db, keyobj);
    latencyEndMonitor(eviction_latency);
    latencyAddSampleIfNeeded("eviction-del", eviction_latency);
    delta -= (long long) zmalloc_used_memory();
    mem_freed += delta;
    server.stat_evictedkeys++;
    signalModifiedKey(NULL, db, keyobj);
    notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted", keyobj, db->id);
    // 将删除操作传播给从库并写到 AOF 文件中
    propagateDeletion(db, keyobj, server.lazyfree_lazy_eviction);
    decrRefCount(keyobj);
    keys_freed++;
    ...
}

slave-lazy-flush

Redis 主从复制中,从节点在加载主节点的 RDB 文件之前,首先会清除自身的数据,slave-lazy-flush 决定了数据清除的方式。

/* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(connection *conn) {
    char buf[PROTO_IOBUF_LEN];
    ssize_t nread, readlen, nwritten;
    int use_diskless_load = useDisklessLoad();
    redisDb *diskless_load_tempDb = NULL;
    functionsLibCtx* temp_functions_lib_ctx = NULL;
    int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
                                                        EMPTYDB_NO_FLAGS;
    ...
    if (use_diskless_load && server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
        /* Initialize empty tempDb dictionaries. */
        diskless_load_tempDb = disklessLoadInitTempDb();
        temp_functions_lib_ctx = functionsLibCtxCreate();

        moduleFireServerEvent(REDISMODULE_EVENT_REPL_ASYNC_LOAD,
                              REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED,
                              NULL);
    } else {
        replicationAttachToNewMaster();

        serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
        emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
    }
    ...
}

总结

综合上面的分析,异步删除各参数的作用如下,

注意,这几个参数的默认值都是 no。

另外,在通过POPTRIM之类的命令从列表(List),集合(Set),有序集合(Sorted Set)中弹出或者移除元素时,对于这些元素的删除都是同步的,并不会异步删除。如果元素的值过大(最大值由 proto-max-bulk-len 决定,默认是 512MB),依然会阻塞主线程。

从源码分析 Redis 异步删除各个参数的具体作用的更多相关文章

  1. jQuery源码分析(九) 异步队列模块 Deferred 详解

    deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...

  2. [Abp vNext 源码分析] - 11. 用户的自定义参数与配置

    一.简要说明 文章信息: 基于的 ABP vNext 版本:1.0.0 创作日期:2019 年 10 月 23 日晚 更新日期:暂无 ABP vNext 针对用户可编辑的配置,提供了单独的 Volo. ...

  3. tornado框架源码分析---Application类之debug参数

    先贴上Application这个类的源码. class Application(httputil.HTTPServerConnectionDelegate): """A ...

  4. dubbo源码分析5——SPI机制_AdaptiveExtension的原理和作用

    private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().n ...

  5. redis源码分析之发布订阅(pub/sub)

    redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...

  6. whatwg-fetch源码分析

    fetch 是什么 XMLHttpRequest的最新替代技术 fetch优点 接口更简单.简洁,更加语义化 基于promise,更加好的流程化控制,可以不断then把参数传递,外加 async/aw ...

  7. 【转载】Redis 4.0 自动内存碎片整理(Active Defrag)源码分析

    click原文链接原文链接:https://blog.csdn.net/zouhuajianclever/article/details/90669409阅读本文前建议先阅读此篇博客: Redis源码 ...

  8. 【源码】Redis exists命令bug分析

    本文基于社区版Redis 4.0.8 1.复现条件 版本:社区版Redis 4.0.10以下版本 使用场景:开启读写分离的主从架构或者集群架构(master只负责写流量,slave负责读流量) 案例: ...

  9. Redis学习——ae事件处理源码分析

    0. 前言 Redis在封装事件的处理采用了Reactor模式,添加了定时事件的处理.Redis处理事件是单进程单线程的,而经典Reator模式对事件是串行处理的.即如果有一个事件阻塞过久的话会导致整 ...

  10. redis源码分析之事务Transaction(上)

    这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...

随机推荐

  1. 26种source-map看花了眼?别急,理解这几个全弄懂

    上一篇 webpack处理模块化源码 的文章中提到了 "source map",这一篇来详细说说. 有什么作用 source map 用于映射编译后的代码与源码,这样如果编译后的代 ...

  2. 如何用windows任务视图管理多个程序,提高.net开发效率

    在 Windows 操作系统中,任务栏是一个非常重要的工具栏,用来显示当前正在运行的程序和任务.如果同时运行了很多程序,任务栏上的图标就会变得非常拥挤,不方便管理和切换.为了提高工作效率,可以通过任务 ...

  3. Hugging Face 的文本生成和大语言模型的开源生态

    [更新于 2023 年 7 月 23 日: 添加 Llama 2.] 文本生成和对话技术已经出现多年了.早期的挑战在于通过设置参数和分辨偏差,同时控制好文本忠实性和多样性.更忠实的输出一般更缺少创造性 ...

  4. 移动优先与响应式Web设计

    1.加速度计-设备置向 2.下拉更新-Iscroll 3.滑动以得到更多选项-panel 4.Sketch a Search 雅虎 画圈搜索 5.<Tapeworthy> 移动用户的行为特 ...

  5. 11、Spring之基于注解的AOP

    11.1.环境搭建 创建名为spring_aop_annotation的新module,过程参考9.1节 11.1.1.配置打包方式和依赖 注意:AOP需要在IOC的基础上实现,因此需要导入IOC的依 ...

  6. ChatGPT赋能低代码开发:打造智能应用的双重引擎

    摘要:本文摘自葡萄城低代码产品活字格的资深用户(格友超哥)所撰写的文章:<惊叹表现!活字格+ChatGPT:低代码开发智能应用的巨大潜力>. ChatGPT的functions函数使用方 ...

  7. Unity UGUI的Toggle(复选框)组件的介绍及使用

    Unity UGUI的Toggle(复选框)组件的介绍及使用 1. 什么是Toggle组件? Toggle(复选框)是Unity UGUI中的一个常用组件,用于实现复选框的功能.它可以被选中或取消选中 ...

  8. 【matplotlib基础】--刻度

    Matplotlib中刻度是用于在绘图中表示数据大小的工具. 刻度是坐标轴上的数字或标签,用于指示数据的大小或值,通常以整数或小数表示,具体取决于坐标轴的类型和限制. 1. 主次刻度 默认的绘制时,坐 ...

  9. 初露头角!Walrus入选服贸会“数智影响力”数字化转型创新案例

    9月5日,由北京市通信管理局.工业和信息化部新闻宣传中心联合主办的"企业数字化转型论坛"在2023中国国际服务贸易交易会期间召开,论坛以"数字化引领 高质量发展" ...

  10. SK 简化流行编程语言对 生成式AI 应用开发的支持

    Semantic Kernel[1] 是一个将大型语言模型(LLM)与流行的编程语言相结合的SDK. Microsoft将Semantic Kernel(简称SK)称为轻量级SDK,支持AI LLM的 ...