结构定义

在redis中,对象的数据结构定义如下:

​typedef struct redisObject {
​unsigned type:4;
​unsgined encoding:4;
​unsigned lru:LRU_BITS;
​int refcount;
​void *ptr;
​}

结构定义中的type:4encoding:4这种定义方式称为位段类型

使用位段类型的好处就是避免浪费内存,如果使用unsigned int type定义type字段,需要4个字节,而使用unsigned type:4,只需要4个位段就足够了。

参数说明

redis对象有许多特性,比如:类型检查(通过type实现)、命令多态(encoding实现)、内存共享(通过refcount实现)等等,这些特性都是通过redisObject中的参数实现的。

type

对象类型,它的取值范围有五个,分别是redis使用的五种对象类型:

  • #define OBJ_STRING 0
  • #define OBJ_LIST 1
  • ​#define OBJ_SET 2
  • #define OBJ_ZSET 3
  • #define OBJ_HASH 4

在执行命令前对type字段进行检查,可判断出对象是否是命令允许执行的对象类型。

encoding

对象使用的编码类型,它的取值范围有下面这些:

  • #define OBJ_ENCODING_RAW 0 /* 动态字符串 */
  • #define OBJ_ENCODING_INT 1 /* 整数 */
  • #define OBJ_ENCODING_HT 2 /* 哈希表 */
  • #define OBJ_ENCODING_ZIPMAP 3
  • #define OBJ_ENCODING_LINKEDLIST 4 /* 旧的列表编码,现在不再使用了 */
  • #define OBJ_ENCODING_ZIPLIST 5 /* 压缩表 */
  • #define OBJ_ENCODING_INTSET 6 /* 整数集合 */
  • #define OBJ_ENCODING_SKIPLIST 7 /* 跳跃表 */
  • #define OBJ_ENCODING_EMBSTR 8 /* 用于保存短字符串的编码类型 */
  • #define OBJ_ENCODING_QUICKLIST 9 /* 压缩链表和双向链表组成的快速列表 */

在调用命令的时候,redis还会根据对象使用的编码类型来选择正确的底层对象,执行对应函数的实现代码。

lru

最近最后一次被命令访问的时间 或者 最近最少使用的数据。

在执行OBJECT IDLETIME命令时,通过当前时间减去lru属性的值,得到键的空转时长。另外,如果服务器打开了maxmemory选项,且使用的内存回收算法是volatile-lur或者allkeys-lru,那么当服务器占用的内存超过了maxmemory的上限值,空转时长较高的键会优先被服务器释放,从而回收内存。

refcount

对象的引用计数。

redis的对象共享和内存回收特性就是通过refcount属性来实现,通过将refcount + 1实现对象共享;进行内存回收检查时,检查refcount == 0的对象,将对象进行回收。

ptr

指向底层数据结构用于保存数据的指针。

对象使用的数据结构

redis有五种对象,不同对象可能用到的数据结构如下图所示:

编码转换与命令多态

同一种对象使用不同的数据结构是通过encoding来实现,而且,同一个命令的实现方法会根据对象的编码属性而变化,这是命令的多态实现。

以哈希对象为例看看编码转换以及命令多态等特性是怎么实现的。

哈希对象

哈希对象使用的编码有:ziplist、hashtable。

如果使用压缩表作为底层实现,每当有新的键值对需要加入哈希对象,会先添加键节点到链表,然后添加值节点。

使用hashtable作为底层实现,每一个新的键值对都会使用字典键值对来保存,键和值分别是字符串对象。

使用不同结构保存后的结构图如下所示:

ziplist编码

hashtable编码

编码转换

每一种对象在使用编码的时候都有一定的条件,使用ziplist编码的哈希对象都应该满足两个条件:

  • 1、所有键值对的键和值字符串对象长度小于64字节
  • 2、哈希对象保存的键值对数量小于512个

如果不能满足上述条件时,redis会进行对哈希对象底层数据结构进行从压缩表到字典的转换,实现步骤是遍历压缩表,获取压缩表中的键和值,使用得到的键和值创建一个字典对象,然后添加字典里,具体代码如下:

hashTypeIterator *hi;
dict *dict;
int ret; // 创建遍历器对象和哈希表
hi = hashTypeInitIterator(o);
dict = dictCreate(&hashDictType, NULL); while (hashTypeNext(hi) != C_ERR) {
sds key, value; // 用获取ziplis中的key、value新增键值对到哈希表
key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
ret = dictAdd(dict, key, value);
if (ret != DICT_OK) {
serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
o->ptr,ziplistBlobLen(o->ptr));
serverPanic("Ziplist corruption detected");
}
}
hashTypeReleaseIterator(hi);
zfree(o->ptr);
o->encoding = OBJ_ENCODING_HT;
o->ptr = dict;

命令多态

命令多态是检查对象的编码,然后执行不同的实现方式。比如哈希对象中的hget命令。

hget命令实现代码:

void hgetCommand(client *c) {
robj *o; // key不存在,返回空
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,OBJ_HASH)) return; addHashFieldToReply(c, o, c->argv[2]->ptr);
}

hget命令的实现最终是调用addHashFieldToReply函数(代码如下),该函数是通过判断哈希对象的编码来决定使用什么函数来获取哈希对象具体field的值,其他命令的实现也是大同小异。

static void addHashFieldToReply(client *c, robj *o, sds field) {
int ret; if (o == NULL) {
addReply(c, shared.nullbulk);
return;
} // 根据底层不同编码获取field的值
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *vstr = NULL;
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX; ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
if (ret < 0) {
addReply(c, shared.nullbulk);
} else {
if (vstr) {
addReplyBulkCBuffer(c, vstr, vlen);
} else {
addReplyBulkLongLong(c, vll);
}
} } else if (o->encoding == OBJ_ENCODING_HT) {
sds value = hashTypeGetFromHashTable(o, field);
if (value == NULL)
addReply(c, shared.nullbulk);
else
addReplyBulkCBuffer(c, value, sdslen(value));
} else {
serverPanic("Unknown hash encoding");
}
}

总结

redis中的很多操作都是基于上面介绍的redis对象,了解这些对象的底层实现,可以为之后更多的redis特性做准备。

【redis源码阅读】redis对象的更多相关文章

  1. Redis源码阅读(一)事件机制

    Redis源码阅读(一)事件机制 Redis作为一款NoSQL非关系内存数据库,具有很高的读写性能,且原生支持的数据类型丰富,被广泛的作为缓存.分布式数据库.消息队列等应用.此外Redis还有许多高可 ...

  2. Redis源码阅读(二)高可用设计——复制

    Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...

  3. Redis源码阅读(六)集群-故障迁移(下)

    Redis源码阅读(六)集群-故障迁移(下) 最近私人的事情比较多,没有抽出时间来整理博客.书接上文,上一篇里总结了Redis故障迁移的几个关键点,以及Redis中故障检测的实现.本篇主要介绍集群检测 ...

  4. Redis源码阅读(五)集群-故障迁移(上)

    Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...

  5. Redis源码阅读(四)集群-请求分配

    Redis源码阅读(四)集群-请求分配 集群搭建好之后,用户发送的命令请求可以被分配到不同的节点去处理.那Redis对命令请求分配的依据是什么?如果节点数量有变动,命令又是如何重新分配的,重分配的过程 ...

  6. Redis源码阅读(三)集群-连接初始化

    Redis源码阅读(三)集群-连接建立 对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供 ...

  7. Redis源码阅读-Adlist双向链表

    Redis源码阅读-链表部分- 链表数据结构在Adlist.h   Adlist.c Redis的链表是双向链表,内部定义了一个迭代器. 双向链表的函数主要是链表创建.删除.节点插入.头插入.尾插入. ...

  8. Redis源码阅读笔记(1)——简单动态字符串sds实现原理

    首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里 ...

  9. [Redis源码阅读]sds字符串实现

    初衷 从开始工作就开始使用Redis,也有一段时间了,但都只是停留在使用阶段,没有往更深的角度探索,每次想读源码都止步在阅读书籍上,因为看完书很快又忘了,这次逼自己先读代码.因为个人觉得写作需要阅读文 ...

  10. Redis源码阅读一:简单动态字符串SDS

    源码阅读基于Redis4.0.9 SDS介绍 redis 127.0.0.1:6379> SET dbname redis OK redis 127.0.0.1:6379> GET dbn ...

随机推荐

  1. 总结JS中string、math、array的常用的方法

    JS为每种数据类型都内置很多方法,真的不好记忆,而且有些还容易记混,现整理如下,以便以后查看: 一.String ①charAt()方法用于返回指定索引处的字符.返回的字符是长度为 1 的字符串. 语 ...

  2. 轻松掌握VS Code开发.Net Core及创建Xunit单元测试

    前言 本篇文章主要还是介绍使用 VS Code 进行.Net Core开发和常用 CLI命令的使用,至于为啥要用VS Code ,因为它是真的是好看又好用 :) ,哈哈,主要还是为了跨平台开发做准备. ...

  3. C 语言中模板的几种实现方式

    简单宏定义实现 简单宏定义 - 方式一 这种方式将主要实现部分放在一个宏定义中,利用字符替换的方式实现不同 type 的运算,详细思路见代码: simple_macro_1.c #include &l ...

  4. Android开发Toast Notifications

    Android开发Toast Notifications 关键类 Toast toast通知是一种在窗口表面弹出的消息.它只占用信息显示所需的空间,用户当前的activity仍保持可见并可交互.该通知 ...

  5. 放大倍数超5万倍的Memcached DDoS反射攻击,怎么破?

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯游戏云 背景:Memcached攻击创造DDoS攻击流量纪录 近日,利用Memcached服务器实施反射DDoS攻击的事件呈大幅上 ...

  6. R语言之内存管理

    转载于:http://blog.csdn.net/hubifeng/article/details/41113789 在处理大型数据过程中,R语言的内存管理就显得十分重要,以下介绍几种常用的处理方法. ...

  7. Struts2实现文件上传(三)

    Struts2实现文件上传 配置文件web.xml <?xml version="1.0" encoding="UTF-8"?> <web-a ...

  8. AM335x(TQ335x)学习笔记——LCD驱动移植

    TI的LCD控制器驱动是非常完善的,共通的地方已经由驱动封装好了,与按键一样,我们可以通过DTS配置完成LCD的显示.下面,我们来讨论下使用DTS方式配置内核完成LCD驱动的思路. (1)初步分析 由 ...

  9. C#图解教程 第十五章 接口

    接口 什么是接口 使用IComparable接口的示例 声明接口实现接口 简单接口示例 接口是引用类型接口和as运算符实现多个接口实现具有重复成员的接口多个接口的引用派生成员作为实现显式接口成员实现 ...

  10. css补充

    (一)水平对齐1.使用margin属性水平对齐可通过将左和右外边距设置为 "auto",来对齐块元素.除非已经声明了 !DOCTYPE,否则使用 margin:auto 在 IE8 ...