从源码分析 Redis 异步删除各个参数的具体作用

这篇具有很好参考价值的文章主要介绍了从源码分析 Redis 异步删除各个参数的具体作用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

所以这次从源码(基于 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),依然会阻塞主线程。文章来源地址https://www.toymoban.com/news/detail-747245.html

到了这里,关于从源码分析 Redis 异步删除各个参数的具体作用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 电脑键盘各个按键作用讲解

    各按键作用解读 1、F键功能键盘区 Esc键:英文Escape 的缩写,中文意思是逃脱、出口等。该键为取消键,在电脑的应用中主要的作用为放弃当前操作或结束程序; F1~F12键:功能键,英文Function,中文为“功能”的意思。在不同的软件中,这12个功能键在不同的应用软件和程序中

    2024年02月13日
    浏览(27)
  • nsq整体架构及各个部件作用详解

    文章目录         前言         nsq的整体架构图         部件:nsqd         部件:nsqlookupd         部件:nsq连接库         部件:nsqadmin  前言 上两篇博客 centos环境搭建nsq单点_YZF_Kevin的博客-CSDN博客 linux环境搭建nsq集群_YZF_Kevin的博客-CSDN博客 我们讲了

    2024年02月15日
    浏览(24)
  • 设计模式 代理模式(静态代理 动态代理) 与 Spring Aop源码分析 具体是如何创建Aop代理的

    代理模式是一种结构型设计模式,它通过创建一个代理对象来控制对真实对象的访问。这种模式可以用于提供额外的功能操作,或者扩展目标对象的功能。 在代理模式中,代理对象与真实对象实现相同的接口,以便在任何地方都可以使用相同的接口来调用真实对象的方法。这

    2024年01月20日
    浏览(29)
  • TCP协议详解—TCP各个报头属性的作用

      首先我们需要知道TCP是什么,TCP全称是传输控制协议(Transmission Control Protocol),它是一种面向连接的、可靠的、基于字节流的传输层通信协议。   网络传输,无非就是要做两件事情,一是做决策,二是做执行,在网络传输中,传输层主要做的是决策,下面的层做执行。

    2024年02月03日
    浏览(33)
  • Pytest的setup/teardown各个方法的作用域

    一、模块级别 二、模块级别(方法) setup_function: #模块中每条用例执行前执行一次,不会作用于class中的test_case teardown_function: #模块中每条用例执行后执行一次,不会作用于class中的test_case 三、测试类级别 四、测试类级别(测试方法)

    2024年02月12日
    浏览(23)
  • openxr runtime Monado 源码解析 源码分析:整体介绍 模块架构 模块作用 进程 线程模型 整体流程

    monado系列文章索引汇总: openxr runtime Monado 源码解析 源码分析:源码编译 准备工作说明 hello_xr解读 openxr runtime Monado 源码解析 源码分析:整体介绍 模块架构 模块作用 进程 线程模型 整体流程 openxr runtime Monado 源码解析 源码分析:CreateInstance流程(设备系统和合成器系统)C

    2024年02月11日
    浏览(68)
  • Linux /dev目录详解和Linux系统各个目录的作用

    在linux下,/dev目录是很重要的,各种设备都在下面。下面简单总结一下: dev是设备(device)的英文缩写。 /dev这个目录对所有的用户都十分重要。 因为在这个目录中包含了所有Linux系统中使用的外部设备。但是这里并不是放的外部设备的驱动程序,这一点和 windows ,dos操作系统不

    2024年04月11日
    浏览(36)
  • 异步非阻塞事件驱动架构的具体流程解析

    异步非阻塞事件驱动架构是一种高效的编程和系统设计模式,特别适用于需要处理大量并发连接和请求的应用,如Web服务器。 1. 初始化和启动 启动过程 :当Nginx启动时,它的主进程初始化配置并启动多个工作进程。 工作进程创建 :这些工作进程独立于主进程,每个进程都能

    2024年01月22日
    浏览(27)
  • 解读手机相机的各个参数(长曝光)

     长曝光,就是长时间曝光的意思。通过控制快门速度来完成。快门速度是控制进光时间的。长时间曝光就是把相机调整的快门尽量慢。 长曝光是选慢快门(曝光时间长)的一种曝光方式。好处就是可以把光线暗的景色拍的更清晰,也可以拍出如梦幻般的画面,比如瀑布和城市

    2024年02月15日
    浏览(26)
  • 超外差半导体收音机:各个元器件的作用,如何进行调试,以及工作原理

    在我的blink实习手册里面都有记录,这里就不在进行过多的叙述了。 电阻主要是起到了分压的作用,如果没有电阻为三极管的各极提供合适的工作电压,三极管无法起到放大作用。 有些电阻不但起到分压的作用,还起到限流的作用,就是作为电流的反馈元件来进行使用的,它

    2024年02月16日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包