您的位置:首页 > 理论基础 > 数据结构算法

redis 数据结构一 之t_string

2014-08-30 16:33 639 查看

简介

REDIS有非常丰富的数据结构以及建立在这数据结构上的操作,在源文件中主要集中在T_hash.c/T_list.c/T_string.c/T_zset.c

可以说读懂了这4个源文件大部分数据结构命令都比较清楚了。先从T_string.c源文件开始读起:

T_string.cSET命令

命令简介

SETkeyvalue[EXseconds][PXmilliseconds][NX|XX]1)设置了KeyValue的时间有2种单位:第一:EX对应的是秒第2:PX是毫秒

命令源码分解

voidsetCommand(redisClient*c){

intj;

robj*expire=NULL;

intunit=UNIT_SECONDS;

intflags=REDIS_SET_NO_FLAGS;

for(j=3;j<c->argc;j++){

char*a=c->argv[j]->ptr;

robj*next=(j==c->argc-1)?NULL:c->argv[j+1];

if((a[0]=='n'||a[0]=='N')&&

(a[1]=='x'||a[1]=='X')&&a[2]=='\0'){

flags|=REDIS_SET_NX;

}elseif((a[0]=='x'||a[0]=='X')&&

(a[1]=='x'||a[1]=='X')&&a[2]=='\0'){

flags|=REDIS_SET_XX;

}elseif((a[0]=='e'||a[0]=='E')&&

(a[1]=='x'||a[1]=='X')&&a[2]=='\0'&&next){

unit=UNIT_SECONDS;

expire=next;

j++;

}elseif((a[0]=='p'||a[0]=='P')&&

(a[1]=='x'||a[1]=='X')&&a[2]=='\0'&&next){

unit=UNIT_MILLISECONDS;

expire=next;

j++;

}else{

addReply(c,shared.syntaxerr);

return;

}

}

c->argv[2]=tryObjectEncoding(c->argv[2]);

setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);

}

解析:line8:a获取每个分割的命令参数的头指针j从3开始,如果命令如:

setkeyvalue这种中间的循环就不会做了,line3-5变量的设置就是原始值

如果做了中间的循环:

首先由2种可能:ex和Px但是在写代码的时候他就会注意到,后面还会不会接其他的命令

最终就解析出来了line33主要是防止ObjectValue是一个数字看看能不能进行重新编码

Line34:就调用通用的SetCommand。这里实现了所有SET命令版本的入口

voidsetGenericCommand(redisClient*c,intflags,robj*key,robj*val,robj*expire,intunit,robj*ok_reply,robj*abort_reply){

longlongmilliseconds=0;/*initializedtoavoidanyharmnesswarning*/

if(expire){

if(getLongLongFromObjectOrReply(c,expire,&milliseconds,NULL)!=REDIS_OK)

return;

if(milliseconds<=0){

addReplyError(c,"invalidexpiretimeinSETEX");

return;

}

if(unit==UNIT_SECONDS)milliseconds*=1000;

}

if((flags&REDIS_SET_NX&&lookupKeyWrite(c->db,key)!=NULL)||

(flags&REDIS_SET_XX&&lookupKeyWrite(c->db,key)==NULL))

{

addReply(c,abort_reply?abort_reply:shared.nullbulk);

return;

}

setKey(c->db,key,val);

server.dirty++;

if(expire)setExpire(c->db,key,mstime()+milliseconds);

addReply(c,ok_reply?ok_reply:shared.ok);

line4到line11:是计算出需要多长时间

line14-15:lookupKeyWrite(c->db,key)!=NULL)查看是否有这个key如果没有就写入

同样的:lookupKeyWrite(c->db,key)==NULL)查看是否有这个key如果有就写入

line20:调用db.c/setKey函数,就把相应的Key和value插入到dict[]中去了

line21:dirty重新赋值

line22:调用db.c/setExpire函数把时间键值插入到这个expire字典里了。mstime()是计算出当前时间的长整形秒数。至此整个SET命令完成了~!

T_string.cGET命令难度【*】

intgetGenericCommand(redisClient*c){

robj*o;

if((o=lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk))==NULL)

returnREDIS_OK;

if(o->type!=REDIS_STRING){

addReply(c,shared.wrongtypeerr);

returnREDIS_ERR;

}else{

addReplyBulk(c,o);

returnREDIS_OK;

}

}

get命名从Line1进入:调用DB.C里的lookupKeyReadOrReply函数如果不存在直接返回OK。

如果不是REDIS_STRING类型:则返回一个错误。

如果完全正确Line11:把结果返回给客户端

T_string.cGETSET/INCR命令难度【*】

这2个命令非常的实用


voidgetsetCommand(redisClient*c){

if(getGenericCommand(c)==REDIS_ERR)return;

c->argv[2]=tryObjectEncoding(c->argv[2]);

setKey(c->db,c->argv[1],c->argv[2]);

server.dirty++;

}

这里可以看到Line2:先调用get命令

然后line4:进行setKey覆盖掉以前的oldkey

这个命令可以完成那种复位计数器的功能:获得当前某个变量值然后置空

INCR命令难度【**】

对key存储的数字加1当然他的key必须要存储的是数字哦

voidincrDecrCommand(redisClient*c,longlongincr){

longlongvalue,oldvalue;

robj*o,*new;

o=lookupKeyWrite(c->db,c->argv[1]);

if(o!=NULL&&checkType(c,o,REDIS_STRING))return;

if(getLongLongFromObjectOrReply(c,o,&value,NULL)!=REDIS_OK)return;

oldvalue=value;

if((incr<0&&oldvalue<0&&incr<(LLONG_MIN-oldvalue))||

(incr>0&&oldvalue>0&&incr>(LLONG_MAX-oldvalue))){

addReplyError(c,"incrementordecrementwouldoverflow");

return;

}

value+=incr;

new=createStringObjectFromLongLong(value);

if(o)

dbOverwrite(c->db,c->argv[1],new);

else

dbAdd(c->db,c->argv[1],new);

signalModifiedKey(c->db,c->argv[1]);

server.dirty++;

addReply(c,shared.colon);

addReply(c,new);

addReply(c,shared.crlf);

}

voidincrCommand(redisClient*c){

incrDecrCommand(c,1);

}

line28:命令入口然后进入Line1:

line5:先找到key对应的value先判断是不是STRING是撤回,然后判断能否转换成Longlong型数如果能转换就继续下面的工作:如果递增会导致数量越界就返回。

修改了具体的数值

注意点:如果o为NULL,则没有这个key那么就会采用这个方案:dbAdd,如果含有则改写

最后一个命令:line21:需要通知下相关的被watch的Key,如果被watched的key里有这个key则那个key的相应标记位需要置为脏。但不影响这里的操作

题外话:引入INCR的原因

试想如果N客户端都想对某个变量做自增的一个操作如果没有INCR的话只能是取回在本地加一然后在传上去但是这样必须进行原子锁所以是不行的如果这样的事情交给服务器做,就可以避免这样的问题,对于客户端来讲只需要提供INCR命令,剩下的都是redis进行流水线操作就绝对能保持其自增效果对于这个INCR在我们实验室的爬虫部分关于多台主机爬取信息怎么根据ID自增来插入表格是一个非常好的解决方案~!

T_string.cAPPEND命令难度【**】

如果已经存在就讲value加到key的末尾如果不存在就是简单的setkeyvalue

voidappendCommand(redisClient*c){

size_ttotlen;

robj*o,*append;

o=lookupKeyWrite(c->db,c->argv[1]);

if(o==NULL){

/*Createthekey*/

c->argv[2]=tryObjectEncoding(c->argv[2]);

dbAdd(c->db,c->argv[1],c->argv[2]);

incrRefCount(c->argv[2]);

totlen=stringObjectLen(c->argv[2]);

}else{

/*Keyexists,checktype*/

if(checkType(c,o,REDIS_STRING))

return;

/*"append"isanargument,soalwaysansds*/

append=c->argv[2];

totlen=stringObjectLen(o)+sdslen(append->ptr);

if(checkStringLength(c,totlen)!=REDIS_OK)

return;

/*Iftheobjectissharedorencoded,wehavetomakeacopy*/

if(o->refcount!=1||o->encoding!=REDIS_ENCODING_RAW){

robj*decoded=getDecodedObject(o);

o=createStringObject(decoded->ptr,sdslen(decoded->ptr));

decrRefCount(decoded);

dbOverwrite(c->db,c->argv[1],o);

}

/*Appendthevalue*/

o->ptr=sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));

totlen=sdslen(o->ptr);

}

signalModifiedKey(c->db,c->argv[1]);

server.dirty++;

addReplyLongLong(c,totlen);

}

分2种:如果没有含key就直接加入

如果没有含key则重新分配

line32:sdscatlen()将o->ptr的内容分配到append->ptr里。

T_string.cMSET/MGET命令

简介:

一次性进行多次插入和取操作命令是一个原子操作

voidmsetGenericCommand(redisClient*c,intnx){

intj,busykeys=0;

if((c->argc%2)==0){

addReplyError(c,"wrongnumberofargumentsforMSET");

return;

}

/*HandletheNXflag.TheMSETNXsemanticistoreturnzeroanddon't

*setnothingatallifatleastonealreadykeyexists.*/

if(nx){

for(j=1;j<c->argc;j+=2){

if(lookupKeyWrite(c->db,c->argv[j])!=NULL){

busykeys++;

}

}

if(busykeys){

addReply(c,shared.czero);

return;

}

}

for(j=1;j<c->argc;j+=2){

c->argv[j+1]=tryObjectEncoding(c->argv[j+1]);

setKey(c->db,c->argv[j],c->argv[j+1]);

}

server.dirty+=(c->argc-1)/2;

addReply(c,nx?shared.cone:shared.ok);

}

voidmsetCommand(redisClient*c){

msetGenericCommand(c,0);

}

同样入口是line29很简单:

进入linemsetGenericCommand之后,Line3先看下是不是有参数错了,如果所有参数和是个偶数就证明错了

line9-14:如果是msetnx就是key不存在的时候能插入如果存在不让插入nx=1的话就是调用msetnx命令

注意点:这里是原子操作,要么全面插入成功要么全部失败,也就是说:如果中间有一个key存在msetnx直接从17返回了

Line21-26:这里是真正的插入信息每2个作为一组进行插入然后就讲dirty更新

MGET命令基本上是一样的原理再次忽略

t_string.c基本上就是这些命令,但是奇怪的是getbitsetbit的源码不在,这个bit2进制数组需要在其他的文件中应该会出现,出现在其他源文件中放在其他文件中分析。

下篇预告:List操作这个是实验室项目用的最多的,所以必须要很好的分析。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: