当前位置: 首页 > 后端技术 > PHP

【Redis5源码学习】redis命令之del文章解析

时间:2023-03-29 16:37:59 PHP

baiyan命令语法命令含义:删除某个键对应的值命令格式:DEL[key1key2...]命令实战:127.0.0.1:6379>delkey1(integer)1返回值:删除key的个数源码分析首先我们打开一个redis客户端,使用gdb-predis-server的端口。由于del命令对应的处理函数是delCommand(),所以在delCommand处设置断点,然后在redis客户端执行如下命令:127.0.0.1:6379>setkey1value1EX100OK127.0.0.1:6379>getkey1"value1"127.0.0.1:6379>delkey1先设置一个键值对为key1-value1,过期时间为100秒,然后在100秒内删除,执行delkey1,,删除还没有的key还过期了。看看redis服务器是如何执行的:Breakpoint1,delCommand(c=0x7fb46230dec0)atdb.c:487487delGenericCommand(c,0);(gdb)sdelGenericCommand(c=0x7fb46230dec0,lazy=0)atdb.c:468468voiddelGenericCommand(client*c,intlazy){(gdb)n471for(j=1;jargc;j++){(gdb)pc->argc$1=2(gdb)pc->argv[0]$2=(robj*)0x7fb462229800(gdb)p*$2$3={type=0,encoding=8,lru=8575647,refcount=1,ptr=0x7fb462229813}我们看到delCommand()直接调用了delGenericCommand(c,0)好像是一个封装好的通用删除函数,我们进去看看内部执行过程。voiddelGenericCommand(client*c,intlazy){intnumdel=0,j;对于(j=1;jargc;j++){expireIfNeeded(c->db,c->argv[j]);//检查这个key是否过期,过期还需要做一些其他的操作intdeleted=lazy?dbAsyncDelete(c->db,c->argv[j]):dbSyncDelete(c->db,c->argv[j]);//异步或同步删除if(deleted){signalModifiedKey(c->db,c->argv[j]);//事务相关notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[j],c->db->id);//发布/订阅相关通知server.dirty++;//AOF持久化相关numdel++;//删除key的个数++,返回的个数就是这个值}}addReplyLongLong(c,numdel);}首先直接执行一个for循环,循环个数为c->argc,我们打印出2.看在变量名处,我们可以猜测可能是del和key1这两个命令参数。我们打印c->argv[0],发现它是一个redis-object,然后我们看到它的encoding对应的类型是8,也就是embstr类型,优化版的字符串存储。要查看其内容,请将指针转换为char*:(gdb)p*((char*)($3.ptr))$4=100'd'(gdb)p*((char*)($3.ptr)ptr+1))$6=101'e'(gdb)p*((char*)($3.ptr+2))$7=108'l'(gdb)p*((char*)($3.ptr+3))然后打印下一个参数:(gdb)p*c->argv[1]$10={type=0,encoding=8,lru=8575647,refcount=1,ptr=0x7fb4622297e3}(gdb)p*(char*)($10.ptr)$11=107'k'(gdb)p*((char*)($10.ptr+1))$12=101'e'(gdb)p*((char*)($10.ptr+2))$13=121'y'(gdb)p*((char*)($10.ptr+3))$14=49'1'我们可以看到del存储在c->argv数组中,key1的值。但是下面的for循环是从1开始的,不包含命令名,所以只针对参数key1循环一次。然后,调用expireIfNeeded()函数:472expireIfNeeded(c->db,c->argv[j]);(gdb)sexpireIfNeeded(db=0x7fb46221a800,key=0x7fb4622297d0)atdb.c:11671167intexpireIfNeeded(redisDb*db,robj*key){(gdb)n1168if(!keyIsExpired(db,key))return0;(gdb)1187}(gdb)我们发现它判断key是否过期,如果没有则直接返回0。所以看起来对于删除命令,过期删除和未过期删除是有区别的。这里先跳过,继续往下:473intdeleted=lazy?dbAsyncDelete(c->db,c->argv[j]):dbSyncDelete(c->db,c->argv[j]);这里有一个lazy参数,lazy参数决定后面是调用dbAsyncDelete()还是dbSyncDelete()函数,我们打印这个值:(gdb)plazy$15=0lazy的值为0,从字面意思看,它似乎不需要延迟删除。非惰性删除对应dbSyncDelete()函数,即同步删除。这里我们可以知道,非惰性删除对应的是同步删除。我们遵循dbSyncDelete(c->db,c->argv[j]):intdbSyncDelete(redisDb*db,robj*key){if(dictSize(db->e??xpires)>0)dictDelete(db->e??xpires,key->指针);//删除key1对应的过期时间字典条目if(dictDelete(db->dict,key->ptr)==DICT_OK){//删除字典中的key1-value1键值对if(server.cluster_enabled)slotToKeyDel(键);返回1;}否则{返回0;在redis中,过期时间和数据保存在redis数据库下的两个字典中。字典的结构已经讨论过了。在过期时间字典中,key就是我们的keykey1;键空间字典将存储真正的键值对。于是去字典里分别删除数据的过期时间和值。先不去深究具体的删除逻辑,只知道字典dict中保存的是过期时间和键值对:typedefstructredisDb{...dict*dict;//键空间字典,保存数据库中的所有键值对dict*expires//过期时间字典,保存键的过期时间...}redisDb;继续往下,删除成功后,被删除变量的值为1,会执行下面的if,会调用两个函数来做一些事务和发布/订阅操作,这两个我们就不解释了部分深入,然后我们将数字++删除,然后将结果保存到输出缓冲区,命令执行结束。在我们的例子中,在扩展惰性删除之前,我们调用了dbSyncDelete()方法,也就是同步删除。但是如果lazy的值为true,即启用了懒删除策略,则会调用dbAsyncDelete()方法:#defineLAZYFREE_THRESHOLD64intdbAsyncDelete(redisDb*db,robj*key){//删除过期key来自过期键字典Timeif(dictSize(db->e??xpires)>0)dictDelete(db->e??xpires,key->ptr);//在键空间字典中查找键的位置指针dictEntry*de=dictUnlink(db->dict,key->ptr);if(de){//通过键指针获取值robj*val=dictGetVal(de);//计算判断是否需要懒删除。如果只删除一个很小的key,可以不用采用惰性删除策略,同步删除即可。size_tfree_effort=lazyfreeGetFreeEffort(val);//当cost超过阈值64时,懒删除任务会分发给后台线程去做,不会阻塞主进程if(free_effort>LAZYFREE_THRESHOLD&&val->refcount==1){atomicIncr(lazyfree_objects,1);//延迟删除的对象数++bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);//发送延迟删除的任务dictSetVal(db->dict,de,NULL);}}if(de){dictFreeUnlinkedEntry(db->dict,de);如果(server.cluster_enabled)slotToKeyDel(键);返回1;}否则{返回0;}}我们可以看到在懒删除之前需要计算当前的key-value是否适合懒删除。当惰性删除超过阈值64时,将启用惰性删除策略。因此,我们知道它使用异步线程对删除的键值对进行延迟内存回收。那么为什么要这样做呢?原因是如果要删除的键值对占用的内存空间非常大,一次执行同步删除会花费很长时间,会导致主进程全部处于阻塞状态时间和无法对外提供服务。因此,为了解决这个问题,redis在4.0版本提出了惰性删除的概念,通过异步线程解决了删除大key时的阻塞问题。异步线程在Redis内部有个专门的名字,就是BIO,全称BackgroundIO,意思是在幕后默默工作的IO线程,是懒删除的载体和核心。参考【Redis源码解析】Redis懒删除简史(lazyfree)