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

所以这次从源码(基于 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. 基于AIidlux平台的自动驾驶环境感知与智能预警

    自动驾驶汽车又称为无人驾驶车,是一种需要驾驶员辅助或者完全不需操控的车辆. 自动驾驶分级: 自动驾驶系统的组成部分: 环境感知系统: 自动驾驶系统架构: 自动驾驶数据集: Aidlux的作用: YOL ...

  2. 五 Android Capabilities讲解(转)

    1.Capabilities介绍 可以看下之前代码里面设置的capabilities DesiredCapabilities capabilities = new DesiredCapabilitie ...

  3. 青少年CTF平台-Web-Robots

    题目信息 题目名称:Robots 题目描述:昨天十三年社团讲课,讲了Robots.txt的作用,小刚上课没有认真听课正在着急,你能不能帮帮忙? 题目难度:一颗星 解题过程 访问题目链接 在这里插入图片 ...

  4. noip2022保龄记

    第一次参加noip,写第一篇游记纪念一下 天还挺热,今天就穿了个秋衣加卫衣,本来还打算穿袄子来着,但是爸妈没让 到了八中才发现好像没带水,然后我的老父亲跑到不知道哪里去给买了一瓶(八中门口看不到有小卖 ...

  5. [django]路由变量与正则表达式

    示例: urlpatterns = [ path('detail.<int:id>.html', detailView, name='detail'), ] 路由变量的类型 示例路由配置了 ...

  6. Pixi.js的使用整理

    最近在做的一个前端项目中,有一些图像的处理操作(3D图,2D图都有),其中3D图也是通过获取后端服务的图像2D数据进行绘制展示,通过鼠标各种操作调用后端服务来进行重新获取新图数据.这里前端设计到图像的 ...

  7. 《Kali渗透基础》11. 无线渗透(一)

    @ 目录 1:无线技术 2:IEEE 802.11 标准 2.1:无线网络分层 2.2:IEEE 2.3:日常使用标准 2.3.1:802.11 2.3.2:802.11b 2.3.3:802.11a ...

  8. 数据可视化【原创】vue复合数字形式展示

    做数据可视化的时候,经常碰到需要很灵活的数字形式展示. 先上个效果图: 如图包括名称,数量,别名,单位,上升下降,环比等等的复合数据展示,并且需要支持样式灵活配置. 此组件包括2个模块,父容器组件bo ...

  9. MutationObserver监听dom元素结构及属性变化

    工作中埋码需求,当某些动态插入的元素出现时触发埋码事件,因此需要对插入元素的父节点进行监听,子节点发生变化时触发相应埋码逻辑. 方法一 监听页面结构及子元素变化: (function () { //事 ...

  10. flask中cookies的使用

    flask中cookies的使用 在Flask中对cookie的处理 1. 设置cookie: 设置cookie,默认有效期是临时cookie,浏览器关闭就失效 可以通过 max_age 设置有效期, ...