Redis设计与实现对象
上一篇:Redis设计与实现-数据结构
Redis是一个Key-Value数据库,key和value都是一个对象,key始终是一个字符串对象,而value是根据encoding来动态决定的,它最终指向的是一种数据结构,比如前面讲到的SDS、字典、跳跃表
定义
typedef struct redisObject{//类型,耳熟能详的的5种类型
unsigned type:4;
//编码方式
unsigned encoding:4;
//指向底层实现数据结构的指针
void *ptr;
//引用计数
int refcount;
//记录对象最后一次被命令程序访问的时间
unsigned lru;
}
redisObject.type
有如下5种类型
字符串
列表
哈希
集合
有序集合
可以使用type keyName来查看键的类型
redisObject.encoding
编码-encoding有如下取值:
REDIS_ENCODING_INT : long类型的整数 : int
REDIS_ENCODING_EMBSTR : embstr编码的简单动态字符串 : embstr
REDIS_ENCODING_RAW : 简单动态字符串-SDS : raw
REDIS_ENCODING_HT : 字典 : hashtable
REDIS_ENCODING_LINKEDLIST : 双端链表 : linkedlist
REDIS_ENCODING_ZIPLIST : 压缩列表 : ziplist
REDIS_ENCODING_INTSET : 整数集合 : intset
REDIS_ENCODING_SKIPLIST : 跳跃表和字典 : skiplist
Redis使用encoding和数据结构中元素的特征(元素个数、元素值的长度)来决定到底是用什么数据结构
对象类型与编码类型对应关系如下:
REDIS_STRING{REDIS_ENCODING_INT,REDIS_ENCODING_EMBSTR,REDIS_ENCODING_RAW}
REDIS_LIST{REDIS_ENCODING_ZIPLIST,REDIS_ENCODING_LINKEDLIST}
REDIS_HASH{REDIS_ENODING_ZIPLIST,REDIS_ENCODING_HT}
REDIS_SET{REDIS_ENCODING_INTSET,REDIS_ENCODING_HT}
REDIS_ZSET{REDIS_ENCODING_ZIPLIST,REDIS_ENCODING_SKIPLIST}
五种对象
面试可能会被经常问到的Redis有那五种数据类型,字符串、列表、哈希、集合、有序集合,下面对它们一一解释
字符串对象
字符串对象用来保存整数或者字符串,它有三种编码方式,并且对于三种数据结构
编码方式
其中REDIS_ENCODING_RAW对应的数据结构上一篇文章已经详细说过。补充一下REDIS_ENCODING_INT和REDIS_ENCODING_EMBSTR两种编码的解释
REDIS_ENCODING_INT:就是C语言的long来保存数据,前提是字符串可以用long来表达,比如不能有字符
REDIS_ENCODING_EMBSTR:它本质上依然是sds,但如果字符串长度小于等于39个字节,就会用该种编码方式。
为什么会有这种编码方式喃,它在什么时候使用喃???因为对象是由redisObject包含sds,REDIS_ENCODING_RAW需要两次申请内存(分别是:redisObject、sds),而REDIS_ENCODING_EMBSTR只需要申请一次内存,将两种数据结构的内存放在一起。它带来了如下三个有点:
两次内存申请变一次
两次释放内存变一次
更好的利用缓存
编码转换
既然有三种不同的方式,那么在某些条件下Redis应该会触发类型转换的
append
setrange
长度超过39字节
整数变字符串
命令实现
列表对象
ZIPLIST实现方式
压缩列表中的节点就是列表中的某一个项,至于操作的时候到底是列表头还是列表尾,由命令决定,比如RPUSH、LPUSH,如:
编码转换
当列表对象同时满足以下两项条件时使用ZIPLIST-压缩列表:
1.所有字符串长度小于64字节
2.元素个数小于512
可以通过修改redis.conf中的如下两个配置项更改条件值:
1.list-max-ziplist-value
2.list-max-ziplist-entries
命令实现
哈希对象
ZIPLIST实现方式
先将保存了"键"的压缩列表节点放到列表表尾,然后将保存了"值"的压缩列表节点放到列表表尾,因此"键"总是在"值"前面的,如:
编码转换
当哈希对象同时满足以下两项条件时使用ZIPLIST:
1.所有字符串长度小于64字节
2.元素个数小于512
可以通过如下两个配置项修改上述两个条件:
1.hash-max-ziplist-value
2.hash-max-ziplist-entries
命令实现
集合对象
整数集合实现方式
数据结构是一个带长度的整数数组,操作元素时会修改长度,并删除或者添加元素到数组中
伪代码如下:
typedef struct intset{//长度
long len;
//数组
long content[];
}
编码转换
当集合对象同时满足以下两项条件时使用intset:
1.所有元素都是整数值
2.元素个数小于512
可以通过如下配置项修改上述第二个条件:
1.set-max-intset-entries
命令实现
有序集合对象
压缩列表实现方式
分值最小的元素存放到压缩列表最前面,"元素的成员"存放在"元素的分值"前面
实现方式为什么是字典+跳跃表
因为两种数据结构都很明显的克服对了对方的缺点
1.跳跃表范围查找非常厉害
2.字典单个查找非常厉害
所以有序集合同时使用两种数据结构,新增一个元素在两种数据结构中都会添加,但是元素(成员和分值)是共享的,因此内存开销并不会增大很多
虽然采用这种组合方式内存增加了一种数据结构和存放元素指针的空间,CPU增加了一种数据结构构造的开销,但是新增并不是redis的主要用途,而是提供高效的查询。两种数据结构结合使用大大的提高了单个和范围的查询效率
编码转换
当有序集合对象同时满足以下两项条件时使用ZIPLIST:
1.所有字符串长度小于64字节
2.元素个数小于128
可以通过如下两个配置项修改上述两个条件:
1.hash-max-ziplist-value
2.hash-max-ziplist-entries
命令实现
内存回收
因为C语言没有自动内存回收机制,因此Redis在自己的对象系统中构建了一个用计数(redisObject.refCount)技术实现的内存回收机制,通过这一机制,程序跟中计数在适当的时候自动释放对象并进行内存回收。
在如下几种场景会修改refCount:
在创建一个新对象的时候,值为1
新程序(通常是五种数据结构)使用时,值加1
程序不再使用时,值减1
当值为0时,对象被释放并回收内存
从这几个选项可以看出对象有创建-操作-释放的生命周期。
共享对象
谈到了内存回收的refCount,应该是可以想到对象共享,正因为有对象共享,引用计数(refCount)才会被命令操作而改变。。。前面讲到的有序集合是通过字典和跳跃表两种数据结构实现的,它的值对像就是共享的,在这里得到了答案。
Redis启动的时候,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值。当服务器要使用这些值的时候,就可以直接使用,而不用再去创建这些值对象。
对象空转时长
使用OBJECT IDLETIME命令可以打印出给定键的空转时长(redisObject.lru),这一空转时长=当前时间-lru的时间,使用该命令的时候,不会更改lru的值。
如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存
以上是 Redis设计与实现对象 的全部内容, 来源链接: utcz.com/z/512331.html