2024-09-15 19:03:21
小编相信大家都或多或少用过Redis,如果你没用过,那你是不是就得emo一会了,这么好用的东西都没接触过,小编只想说,你们公司还缺不缺人。
今晚这篇文章我们一起来了解一下,redis的set方法究竟是如何运行的,小编将带大家一起以redis的setkeyvalue方法为例单步调试一下redis(不会的偶尔也直接跳过)。
按惯例,先说调试工具
clion
redis6.0.1源码
编译环境-cygwin
cmake
接下来就要开始卷了注:setkeyvalue具体执行的方法定义在t_string.c文件中。
首先我在客户端执行了setflkeyflvalue方法。通过我们后端打的断点通过setCommand方法走到了具体的功能方法voidsetGenericCommand如下(我们从具体的业务方法开始说,之前的socket连接传送具体命令都不属于具体的功能方法,就不提了,有想了解了可以私聊小编一起过几招)。注:小编在下面代码中做了注释。
voidsetGenericCommand(client*c,intflags,robj*key,robj*val,robj*expire,intunit,robj*ok_reply,robj*abort_reply){longlongmilliseconds=0;/*initializedtoavoidanyharmnesswarning*///如果通过setex方法设置了超时时间if(expire){//获取超时时间给milliseconds赋值if(getLongLongFromObjectOrReply(c,expire,&milliseconds,NULL)!=C_OK)return;//判断时间是否小于等于0if(milliseconds<=0){addReplyErrorFormat(c,"invalidexpiretimein%s",c->cmd->name);return;}//时间单位转换if(unit==UNIT_SECONDS)milliseconds*=1000;}//校验:nx不能互斥设值,xx需要有值if((flags&OBJ_SET_NX&&lookupKeyWrite(c->db,key)!=NULL)||(flags&OBJ_SET_XX&&lookupKeyWrite(c->db,key)==NULL)){addReply(c,abort_reply?abort_reply:shared.null[c->resp]);return;}//设置keyvalue值genericSetKey(c,c->db,key,val,flags&OBJ_SET_KEEPTTL,1);server.dirty++;//设置超时时间if(expire)setExpire(c,c->db,key,mstime()+milliseconds);//对订阅了事件set的客户端进行通知notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);//如果设置了超时时间,对订阅了事件expire的客户端进行通知if(expire)notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);addReply(c,ok_reply?ok_reply:shared.ok);}接下来我们主要看genericSetKey方法
voidgenericSetKey(client*c,redisDb*db,robj*key,robj*val,intkeepttl,intsignal){//当前redis中不存在要插入的keyif(lookupKeyWrite(db,key)==NULL){//添加keyvaldbAdd(db,key,val);}else{//覆盖keyvaldbOverwrite(db,key,val);}//增加对val的引用计数incrRefCount(val);//保留设置前指定key的过期时间,6.0版本之后新增的参数if(!keepttl)removeExpire(db,key);if(signal)signalModifiedKey(c,db,key);}由于我们的执行的key是不存在的,所以这个时候走dbAdd方法,截图为证接下来我们走到dbAdd方法里看一看
voiddbAdd(redisDb*db,robj*key,robj*val){//ptr是key字符串的内存首地址指针,将其封装成一个sds对象sdscopy=sdsdup(key->ptr);//添加keyvalintretval=dictAdd(db->dict,copy,val);//通过宏验证是否添加成功,如果失败直接_exit(1)serverAssertWithInfo(NULL,key,retval==DICT_OK);//这里返回给客户端通知先不管,和本次文章的字符串类型不相关if(val->type==OBJ_LIST||val->type==OBJ_ZSET||val->type==OBJ_STREAM)signalKeyAsReady(db,key);//如果是集群部署,将命令发送给其他节点(slot)if(server.cluster_enabled)slotToKeyAdd(key->ptr);}这个dbAdd方法主要就是通过key对应的指针首地址创建一个sds对象。然后将sds对象插入到hash表中。
sds:是redis作者自己写的一个字符串数据结构(简单动态字符串),小编猜测,应该是redis整体是用c语言写的,二c语言中是没有字符串这个数据结构的,如果作者不自己封装一个sds,那就得用c语言中的char[]表示一个字符串。而且char[]用起来也很不方便,要自己记录长度,如果遇到字符串结尾还得自己添加\0,并且对于redis这种内存存储的系统,要尽量做到节省内存,就更不能直接用char数组了。hash表:redis里定义为dict结构体。其实就是一个hashtable结构。
typedefstructdict{dictEntry**table;dictType*type;unsignedlongsize;unsignedlongsizemask;unsignedlongused;void*privdata;}dict;typedefstructdictEntry{voidkey;voidval;structdictEntry*next;}dictEntry;
接下来就是往hash表中(dict)添加具体的元素```c/*往hash表中添加元素*/intdictAdd(dict*d,void*key,void*val){//这里会为要插入的key申请内存和rehash等操作,都是一些hash表基本的操作就不看了dictEntry*entry=dictAddRaw(d,key,NULL);if(!entry)returnDICT_ERR;//setvaldictSetVal(d,entry,val);returnDICT_OK;}#definedictSetVal(d,entry,_val_)do{\if((d)->type->valDup)\(entry)->v.val=(d)->type->valDup((d)->privdata,_val_);\else\(entry)->v.val=(_val_);\}while(0)redis中set方法插入一个字符串keyval的主流程大概就是这些,大家学废了吗?当然后面添加方法返回后还有一些集群化的代码,我这里是本地起的单机服务,没有走这些代码,就不介绍了。有兴趣的可以私聊小编一起卷。
问君能有几多愁,恰似满屏代码加需求
对一些新技术感兴趣的可以关注公众号:云下凤澜
原文:https://juejin.cn/post/7097999633686724616