在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. 理科生用创意毁灭世界,爆笑!
  2. 如何从KDE中切換到openSUSE的GNOME桌面环境
  3. python 字符编码
  4. 【Qt开发】【VS开发】【Linux开发】OpenCV、Qt-MinGw、Qt-msvc、VS2010、VS2015、Ubuntu Linux、ARM Linux中几个特别容易混淆的内容...
  5. 开源项目event-stream被注入恶意代码,盗取区块链钱包助记词
  6. 验证mongodb主从复制过程~记录操作
  7. Java8新特性Stream
  8. mac 下 使用 brew 配置 环境
  9. 汽车电子传感器科普:激光雷达 毫米波雷达 超声波雷达
  10. Maven中安装本地Jar包到仓库中或将本地jar包上传
  11. 20180927-1
  12. 国外虚拟机下linux及mysql常用命令
  13. 双十一京东PLUS会员 VS 阿里88VIP,谁的羊毛多?
  14. Contours 等高线图
  15. SHELL编写NGINX自动部署脚本
  16. Linux下Socket客户端服务器通信
  17. 汉王人脸通正式亮相 让十三亿张脸生动起来
  18. 电脑从windows变成linux,从Windows换到Linux下来
  19. kde下gwenview启动慢,甚至几十秒才能启动
  20. uni真机showToast不显示

热门文章

  1. 四则运算3(二柱子同学的第三炼狱)
  2. STL源码剖析---红黑树原理详解下
  3. 2009年浙江大学计算机及软件工程研究生机试真题
  4. 在linux中安装.sh 文件
  5. UG集成显卡 显示成烂面
  6. 用CURD来拯救前端的一切的一切
  7. ffmpeg 0.8.11 VC编译的SDK已经发布
  8. msf与cs互相传动
  9. 【数据结构与算法】之深入解析“颜色分类”的求解思路与算法示例
  10. Swift之深入解析访问控制权限