设为首页 收藏本站
查看: 1118|回复: 0

[经验分享] redis 数据结构一 之t_string

[复制链接]

尚未签到

发表于 2015-7-21 11:57:47 | 显示全部楼层 |阅读模式
简介
   REDIS有非常丰富的数据结构 以及建立在这数据结构上的操作,在源文件中主要集中在 T_hash.c /T_list.c /T_string.c/T_zset.c
  可以说读懂了这4个源文件  大部分数据结构命令都比较清楚了。 先从T_string.c源文件开始读起:
T_string.c  SET命令
命令简介
SET key value [EX seconds] [PX milliseconds] [NX|XX]   1)设置了Key Value的时间  有2种单位: 第一: EX  对应的是秒 第2: PX  是毫秒
  命令源码分解

  • void setCommand(redisClient *c) {
  • int j;
  •     robj *expire = NULL;
  • int unit = UNIT_SECONDS;
  • int flags = 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;
  •         } else if ((a[0] == 'x' || a[0] == 'X') &&
  •                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
  •             flags |= REDIS_SET_XX;
  •         } else if ((a[0] == 'e' || a[0] == 'E') &&
  •                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
  •             unit = UNIT_SECONDS;
  •             expire = next;
  •             j++;
  •         } else if ((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);
  • }
  
  解析:line 8: a获取每个分割的命令参数的头指针  j从3开始, 如果命令如:
  set key value 这种  中间的循环就不会做了, line 3-5变量的设置就是原始值
  如果做了中间的循环:
  首先由2种可能: ex 和Px 但是在写代码的时候  他就会注意到, 后面还会不会接其他的命令
  最终就解析出来了   line33主要是防止Object Value是一个数字  看看能不能进行重新编码
  Line 34:就调用通用的SetCommand。 这里实现了所有SET命令版本的入口

  • void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
  • long long milliseconds = 0; /* initialized to avoid any harmness warning */
  • if (expire) {
  • if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
  • return;
  • if (milliseconds 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);
  
  line 4到line 11 :是计算出需要多长时间  
  line 14- 15:  lookupKeyWrite(c->db,key) != NULL) 查看是否有这个key 如果没有就写入  
  同样的:lookupKeyWrite(c->db,key) == NULL)  查看是否有这个key 如果有就写入
  line 20: 调用db.c/setKey函数,就把相应的Key和value插入到dict[]中去了
  line 21: dirty重新赋值
  line22:调用db.c/setExpire函数 把时间键值插入到这个expire字典里了。 mstime()是计算出当前时间的长整形秒数。 至此 整个SET命令完成了~!
T_string.c  GET命令  难度【*】

  • int getGenericCommand(redisClient *c) {
  •     robj *o;
  • if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
  • return REDIS_OK;
  • if (o->type != REDIS_STRING) {
  •         addReply(c,shared.wrongtypeerr);
  • return REDIS_ERR;
  •     } else {
  •         addReplyBulk(c,o);
  • return REDIS_OK;
  •     }
  • }
  
  get命名从Line 1 进入:  调用DB.C里的lookupKeyReadOrReply函数 如果不存在  直接返回OK。
  如果不是REDIS_STRING类型: 则返回一个错误。
  如果完全正确 Line 11:把结果返回给客户端
T_string.c  GETSET/INCR命令   难度【*】
      这2个命令非常的实用


  • void getsetCommand(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++;
  • }
  
  这里可以看到 Line 2:  先调用get命令  
  然后line 4:进行setKey  覆盖掉以前的old key
  这个命令可以完成那种复位计数器的功能:获得当前某个变量值 然后置空
INCR命令                                                                                                                                                   难度【**】
   对key 存储的数字加1  当然他的key必须要存储的是数字哦

  • void incrDecrCommand(redisClient *c, long long incr) {
  • long long value, 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,"increment or decrement would overflow");
  • 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);
  • }
  • void incrCommand(redisClient *c) {
  •     incrDecrCommand(c,1);
  • }
  
  line28:命令入口 然后进入Line1:
  line 5: 先找到key对应的value  先判断是不是STRING 是撤回, 然后判断能否转换成Longlong型数 如果能转换 就继续下面的工作: 如果递增 会导致数量越界  就返回。
  修改了具体的数值
  注意点: 如果o为NULL,则没有这个key  那么就会采用这个方案:dbAdd,如果含有 则改写
  最后一个命令:line 21: 需要通知下 相关的被watch的Key, 如果被watched的key里有这个key  则那个key的相应标记位需要置为脏。 但不影响这里的操作
题外话 : 引入INCR的原因
      试想 如果N客户端 都想对某个变量做自增的一个操作  如果没有INCR的话 只能是取回 在本地加一  然后在传上去  但是这样必须进行原子锁  所以是不行的  如果这样的事情交给服务器做,就可以避免这样的问题,对于客户端来讲只需要提供INCR命令,剩下的都是redis 进行流水线操作  就绝对能保持其自增效果   对于这个INCR在我们实验室的爬虫部分 关于多台主机爬取信息 怎么根据ID自增来插入表格 是一个非常好的解决方案~!
T_string.c  APPEND命令       难度【**】
      如果已经存在 就讲value加到key的末尾  如果不存在  就是简单的set key value

  • void appendCommand(redisClient *c) {
  •     size_t totlen;
  •     robj *o, *append;
  •     o = lookupKeyWrite(c->db,c->argv[1]);
  • if (o == NULL) {
  • /* Create the key */
  •         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 {
  • /* Key exists, check type */
  • if (checkType(c,o,REDIS_STRING))
  • return;
  • /* "append" is an argument, so always an sds */
  •         append = c->argv[2];
  •         totlen = stringObjectLen(o)+sdslen(append->ptr);
  • if (checkStringLength(c,totlen) != REDIS_OK)
  • return;
  • /* If the object is shared or encoded, we have to make a copy */
  • 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);
  •         }
  • /* Append the value */
  •         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  则重新分配
      line 32:sdscatlen() 将o->ptr的内容分配到append->ptr里。
T_string.c  MSET/MGET命令
简介:
    一次性进行多次插入和取操作命令   是一个原子操作
  void msetGenericCommand(redisClient *c, int nx) {

  • int j, busykeys = 0;
  • if ((c->argc % 2) == 0) {
  •         addReplyError(c,"wrong number of arguments for MSET");
  • return;
  •     }
  • /* Handle the NX flag. The MSETNX semantic is to return zero and don't
  •      * set nothing at all if at least one already key exists. */
  • 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);
  • }
  • void msetCommand(redisClient *c) {
  •     msetGenericCommand(c,0);
  • }
  
同样 入口是line 29 很简单:
      进入line msetGenericCommand之后, Line 3 先看下是不是有参数错了, 如果所有参数和是个偶数 就证明错了
      line 9-14: 如果是msetnx就是key不存在的时候能插入  如果存在不让插入  nx=1的话 就是调用msetnx命令
          注意点:这里是原子操作,要么全面插入成功 要么全部失败,也就是说:如果中间有一个key存在  msetnx直接从17返回了
      Line21-26:这里是真正的插入信息 每2个作为一组进行插入  然后就讲dirty更新
  MGET命令基本上是一样的原理  再次忽略
  t_string.c 基本上就是这些命令 ,但是奇怪的是getbit setbit的源码不在,这个bit 2进制数组需要在其他的文件中应该会出现,出现在其他源文件中放在其他文件中分析。
  下篇预告: List操作  这个是实验室项目用的最多的,所以必须要很好的分析。


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-89054-1-1.html 上篇帖子: PHP redis Api 中文文档 下篇帖子: redis的sort排序
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表