欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

Redis源码解析3

程序员文章站 2022-04-05 21:07:45
...

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的操作进行讲解

Redis源码解析3

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

Redis源码解析3

引用计数 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; { 5 char *val; 6 int len; HashTable *ht; zend_object_value obj; } zvalue_value;

可以看到,PHP中的变量有两种引用计数

1. refcount:直接引用计数,赋值时计数增加
2. is_ref:间接引用计数,赋引用时计数增加

之所以有这两个值,是因为PHP中的变量有引用的概念,香港虚拟主机,并且涉及到几个重要原则:

1. 赋值零拷贝:变量赋值时,不会复制一份新的变量,而是直接对zval结构加引用,且引用计数加在refcount变量上
但是,自定义类对象的赋值,和普通变量不同,默认是赋引用,导致计数加在is_ref上
我认为这是PHP语言设计上很混乱的一个地方

2. 写时复制:变量改变会引发变量分离,导致复制一份新变量