以前对异步删除几个参数的作用比较模糊,包括网上的很多资料都是一笔带过,语焉不详。
所以这次从源码(基于 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命令调用的函数
voiddelCommand(client*c){
delGenericCommand(c,server.lazyfree_lazy_user_del);
}
//UNLINK命令调用的函数
voidunlinkCommand(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,则还是主线程同步删除。
FLUSHALLASYNC
FLUSHDBASYNC
在 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 参数的相关代码:
/*ReturnthesetofflagstousefortheemptyDb()callforFLUSHALL
*andFLUSHDBcommands.
*
*sync:flushesthedatabaseinansyncmanner.
*async:flushesthedatabaseinanasyncmanner.
*nooption:determinesyncorasyncaccordingtothevalueoflazyfree-lazy-user-flush.
*
*OnsuccessC_OKisreturnedandtheflagsarestoredin*flags,otherwise
*C_ERRisreturnedandthefunctionsendsanerrortotheclient.*/
intgetFlushCommandFlags(client*c,int*flags){
/*ParsetheoptionalASYNCoption.*/
if(c->argc==2&&!strcasecmp(c->argv[1]->ptr,"sync")){
*flags=EMPTYDB_NO_FLAGS;
}elseif(c->argc==2&&!strcasecmp(c->argv[1]->ptr,"async")){
*flags=EMPTYDB_ASYNC;
}elseif(c->argc==1){
*flags=server.lazyfree_lazy_user_flush?EMPTYDB_ASYNC:EMPTYDB_NO_FLAGS;
}else{
addReplyErrorObject(c,shared.syntaxerr);
returnC_ERR;
}
returnC_OK;
}
可以看到,在不指定任何修饰符的情况下(c->argc == 1),修饰符的取值由 server.lazyfree_lazy_user_flush 决定。
lazyfree-lazy-server-del
lazyfree-lazy-server-del 主要用在两个函数中:dbDelete
和dbOverwrite
。这两个函数的实现代码如下:
/*ThisisawrapperwhosebehaviordependsontheRedislazyfree
*configuration.Deletesthekeysynchronouslyorasynchronously.*/
intdbDelete(redisDb*db,robj*key){
returndbGenericDelete(db,key,server.lazyfree_lazy_server_del);
}
/*Overwriteanexistingkeywithanewvalue.Incrementingthereference
*countofthenewvalueisuptothecaller.
*Thisfunctiondoesnotmodifytheexpiretimeoftheexistingkey.
*
*Theprogramisabortedifthekeywasnotalreadypresent.*/
voiddbOverwrite(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
。
/*Deletethespecifiedexpiredkeyandpropagateexpire.*/
voiddeleteExpiredKeyAndPropagate(redisDb*db,robj*keyobj){
mstime_texpire_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{
if(rdbflags&RDBFLAGS_FEED_REPL){
/*Callershouldhavecreatedreplicationbacklog,
*andnowthispathonlyworkswhenrebooting,
*sowedon'thavereplicasyet.*/
serverAssert(server.repl_backlog!=NULL&&listLength(server.slaves)==0);
robjkeyobj;
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
。
/*Finallyremovetheselectedkey.*/
if(bestkey){
db=server.db+bestdbid;
robj*keyobj=createStringObject(bestkey,sdslen(bestkey));
delta=(longlong)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-=(longlong)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 决定了数据清除的方式。
/*Asynchronouslyread服务器托管网theSYNCpayloadwere服务器托管网ceivefromamaster*/
#defineREPL_MAX_WRITTEN_BEFORE_FSYNC(1024*1024*8)/*8MB*/
voidreadSyncBulkPayload(connection*conn){
charbuf[PROTO_IOBUF_LEN];
ssize_tnread,readlen,nwritten;
intuse_diskless_load=useDisklessLoad();
redisDb*diskless_load_tempDb=NULL;
functionsLibCtx*temp_functions_lib_ctx=NULL;
intempty_db_flags=server.repl_slave_lazy_flush?EMPTYDB_ASYNC:
EMPTYDB_NO_FLAGS;
...
if(use_diskless_load&&server.repl_diskless_load==REPL_DISKLESS_LOAD_SWAPDB){
/*InitializeemptytempDbdictionaries.*/
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,"MASTERREPLICAsync:Flushingolddata");
emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
}
...
}
总结
综合上面的分析,异步删除各参数的作用如下,
注意,这几个参数的默认值都是 no。
另外,在通过POP
、TRIM
之类的命令从列表(List),集合(Set),有序集合(Sorted Set)中弹出或者移除元素时,对于这些元素的删除都是同步的,并不会异步删除。如果元素的值过大(最大值由 proto-max-bulk-len 决定,默认是 512MB),依然会阻塞主线程。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 【聚类】DBCAN聚类原理DBSCAN的优缺点及应用场景:python实现
OPTICS是基于DBSCAN改进的一种密度聚类算法,对参数不敏感。当需要用到基于密度的聚类算法时,可以作为DBSCAN的一种替代的优化方案,以实现更优的效果。 原理 基于密度的聚类算法(1)——DBSCAN详解_dbscan聚类_root-cause的博客-…