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的末尾如果不存在就是简单的setkeyvaluevoidappendCommand(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操作这个是实验室项目用的最多的,所以必须要很好的分析。
相关文章推荐
- Redis-数据结构(字符串String)
- redis 3.2 新数据结构:quicklist、String的embstr与raw编码方式分界点
- redis 数据结构之和对象---简单动态字符串SDS(simple dynamic string)
- 使用stringRedisTemplate操作redis hash结构数据只能存储String类型的问题
- [置顶] Redis String类型数据常用的16条命令总结
- Redis学习手册(String数据类型)
- redis 源代码之数据结构(sds,链表的实现)
- redis 源代码之数据结构(3)--hash表实现
- Redis源码解析4 - 数据类型之 String & List
- redis(三)主要数据结构
- Redis String类型数据常用的16条命令总结
- Redis学习手册(String数据类型)
- 深入理解Redis:底层数据结构
- Redis学习手册(String数据类型)
- NoSQL之Redis对string数据类型的操作
- redis--string数据类型
- redis 源代码之数据结构(2)--sds实现
- Redis学习手册(String数据类型)
- redis 源代码之数据结构(5)--ziplist实现
- Redis数据类型之string