从源码分析 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 中,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 参数的相关代码:
/* 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 主要用在两个函数中:dbDelete
和dbOverwrite
。这两个函数的实现代码如下:
/* 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 端的一些内部删除操作,常用于以下场景:
执行
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
。
/* 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
,PEXPIRE
, EXPIREAT
, PEXPIREAT
命令中,当设置的时间过期时(譬如 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。
另外,在通过POP
、TRIM
之类的命令从列表(List),集合(Set),有序集合(Sorted Set)中弹出或者移除元素时,对于这些元素的删除都是同步的,并不会异步删除。如果元素的值过大(最大值由 proto-max-bulk-len 决定,默认是 512MB),依然会阻塞主线程。
从源码分析 Redis 异步删除各个参数的具体作用的更多相关文章
- jQuery源码分析(九) 异步队列模块 Deferred 详解
deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...
- [Abp vNext 源码分析] - 11. 用户的自定义参数与配置
一.简要说明 文章信息: 基于的 ABP vNext 版本:1.0.0 创作日期:2019 年 10 月 23 日晚 更新日期:暂无 ABP vNext 针对用户可编辑的配置,提供了单独的 Volo. ...
- tornado框架源码分析---Application类之debug参数
先贴上Application这个类的源码. class Application(httputil.HTTPServerConnectionDelegate): """A ...
- dubbo源码分析5——SPI机制_AdaptiveExtension的原理和作用
private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().n ...
- redis源码分析之发布订阅(pub/sub)
redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...
- whatwg-fetch源码分析
fetch 是什么 XMLHttpRequest的最新替代技术 fetch优点 接口更简单.简洁,更加语义化 基于promise,更加好的流程化控制,可以不断then把参数传递,外加 async/aw ...
- 【转载】Redis 4.0 自动内存碎片整理(Active Defrag)源码分析
click原文链接原文链接:https://blog.csdn.net/zouhuajianclever/article/details/90669409阅读本文前建议先阅读此篇博客: Redis源码 ...
- 【源码】Redis exists命令bug分析
本文基于社区版Redis 4.0.8 1.复现条件 版本:社区版Redis 4.0.10以下版本 使用场景:开启读写分离的主从架构或者集群架构(master只负责写流量,slave负责读流量) 案例: ...
- Redis学习——ae事件处理源码分析
0. 前言 Redis在封装事件的处理采用了Reactor模式,添加了定时事件的处理.Redis处理事件是单进程单线程的,而经典Reator模式对事件是串行处理的.即如果有一个事件阻塞过久的话会导致整 ...
- redis源码分析之事务Transaction(上)
这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...
随机推荐
- quarkus依赖注入之六:发布和消费事件
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus依赖注入> ...
- 解码Transformer:自注意力机制与编解码器机制详述与代码实现
本文全面探讨了Transformer及其衍生模型,深入分析了自注意力机制.编码器和解码器结构,并列举了其编码实现加深理解,最后列出基于Transformer的各类模型如BERT.GPT等.文章旨在深入 ...
- #Powerbi 1分钟学会,RANK函数,多字段排名函数.
一:思维导图&数据源示例 1.1思维导图 1.2示例数据源 二:参数构成 三:案例度量值 基础度量值 总销量 = CALCULATE(SUM('数据源'[销量])) 总销售额 = CALCUL ...
- Leetcode刷题笔记——单调性
单调性 单调性是数学中使用的一种常见性质,通常用于描述函数,在高等数学中的定义常常为: 设函数f(x)在区间I上有定义,如果对于I上的任意两个数x1和x2,当x1<x2时,有f(x1)<f ...
- 震坤行根据ID取商品详情 API
item_get-根据ID取商品详情 注册开通 zhenkunhang.item_get 公共参数 名称 类型 必须 描述 key String 是 调用key(必须以GET方式拼接在URL中) se ...
- ContentPresenter使用DataTemplate
在使用自定义样式内容时,有时也需要在自定义样式中绑定一下数据模板 可以使用ContentPresenter的ContentTemplate绑定定义好的资源 DateTemplate 用法代码如下 &l ...
- 「tjoi 2018」智力竞赛
link. 这题数据应该蛮水的,直接把大于二分值的点去掉实际上应该是有问题的.然而题解区里都写的是这种做法,所以这里主要对如何处理大于二分值的点做分析. 注意这里大于二分值的点的意义是「可以走,但走了 ...
- Solution -「营业」「ABC 209F」Deforestation
Description Link. 有 \(n\) 棵树,每棵的高度为 \(a(i)\),看到一棵树对答案的贡献为 \(a(i-1)+a(i)+a(i+1)\)(未定义范围为 \(0\)),求 ...
- IntelliJ IDEA安装中文插件
1.运行IntelliJ IDEA程序2.点击左上角"File"//文件3.点击下拉的"Settings" //设置4.点击"Plugins" ...
- css中的字体样式
一.字体的样式 font-style:"normal" 正常 font-style:"italic"斜体 二.字体的粗细 font-weight:"b ...