在Redis的源码中,到处可见robj类型的变量,在介绍其他模块时,只是将它看成Redis的数据类型,并没有深入探究。而事实上,它是对象系统,提供了对多种类型的封装,Redis可以根据数据的具体形式,采用不同的类型进行存储,一方面提高了灵活性,一方面也为节省内存提供了便利,因为Redis所有的数据都是直接存在内存中的,所以需要想方设法节省内存

对象结构

redisObject结构中包含了对象系统的定义,记录了数据类型,数据编码格式,最后一次访问的时间,引用计数,值

//server.h
/* 对象系统的定义 */
typedef struct redisObject {unsigned type:4; //类型,可以是string, hash, list, set和zset(宏定义给出)unsigned encoding:4; //编码,表示ptr底层数据以何种方式存储unsigned lru:LRU_BITS; //最后一次访问的时间int refcount; //引用计数void *ptr; //实际存放的值
} robj;

:n是位域,显式指出该变量占用的位数,上述定义中,type占4位,encoding占4位,二者共占8位,即1个字节

类型

类型就是命令指出的数据格式

命令 操作 举例
SET 键为字符串对象,值为字符串对象 SET db redis
SADD 键为字符串对象,值为集合对象 SADD db redis mongodb mysql
RPUSH 键为字符串对象,值为列表对象 RPUSH db redis mongodb mysql
HMSET 键为字符串对象,值为哈希对象 HMSET profile name Tom age 25 sex male
ZADD 键为字符串对象,值为有序集合对象 ZADD price 8.5 apple 5.0 banana 6.0 cherry

这5种类型由宏定义给出

//server.h
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4

Redis提供了TYPE命令用于返回不同数据的类型

127.0.0.1:6379> SET db redis
OK
127.0.0.1:6379> TYPE db //SET,字符串类型值
string
127.0.0.1:6379> SADD db_sadd redis mongodb mysql
(integer) 3
127.0.0.1:6379> TYPE db_sadd    //SADD,集合类型值
set
127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> TYPE price  //ZADD,有序集合类型值
zset
127.0.0.1:6379> RPUSH db_rpush redis mongodb mysql
(integer) 3
127.0.0.1:6379> TYPE db_rpush   //RPUSH,列表类型值
list
127.0.0.1:6379> HMSET profile name Tom age 25 sex male
OK
127.0.0.1:6379> TYPE profile    //HMSET,哈希表类型值
hash

编码

编码代表数据实际的存储格式,实际保存的类型和提供的类型不一定相同,举个例子,如果使用SET version 10添加一个字符串类型的键值对

#define OBJ_ENCODING_RAW 0     /* Raw格式,常规字符串类型 */
#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  /* EMBSTR格式,适用于存储较短的字符串类型,比Raw少申请一次内存 */
#define OBJ_ENCODING_QUICKLIST 9 /* 快速列表 */

Redis提供OBJECT ENCODING命令获取键对应的值在底层的编码格式

底层数据结构 编码常亮 OBJECT ENCODING命令输出
整数 OBJ_ENCODING_INT “int”
embstr编码字符串 OBJ_ENCODING_EMBSTR “embstr”
raw编码字符串 OBJ_ENCODING_RAW “raw”
字典 OBJ_ENCODING_HT “hashtable”
双端链表 OBJ_ENCODING_LINKEDLIST “linkedlist”
压缩列表 OBJ_ENCODING_ZIPLIST “ziplist”
整数集和 OBJ_ENCODING_INTSET “intset”
跳表 OBJ_ENCODING_SKIPLIST “skiplist”
127.0.0.1:6379> set db_set_embstr redis
OK
127.0.0.1:6379> OBJECT ENCODING db_set_embstr   //短字符串采用embstr编码
"embstr"
127.0.0.1:6379> set db_set_raw "long long long long long long long long long long ago ..."
OK
127.0.0.1:6379> OBJECT ENCODING db_set_raw  //长字符串采用raw编码
"raw"
127.0.0.1:6379> SADD numbers 1 3 5
(integer) 3
127.0.0.1:6379> OBJECT ENCODING numbers //只有数字,采用整数集合
"intset"
127.0.0.1:6379> SADD numbers "seven"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING numbers //增加了一个字符串,不能再采用整数集合,改为哈希表
"hashtable"

可以看到,Redis会自适应改变数据底层的编码格式,而不是固定和一种类型绑定,这大大提高了灵活性

最后一次访问时间

用来记录最后一次访问该数据的时间,可以获得该数据的空转时长,使用频率等

引用计数

模仿C++的智能指针,使多个对象共享同一个底层数据,以便于节省内存占用,当引用计数为0时,Redis会释放该对象的内存。

对象创建

对象操作主要涉及根据不同类型创建不同对象等操作

最基本的创建对象操作由createObject函数完成,函数根据给定类型和值创建编码格式为raw的对象,其它创建对象的函数大多数都是直接或间接调用该函数

//object.c
/* 根据type和ptr创建编码为raw字符串的对象 */
robj *createObject(int type, void *ptr) {/* 申请对象内存空间 */robj *o = zmalloc(sizeof(*o));/* 设置类型,编码,值,引用计数初始化为1 */o->type = type;o->encoding = OBJ_ENCODING_RAW;o->ptr = ptr;o->refcount = 1;/* Set the LRU to the current lruclock (minutes resolution). *//* 计算当前时间,赋值给lru作为最后一次访问时间 */o->lru = LRU_CLOCK();/* 返回对象指针 */return o;
}

创建字符串类型对象

字符串有raw和embstr两种类型和编码格式,raw适用于长字符串,需要执行两次动态内存的申请,而embstr适用于短字符串,仅仅需要一次内存申请,在创建字符串类型的对象时,Redis会判断数据的长度以决定采用哪一个

//object.c
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
/* 创建字符串类型对象,根据数据长度不同选择不同的类型格式 */
robj *createStringObject(const char *ptr, size_t len) {/* 根据长度不同选择不同的编码方式 */if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len);
}

可以看到,长度小于44的字符串默认都采用embstr,而大于44的采用raw

raw类型的字符串对象创建直接调用createObject函数即可,因为raw类型的字符串底层编码也是raw

//object.c
/* 创建raw字符串类型变量 */
robj *createRawStringObject(const char *ptr, size_t len) {/* sdsnewlen()创建一个长度为len的sds字符串 */return createObject(OBJ_STRING,sdsnewlen(ptr,len));
}

sdsnewlen函数是创建一个长度为len,值为ptr的sds变量

embstr类型的字符串创建不可以调用createObject函数,由于采用embstr编码格式,数据分布是不同的,需要重新实现创建函数

//object.c
/* 创建类型为embstr,编码为embstr的字符串对象 */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {/* 和sds对象的创建有关 */robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);struct sdshdr8 *sh = (void*)(o+1);/* 设置类型,编码,数据,引用计数,最后一次访问时间 */o->type = OBJ_STRING;o->encoding = OBJ_ENCODING_EMBSTR;o->ptr = sh+1;o->refcount = 1;o->lru = LRU_CLOCK();/* 将数据复制给sds对象,和sds有关 */sh->len = len;sh->alloc = len;sh->flags = SDS_TYPE_8;if (ptr) {memcpy(sh->buf,ptr,len);sh->buf[len] = '\0';} else {memset(sh->buf,0,len+1);}return o;
}

此外,Redis还提供根据长整型,长浮点型创建一个字符串类型对象,本质都一样,这里不再一一赘述

创建其它类型对象

除了字符串类型对象之外,其它类型对象的创建都显得比较简单,仅仅是创建一个相应类型的变量,然后调用createObject函数,返回后将编码格式改成对应类型的编码格式

//object.c
/* 创建快速列表对象 */
robj *createQuicklistObject(void) {quicklist *l = quicklistCreate();robj *o = createObject(OBJ_LIST,l);o->encoding = OBJ_ENCODING_QUICKLIST;return o;
}/* 创建压缩列表对象 */
robj *createZiplistObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_LIST,zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o;
}/* 创建集合对象 */
robj *createSetObject(void) {dict *d = dictCreate(&setDictType,NULL);robj *o = createObject(OBJ_SET,d);o->encoding = OBJ_ENCODING_HT;return o;
}/* 创建整数集合对象 */
robj *createIntsetObject(void) {intset *is = intsetNew();robj *o = createObject(OBJ_SET,is);o->encoding = OBJ_ENCODING_INTSET;return o;
}/* 创建哈希对象 */
robj *createHashObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_HASH, zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o;
}/* 创建有序集合对象 */
robj *createZsetObject(void) {zset *zs = zmalloc(sizeof(*zs));robj *o;zs->dict = dictCreate(&zsetDictType,NULL);zs->zsl = zslCreate();o = createObject(OBJ_ZSET,zs);o->encoding = OBJ_ENCODING_SKIPLIST;return o;
}/* 创建集合压缩列表对象 */
robj *createZsetZiplistObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_ZSET,zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o;
}

小结

本篇主要是对Redis对象系统的一个概述,核心目的就是弄清楚Redis底层的类型和编码都有哪些,接下来会对每个数据结构进行具体的分析,到时候还会引用本篇的部分代码。分析数据结构是最无聊的事情,也正因为如此才没有在最开始分析,不过为了后面的持久化功能,对象系统是不得不啃的骨头,希望自己能够坚持!

Redis源码剖析(九)对象系统概述相关推荐

  1. Redis源码剖析和注释(十六)---- Redis输入输出的抽象(rio)

    Redis源码剖析和注释(十六)---- Redis输入输出的抽象(rio) . https://blog.csdn.net/men_wen/article/details/71131550 Redi ...

  2. 【Redis源码剖析】 - Redis IO操作之rio

    原创作品,转载请标明:http://blog.csdn.net/xiejingfa/article/details/51433696 Redis源码剖析系列文章汇总:传送门 Reids内部封装了一个I ...

  3. Redis源码剖析之GEO——Redis是如何高效检索地理位置的?

    Redis GEO 用做存储地理位置信息,并对存储的信息进行操作.通过geo相关的命令,可以很容易在redis中存储和使用经纬度坐标信息.Redis中提供的Geo命令有如下几个: geoadd:添加经 ...

  4. 【Redis源码剖析】 - Redis持久化之RDB

    原创作品,转载请标明:http://blog.csdn.net/xiejingfa/article/details/51553370 Redis源码剖析系列文章汇总:传送门 Redis是一个高效的内存 ...

  5. Redis源码剖析之内存淘汰策略(Evict)

    文章目录 何为Evict 如何Evict Redis中的Evict策略 源码剖析 LRU具体实现 LFU具体实现 LFU计数器增长 LFU计数器衰减 evict执行过程 evict何时执行 evict ...

  6. redis源码剖析(十五)——客户端思维导图整理

    redis源码剖析(十五)--客户端执行逻辑结构整理 加载略慢

  7. 【Redis源码剖析】 - Redis内置数据结构之压缩列表ziplist

    在前面的一篇文章[Redis源码剖析] - Redis内置数据结构之双向链表中,我们介绍了Redis封装的一种"传统"双向链表list,分别使用prev.next指针来指向当前节点 ...

  8. redis源码剖析(3):基础数据结构dict

    目录 1.dict概述 2.字典的定义 3.哈希算法 4.字典的初始化及新增键值对 4.1 字典初始化 4.2 新增键值对 5.rehash(重新散列)操作 5.1 rehash操作方式 5.2 re ...

  9. Redis源码剖析和注释(二十四)--- Redis Sentinel实现(哨兵操作的深入剖析)

    Redis Sentinel实现(下) 本文是Redis Sentinel实现(上)篇文章的下半部分剖析.主要剖析以下内容: 4. 哨兵的使命 Redis Sentinel实现下 哨兵的使命 1 周期 ...

最新文章

  1. python wav模块获取采样率, 采样点,声道,量化位数和时间
  2. H5新增API_geoLocation
  3. ubuntu与win10互换硬盘
  4. [poco] 访问数据库
  5. 认识阿里云的产品逻辑:基础设施必须必业务跑得快
  6. Ubuntu16.04下 安装使用svn记录(注意不是搭建)
  7. Windows平台手动卸载Oracle Server【完整+干净】
  8. shell字符串的截取的问题
  9. 吴恩达机器学习视频笔记记录(第2、5、7、8章)
  10. ant design pro模板_ant design pro超详细入门教程
  11. C语言加减乘除运算符
  12. 银耳椰椰——Alpha冲刺Day06
  13. 手把手教你使用--常用模块--HC05蓝牙模块,无线蓝牙串口透传模块,(实例:手机蓝牙控制STM32单片机点亮LED灯)
  14. 如何判断建设用地是否符合土地利用总体规划?
  15. org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction
  16. 电脑断电后,突然无线有线网卡无法使用
  17. SAP MM模块常用表总结
  18. (转)[教你开启冻酸奶的app2sd] android2.2的APP TO SD功能启动方法
  19. Air Quality Index,简称AQI
  20. 不自律的人,如何把一件事做成功?

热门文章

  1. oracle 触发器登录,【学习笔记】Oracle触发器 实现指定用户登录oracle案例
  2. 海量数据随机抽样问题(蓄水池问题)
  3. 40.QT-QPropertyAnimationdong和QParallelAnimationGroup动画实现
  4. 第27章:MongoDB-索引--唯一索引
  5. Uva 1220,Hali-Bula 的晚会
  6. 让你觉得破坏了封装性的扩展方法
  7. Display Skin
  8. Django CVE-2019-14234
  9. [Python从零到壹] 八.数据库之MySQL和Sqlite基础知识及操作万字详解
  10. 【数据结构与算法】之深入解析“股票价格波动”的求解思路与算法示例