您好,欢迎访问一九零五行业门户网

Redis源码解析3

everything is object 数据结构 在redis中,用 robj 结构表示一切数据对象,可以把它看作一种元数据(metadata) 各种不同的结构化数据,通过该对象进行封装、传递、变换、编码,而该对象本身却十分简单 其类型定义如下: 1 typedef struct redisobject { un
everything is object
数据结构在redis中,用 robj 结构表示一切数据对象,可以把它看作一种元数据(metadata)
各种不同的结构化数据,通过该对象进行封装、传递、变换、编码,而该对象本身却十分简单
其类型定义如下:
1 typedef struct redisobject {unsigned storage:unsigned encoding:unsigned lru:refcount; } 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的操作进行讲解
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 = {null,null,dictsdskeycompare,dictsdsdestructor,dictredisobjectdestructor };
   根据这个操作定义,执行完比后
   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
引用计数php中的变量通过引用计数管理对象的生存期,是个好主意。通过上面两张图,也可以初步看出,redis如何通过引用计数来管理对象的生死
在很多动态类型的语言中,也有类似的做法
比如在php中,用以下结构表示变量:
1 struct _zval_struct {zend_uint refcount;zend_uchar type;zend_uchar is_ref;};7 typedef struct _zval_struct zval;
其中,value是一个 “zvalue_value” 类型的指针
zvalue_value是一个union类型的结构,将不同类型的数据整合进了同一个结构里
1 typedef union _zvalue_value {dval; { 5char *val; 6int len;hashtable *ht; zend_object_value obj; } zvalue_value;
可以看到,php中的变量有两种引用计数
1. refcount:直接引用计数,赋值时计数增加
2. is_ref:间接引用计数,赋引用时计数增加
之所以有这两个值,是因为php中的变量有引用的概念,香港虚拟主机,并且涉及到几个重要原则:
1. 赋值零拷贝:变量赋值时,不会复制一份新的变量,而是直接对zval结构加引用,且引用计数加在refcount变量上
                    但是,自定义类对象的赋值,和普通变量不同,默认是赋引用,导致计数加在is_ref上
                    我认为这是php语言设计上很混乱的一个地方
2. 写时复制:变量改变会引发变量分离,导致复制一份新变量
其它类似信息

推荐信息