redis对象

来源:互联网 发布:网络文案编辑招聘 编辑:程序博客网 时间:2024/06/10 05:50

redis对象

前面我们学习了redis各种数据结构,包括简单动态字符串、链表、字典、哈希表、整数集合、压缩列表,其实redis实际不是直接使用这些数据结构的,而是使用称为redis对象的数据结构:redisObject。

1. redis对象定义

redis对象的定义如下:

typedef struct redisObject {    //对象类型    unsigned type:4;    //对象编码    unsigned encoding:4;    //最后一次被访问的时间    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */    //对象的引用计数    int refcount;    //底层数据结构    void *ptr;} robj;

1.1 对象类型-type

其中type表示的是具体数据类型,类型包括如下几种:

类型常量 对象的名称 REDIS_STRING 字符串对象 REDIS_LIST 列表对象 REDIS_HASH 哈希对象 REDIS_SET 集合对象 REDIS_ZSET 有序集合对象

redis中的键总是一个字符串对象,值可以是上述几种对象的一种。

当我们对redis键使用type命令时,返回的结果是值的类型,看下例:

127.0.0.1:6379> set msg "hello world"OK127.0.0.1:6379> type msgstring127.0.0.1:6379> hset jack age 23(integer) 1127.0.0.1:6379> type jackhash

键msg对应的值对象是字符串对象、键jack对应的值对象是哈希对象,下表展示了不同对象类型type命令的输出:

对象 对象type属性的值 type命令的输出 字符串对象 REDIS_STRING “string” 列表对象 REDIS_LIST “list” 哈希对象 REDIS_HASH “hash” 集合对象 REDIS_SET ”set” 有序集合对象 REDIS_ZSET “zset”

1.2 对象编码-encoding

encoding记录了对象的编码,也就是这个对象使用了什么样的底层数据结构实现的,下表展示了encoding的可能取值:

编码常量 编码所对应的底层数据结构 REDIS_ENCODING_INT long类型的整数 REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串 REDIS_ENCODING_RAW 简单动态字符串 REDIS_ENCODING_HT 字典 REDIS_ENCODING_LINKEDLIST 双端链表 REDIS_ENCODING_ZIPLIST 压缩列表 REDIS_ENCODING_INTSET 整数集合 REDIS_ENCODING_SKIPLIST 跳表和字典

下表展示了每种类型的对象使用的所有可能编码:

类型 编码 对象 REDIS_STRING REDIS_ENCODING_INT 使用整数实现的字符串对象 REDIS_STRING REDIS_ENCODING_EMBSTR 使用embstr编码的简单动态字符串实现的字符串对象 REDIS_STRING REDIS_ENCODING_RAW 使用简单动态字符串实现的字符串对象 REDIS_LIST REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象 REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用双端链表实现的列表对象 REDIS_HASH REDIS_ENCODING_ZIPLIST 使用压缩列表实现的哈希对象 REDIS_HASH REDIS_ENCODING_HT 使用字典实现的哈希对象 REDIS_SET REDIS_ENCODING_INTSET 使用整数集合实现的集合对象 REDIS_SET REDIS_ENCODING_HT 使用字典实现的集合对象 REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用压缩列表实现的有序集合对象 REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳表和字典实现的有序集合对象

使用object encoding命令可以查看一个键的值对象的编码,例如:

127.0.0.1:6379> set msg "hello redis"OK127.0.0.1:6379> type msgstring127.0.0.1:6379> object encoding msg"embstr"127.0.0.1:6379> lpush languages chinese japanese english(integer) 3127.0.0.1:6379> type languageslist127.0.0.1:6379> object encoding languages"ziplist"

其中列表languages的编码是ziplist。

redis每种类型的对象都关联2到3种编码方式,不同的场景使用不同的编码方式,提高了内存使用率。后面会详细介绍每种类型的对象底层不同的编码方式,以及同一种类型的对象不同编码方式的转换和转换条件。

1.3 对象引用计数-refcount

redis通过对象的引用计数字段(refcount)来实现内存的回收。随着对象的使用,该字段也会动态变化

  • 创建一个新的对象时,refcount会被初始化为1。
  • 当对象被新的程序引用时,refcount计数递增1。
  • 当对象不再被一个程序使用时,refcount计数递减1。
  • 当refcount等于0的时候,对象所占用的内存会被释放。

除了实现内存回收外,还可以通过引用计数实现对象的共享,例如键A已经创建了包含整数值100的字符串对象,此时若键B也需要整数值100的字符串对象,那么完全可以将键B的值指针指向键A创建的整数值100的字符串对象,并将该对象的引用计数加1 。这种方式极大的提高了内存使用率。

在redis初始化时,会创建10000个从0到9999的字符串对象,这些对象就成为了共享对象。看下例:

127.0.0.1:6379> set k1 123OK127.0.0.1:6379> object refcount k1(integer) 2127.0.0.1:6379> set k2 123OK127.0.0.1:6379> object refcount k2(integer) 3

首先设置k1的值为123,通过object refcount命令看到它的引用计数变为2了。这是因为当redis初始化创建该对象时就将该对象的refcount设置为1,这里又将键k1设置为123,该对象的refcount增加1,变为2。

接着又将k2设置为123,该对象的引用计数加1,变为3。

另外,redis只共享编码为整数的字符串对象。因为只有在共享对象和想要创建的对象相同时,才能使用共享对象。判断两个对象是否相等,对于编码为整数的字符串对象来说,时间复杂度为O(1);对于编码为字符串的字符串对象来说,时间复杂度为O(N);对于包含多个值的对象来说,时间复杂度为O(N^2^),所以为了提高效率,redis只共享编码为整数的字符串对象。

1.4 对象的空转时长-lru

redis对象的lru属性记录了该对象最后被程序访问的时间,该属性可以用来计算键的空转时长,空转时长就是通过将当前的时间减去对象的lru时间计算得到的。例如,通过object idle命令可以查看键的空转时长是多少。

127.0.0.1:6379> object idletime k1(integer) 700902

注意:命令object idletime比较特殊,它不会去修改对象的lru值。

另外,在内存回收的时候,空转时间较长的键可能被优先回收。

1.5 对象的底层实现-ptr

redis对象的ptr字段指向了底层实现,这个底层实现是由encoding值决定的。

2. 字符串对象

字符串对象的类型(type)是string,编码(encoding)是int、raw、embstr的一种。

2.1 int编码的字符串对象

当字符串的值可以用long型整数来表示的时候,redis会将该字符串用int编码。redis会将整数值保存在对象结构的ptr属性里面。此时ptr的类型由原来的void*变为long*。例如,将k1设置为123时,object encoding返回int。

127.0.0.1:6379> set k1 123OK127.0.0.1:6379> object encoding k1"int"

该字符串对象如图1示:

这里写图片描述

图1

2.2 raw编码的字符串对象

当字符串对象保存的是一个字符串值,并且这个字符串值长度大于39字节,redis用简单动态字符串来保存这个值,并将对象的编码设置为raw。看下例子:

这里写图片描述

图2

2.3 embstr编码的字符串对象

当字符串对象保存的是一个字符串值,并且这个字符串值长度小于等于39字节,redis用embstr编码的方式来保存这个字符串值。

embstr编码是专门用来保存短字符串的一种优化编码方式。embstr编码和raw编码一样,都使用redisObject和sdshdr两种数据结构来表示字符串对象。但是raw编码会调用两次内存分配函数分别为redisObject和sdshdr分配内存空间,而embstr编码只会调用一次内存分配函数为redisObject和sdshdr分配一块连续的内存空间。如下图示,值为”hello”的字符串对象:

这里写图片描述

图3

这种方式的好处有:

  • 分配内存次数降为1次
  • 释放内存次数降为1次
  • 内存具有连续性,更好利用缓存

最后,double类型也可以通过字符串对象来保存,看下例子:

127.0.0.1:6379> set k3 1.234OK127.0.0.1:6379> object encoding k3"embstr"

当在需要的时候,redis会在double和字符串之间正确转换。

2.4 字符串对象各编码之间的转换

int编码的和embstr编码的字符串对象在满足一定的条件下会转换为raw编码来存储。

  • 向一个int编码的字符串对象追加字符时,会直接转换成raw编码
  • embstr编码的字符串对象执行任何修改时,会直接转换成raw编码,可以认为embstr编码的字符串对象是只读的

3 列表对象

列表对象的编码可以是ziplist和linkedlist。

3.1 ziplist编码的列表对象

下图是一个ziplist编码的列表对象,其中节点元素是字节数组”hello”、整数值23、整数值35。

这里写图片描述

图4

3.2 linkedlist编码的列表对象

linkedlist编码的列表对象底层使用双端链表实现,每个双端链表节点都是一个字符串对象,下图是一个linkedlist编码的列表对象示意图,其中链表包括两个字符串对象(这里的字符串对象简化了)。

这里写图片描述

图5

3.3 列表对象各编码之间的转换

当列表对象同时满足如下两个条件时使用ziplist编码,否则使用linkedlist编码

  • 列表对象保存的所有字符串对象元素的长度都小于64字节
  • 列表对象保存的元素数量小于512个

当然,这两个值是可以在配置文件中修改的。

4. 哈希对象

哈希对象的编码可以是ziplist和hashtable

4.1 ziplist编码的哈希对象

ziplist编码的哈希对象,当有新的键值对需要插入到哈希对象时,首先会将保存键的压缩列表节点保存到压缩列表的表尾,再将保存值的压缩列表节点保存到压缩列表的表尾。因此同一个键值对总是会紧挨在一起,前一个是键,后一个是值。

下图所示压缩列表编码的哈希对象:

这里写图片描述

图6

该哈希对象包括了两个键值对age:23和name:jack,其中age:23是先添加的,name:jack是后添加的。

4.2 hashtable编码的哈希对象

hashtable编码的哈希对象使用字典作为底层实现。

4.3 哈希对象各编码之间的转换

当哈希对象同时满足以下两个条件时,使用ziplist编码,否则使用hashtable编码

  • 哈希对象保存的键值对的键和值长度都小于64字节
  • 哈希对象保存的键值对数量小于512个

同样地,这两个数值可以通过配置文件进行配置。

5. 集合对象

集合的编码是intset和hashtable。intset编码的集合使用整数集合作为底层实现。

5.1 intset编码的集合对象

下面例子是一个包含三个整数的集合对象,使用了整数集合编码。

这里写图片描述

图7

5.2 hashtable编码的集合对象

hashtable编码的集合对象底层是用字典实现的,其中键就是集合的元素(字符串对象),值都是NULL。

5.3 集合对象各编码之间的转换

当集合对象同时满足以下两个条件时,对象使用intset编码,否则使用hashtable编码

  • 集合对象保存的值都是整数
  • 集合对象保存元素数量不超过512个

其中512这个值是可以通过配制文件修改的。

6. 有序集合对象

有序集合对象的编码可以是ziplist和skiplist。

6.1 ziplist编码的有序集合对象

ziplist编码的有序集合对象,底层使用压缩列表实现,每个集合元素由两部分组成,第一个保存元素的成员,第二个保存元素的分值。并且压缩列表集合内的元素按照分值从小到大排列。

例如,下图是一个压缩列表编码的有序集合对象的例子,其中元素都是按照分值从小到大排序的。

这里写图片描述

图8

6.2 skiplist实现的有序集合对象

skiplist实现的有序集合对象底层是通过跳表和字典实现的。底层数据结构定义如下:

typedef struct zset {  zskiplist *zsl;  dict *dict;} zset;

其中跳表zsl按分值大小保存了有序集合的所有元素。

字典dict存储了成员到分值的映射。

有序集合对象中的每个成员都是一个字符串对象,每个分值都是一个double类型的浮点数。zsl和dict会共用字符串和分值对象,不会产生额外的内存。

6.3 有序集合对象各编码之间换转换

当有序集合对象同时满足以下两个条件时,使用ziplist编码,否则使用skiplist进行编码

  • 有序集合保存的所有元素成员的长度都小于64字节
  • 有序集合保存的元素数量小于128个

同样地,这两个值也是可以通过配制文件配制的。

7. 类型检查和命令多态

redis有很多命令,有些命令是通用的,可以对任何键执行。而有些命令只能对特定的键执行。

7.1 类型检查

在执行一个命令前,会首先根据键去查找值对象,然后确定值对象redisObject的type类型是否是执行命令所需要的类型,如果是的话就执行命令,否则返回错误。

7.2 命令多态

除了进行类型检查,redis还可以根据redisObject的编码方式决定如何执行命令。

例如对于ziplist和linkedlist编码的列表对象,当执行命令llen时,显然会执行不同的方法获取列表的长度。

参考:

  1. Redis设计与实现. 黄健宏著
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小孩总是流鼻子怎么办 四个月小孩咳嗽怎么办 儿童流清水鼻涕怎么办 四个月婴儿发烧怎么办 宝宝体温36度怎么办 儿童感冒后鼻塞怎么办 儿童没感冒鼻塞怎么办 宝宝鼻塞怎么办3岁 儿童刚开始感冒鼻塞怎么办 婴儿感冒打喷嚏流鼻涕怎么办 宝宝感冒鼻涕多怎么办 一个多月的宝宝鼻塞怎么办 五个月宝宝鼻塞怎么办 4个月大婴儿鼻塞怎么办 四个月婴儿鼻塞怎么办 鼻炎晚上睡觉鼻塞怎么办 感冒了一直流鼻涕怎么办 感冒了鼻塞严重怎么办 两边鼻子都塞怎么办 感冒睡觉鼻子堵怎么办 孩子鼻炎流清水怎么办 小儿鼻涕流不停怎么办 老是单侧流鼻涕怎么办 有鼻炎光流鼻涕怎么办 一只鼻塞流鼻涕怎么办 鼻子总痒流鼻涕怎么办 鼻子总是痒流鼻涕怎么办 感冒后老流鼻涕怎么办 鼻炎犯了鼻塞怎么办 鼻炎鼻塞鼻涕多怎么办 猪鼻塞怎么办速效办法 感冒难受怎么办小窍门 来月经上火了怎么办 鼻血狂流不止怎么办 十三个月宝宝流鼻血怎么办 单侧鼻子出血怎么办 流鼻血停不下来怎么办 上火鼻血流不停怎么办 孩子经常出鼻血怎么办 6岁宝宝流鼻血怎么办 鼻血倒流到喉咙怎么办