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

[经验分享] Redis源码解析3

[复制链接]

尚未签到

发表于 2015-7-21 10:43:55 | 显示全部楼层 |阅读模式
Everything is Object
  (转载请注明出处:http://www.iyunv.com/curve/archive/2012/09/18/2686900.html)

数据结构
  在Redis中,用 robj 结构表示一切数据对象,可以把它看作一种元数据(MetaData)
各种不同的结构化数据,通过该对象进行封装、传递、变换、编码,而该对象本身却十分简单
  其类型定义如下:



1 typedef struct redisObject {
2     unsigned type:4;  // 数据类型
3     unsigned storage:2;  // 数据存储位置(VM管理中用到)
4     unsigned encoding:4; // 数据编码方式
5     unsigned lru:22;   //lru时间计数
6     int refcount; // 引用计数
7 } robj;
  type字段表示数据类型,有以下几种定义:
REDIS_STRING   // 字符串
REDIS_LIST  // 链表
REDIS_SET  // 集合
REDIS_ZSET  // 有序集合
REDIS_HASH  // HASH结构(注意,此处不同于传统意义上的哈希表(如stl::hash_map),这里的hash仅有字段散列的语义)
REDIS_VMPOINTER  // VM指针(表示数据处于VM管理之下)
  
  encoding字段表示数据编码方式,有以下几种定义:
REDIS_ENCODING_RAW  // 原始编码,就是一个原始字符串
REDIS_ENCODING_INT  // INT型编码,会将数字类型的字符串编码成该格式
REDIS_ENCODING_HT  // 哈希表编码,源代码中以dict结构来管理
REDIS_ENCODING_ZIPMAP  // 精简编码的hash结构,更省内存
REDIS_ENCODING_LINKEDLIST  // 双向链表
REDIS_ENCODING_ZIPLIST  // 精简编码的链表,更省内存
REDIS_ENCODING_INTSET  // 精简编码的集合,更省内存
REDIS_ENCODING_SKIPLIST  //
  refcount字段是对象引用计数,每多一次引用,该计数加1;每减少一次引用,该计数减1,若减至0,则释放该对象内存

  

操作流程
  Redis源代码中,有大量的robj指针在函数间传递
下面,以redis处理“set”命令为例,对 robj的操作进行讲解
DSC0000.png
  
  1. 在 ProcessInlineBuffer、ProcessMultiBulkBUffer中,对命令缓存进行解析
    通常是以空格为分隔符进行分离,每一个字符块都编码为一个独立的robj对象
    对象存储在redisClient结构中的argc数组中,提供给后续函数使用
    InlineBuffer:对应于Redis单行命令
    MultiBulkBuffer:对应于多行命令
  2. ProcessCommand对命令进行解析,然后进行函数分发
    处理流程进入具体的命令处理函数
  3. setCommand是对应于“set”命令的处理函数
    该函数非常简单,主要起到一个命令接入作用
    它会对obj-val进行一次尝试性的编码转换,在本例中,会尝试将val对象转换为一个INT型的对象
    转换完成后,进入内部共享函数setGenericCommand处理流程
  4. setGenericCommand进行实际的“set”操作逻辑处理,即:
    将kv键值对,加入到该连接对应的命名空间中(即一个dict结构)
    对应于该dict结构,插入操作的具体语义由一个全局性的 dictType 进行定义:



1 dictType commandTableDictType = {
2     dictSdsCaseHash,           // hashid计算函数
3     NULL,                      // key构造函数,在本例中NULL表示直接指针赋值
4     NULL,                      // val构造函数,在本例中NULL表示直接指针赋值
5     dictSdsKeyCompare,         // key比较函数
6     dictSdsDestructor,         // key析构函数,该函数会将key作为一个原始字符串进行内存释放
7     dictRedisObjectDestructor  // value析构函数,会对obj-val做一次decrRefCount操作
8 };
  根据这个操作定义,执行完比后
   obj-key会复制一份原始字符串,将指针加入dict中(复制操作在dbAdd函数中实现)
   obj-val直接将指针加入dict中,同时将该对象的refcount加1(加引用操作在setKey函数中实现)
  5. 整个处理流程结束后,释放redisClient中的argc对象数组
    在本例中,导致的结果是:
    obj-key引用计数减1,最终值为0,导致对象删除
    obj-val引用计数减1,最终值为1,该对象继续存在于全局key的dict表中

  
  继续上一条“set”命令,针对更进一步的命令,对redis的对象编码转换作个初步了解
在下图中,主要注意 appendCommand中,由于该命令需要改变 obj-val对象的值,从而导致obj-val从INT编码状态解码到RAW编码状态。
  解码时生成了临时对象 obj-decoded
临时对象与obj-append合并,将合并后的值赋给obj-val
  
DSC0001.png
  
  

  

引用计数

php中的变量
  通过引用计数管理对象的生存期,是个好主意。通过上面两张图,也可以初步看出,redis如何通过引用计数来管理对象的生死
在很多动态类型的语言中,也有类似的做法
比如在PHP中,用以下结构表示变量:



1 struct _zval_struct {
2     zvalue_value value;     // 变量的值
3     zend_uint refcount;     // 直接引用计数
4     zend_uchar type;        // 变量类型
5     zend_uchar is_ref;      // 间接引用计数
6 };
7 typedef struct _zval_struct zval;
  其中,value是一个 “zvalue_value” 类型的指针
zvalue_value是一个union类型的结构,将不同类型的数据整合进了同一个结构里



1 typedef union _zvalue_value {
2     long lval;        // long value
3     double dval;    // double value
4     struct {
5         char *val;
6         int len;
7     } str;          // string value
8     HashTable *ht;    // hash table(array) value
9     zend_object_value obj;  // resource handle
10 } zvalue_value;
  
  可以看到,PHP中的变量有两种引用计数
  1. refcount:直接引用计数,赋值时计数增加
2. is_ref:间接引用计数,赋引用时计数增加
  之所以有这两个值,是因为PHP中的变量有引用的概念,并且涉及到几个重要原则:
  1. 赋值零拷贝:变量赋值时,不会复制一份新的变量,而是直接对zval结构加引用,且引用计数加在refcount变量上
                    但是,自定义类对象的赋值,和普通变量不同,默认是赋引用,导致计数加在is_ref上
                    我认为这是PHP语言设计上很混乱的一个地方
  2. 写时复制:变量改变会引发变量分离,导致复制一份新变量
  3. 写时改变:变量如果加了is_ref引用,那么变量改变时,不会引发分离,而是直接改变zval的值,使数个引用的变量可以共享改变
                  注意,第2点和第3点是冲突的

  在redis中,robj的设计在思想上同PHP的变量设计是相同的
  不过robj没有赋引用的概念,所以设计上应该更简单一些
  不过,robj上有encode的概念,这在PHP里是没有的,这会导致更多的编码转换
  编码转换的相关话题,会在后续几篇文章中详细解释


共享对象
  redis中,为了操作方便,同时为了节省内存,定义了一组全局的共享对象“shared”



1 struct sharedObjectsStruct {
2     robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space,
3     *colon, *nullbulk, *nullmultibulk, *queued,
4     *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
5     *outofrangeerr, *loadingerr, *plus,
6     *select0, *select1, *select2, *select3, *select4,
7     *select5, *select6, *select7, *select8, *select9,
8     *messagebulk, *pmessagebulk, *subscribebulk, *unsubscribebulk, *mbulk3,
9     *mbulk4, *psubscribebulk, *punsubscribebulk,
10     *integers[REDIS_SHARED_INTEGERS];
11 };
12
13 extern struct sharedObjectsStruct shared;
  如上定义,比如其中的 crlf 代表一个“\r\n”字符串对象,ok代表一个"ok"字符串对象
值得注意的是其中有一个 integers的变量,定义为10000大小的数组
  这个变量是干什么的呢?
  其实,这应该算是redis里十分精巧的一个设计
因为Redis设计者认为,数字1~10000是绝大部分系统中,都会频繁使用到的数字
所以,系统预定义了这10000个INT对象,以达到节省内存的目的
以后凡是用户输入中包含有10000以内的整数
都不用重新生成新的对象了,而是直接保存一个指针,并将对应共享对象的引用计数加1,就行了。
  
  
  

运维网声明 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-88993-1-1.html 上篇帖子: PHP5.5安装php-redis扩展 下篇帖子: 缓存篇~第八回 Redis实现基于方法签名的数据集缓存~续(优化缓存中的key)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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