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

打印 上一主题 下一主题

主题 853|帖子 853|积分 2559

以前对异步删除几个参数的作用比较模糊,包括网上的很多资料都是一笔带过,语焉不详。
所以这次从源码(基于 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命令的实现代码。
  1. // DEL 命令调用的函数
  2. void delCommand(client *c) {
  3.     delGenericCommand(c,server.lazyfree_lazy_user_del);
  4. }

  5. // UNLINK 命令调用的函数
  6. void unlinkCommand(client *c) {
  7.     delGenericCommand(c,1);
  8. }
复制代码
可以看到,当 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,则还是主线程同步删除。
  1. FLUSHALL ASYNC
  2. FLUSHDB ASYNC
复制代码
在 Redis 6.2.0 中,FLUSHALL和FLUSHDB命令又新增了一个 SYNC 修饰符,它的效果与之前的FLUSHALL和FLUSHDB命令一样,都是用来进行同步删除操作。
既然效果一样,为什么要引入这个修饰符呢?这实际上与 Redis 6.2.0 中引入的 lazyfree-lazy-user-flush 参数有关。该参数控制了没有加修饰符的FLUSHALL和FLUSHDB命令的行为。
默认情况下,lazyfree-lazy-user-flush 的值为 no,这意味着 FLUSHALL/FLUSHDB 将执行同步删除操作。如果将 lazyfree-lazy-user-flush 设置为 yes,即使不加 ASYNC 修饰符,FLUSHALL/FLUSHDB 也会进行异步删除。
以下是 lazyfree-lazy-user-flush 参数的相关代码:
  1. /* Return the set of flags to use for the emptyDb() call for FLUSHALL
  2.  * and FLUSHDB commands.
  3.  *
  4.  * sync: flushes the database in an sync manner.
  5.  * async: flushes the database in an async manner.
  6.  * no option: determine sync or async according to the value of lazyfree-lazy-user-flush.
  7.  *
  8.  * On success C_OK is returned and the flags are stored in *flags, otherwise
  9.  * C_ERR is returned and the function sends an error to the client. */
  10. int getFlushCommandFlags(client *c, int *flags) {
  11.     /* Parse the optional ASYNC option. */
  12.     if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"sync")) {
  13.         *flags = EMPTYDB_NO_FLAGS;
  14.     } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"async")) {
  15.         *flags = EMPTYDB_ASYNC;
  16.     } else if (c->argc == 1) {
  17.         *flags = server.lazyfree_lazy_user_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS;
  18.     } else {
  19.         addReplyErrorObject(c,shared.syntaxerr);
  20.         return C_ERR;
  21.     }
  22.     return C_OK;
  23. }
复制代码
可以看到,在不指定任何修饰符的情况下(c->argc == 1),修饰符的取值由 server.lazyfree_lazy_user_flush 决定。
lazyfree-lazy-server-del

lazyfree-lazy-server-del 主要用在两个函数中:dbDelete和dbOverwrite。这两个函数的实现代码如下:
  1. /* This is a wrapper whose behavior depends on the Redis lazy free
  2.  * configuration. Deletes the key synchronously or asynchronously. */
  3. int dbDelete(redisDb *db, robj *key) {
  4.     return dbGenericDelete(db, key, server.lazyfree_lazy_server_del);
  5. }

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

  18.     dictFreeVal(db->dict, &auxentry);
  19. }
复制代码
下面我们分别看看这两个函数的使用场景。
dbDelete

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

  • 执行MIGRATE命令时,删除源端实例的 KEY。
  • RESTORE命令中,如果指定了 REPLACE 选项,当指定的 KEY 存在时,会调用 dbDelete 删除这个 KEY。
  • 通过POP、TRIM之类的命令从列表(List),集合(Set),有序集合(Sorted Set)中弹出或者移除元素时,当 KEY 为空时,会调用 dbDelete 删除这个 KEY。
  • SINTERSTORE、ZINTERSTORE等 STORE 命令中。这些命令会计算多个集合(有序集合)的交集、并集、差集,并将结果存储在一个新的 KEY 中。如果交集、并集、差集的结果为空,当用来存储的 KEY 存在时,会调用 dbDelete 删除这个 KEY。
dbOverwrite

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

  • SET 相关的命令。如 SET,SETNX,SETEX,HSET,MSET。
  • SINTERSTORE、ZINTERSTORE 等 STORE 命令中。如果交集、并集、差集的结果不为空,且用来存储的 KEY 存在,则该 KEY 的值会通过 dbOverwrite 覆盖。
lazyfree-lazy-expire

lazyfree-lazy-expire 主要用于以下四种场景:
1. 删除过期 KEY,包括主动访问时删除和 Redis 定期删除。
不仅如此,该参数还决定了删除操作传播给从库及写到 AOF 文件中是用DEL还是UNLINK。
  1. /* Delete the specified expired key and propagate expire. */
  2. void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
  3.     mstime_t expire_latency;
  4.     latencyStartMonitor(expire_latency);
  5.     // 删除过期 KEY
  6.     if (server.lazyfree_lazy_expire)
  7.         dbAsyncDelete(db,keyobj);
  8.     else
  9.         dbSyncDelete(db,keyobj);
  10.     latencyEndMonitor(expire_latency);
  11.     latencyAddSampleIfNeeded("expire-del",expire_latency);
  12.     notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",keyobj,db->id);
  13.     signalModifiedKey(NULL, db, keyobj);
  14.     // 将删除操作传播给从库及写到 AOF 文件中
  15.     propagateDeletion(db,keyobj,server.lazyfree_lazy_expire);
  16.     server.stat_expiredkeys++;
  17. }
复制代码
2. 主库启动,加载 RDB 的时候,当碰到过期 KEY 时,该参数决定了删除操作传播给从库是用DEL还是UNLINK。
[code]if (iAmMaster() &&
    !(rdbflags & RDBFLAGS_AOF_PREAMBLE) &&
    expiretime != -1 && expiretime 
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

何小豆儿在此

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表