标签:int l命令 sign 跳跃表 void type属性 unsigned 字符串长度 tin
Redis使用对象来表示数据库中的键和值,每次当我们在redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个键对象,一个值对象。
Redis中的每个对象都由一个redisObject结构表示。
typedef struct redisObject {
// 对象类型
unsigned type:4;
// 编码 对象使用的底层数据结构
unsigned encoding:4;
// 指向对象的底层实现数据结构
void *ptr;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用计数--->用于内存回收
int refcount;
} robj;
通过encoding属性来设定对象所使用的编码,使得Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率,极大地提升了redis的灵活性和效率。
字符串对象的编码可以是int、embstr、raw
(1)int:如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,会直接将整数值保存在字符串对象结构的ptr属性里(将void*转换成long),并将字符串对象的编码设置为int。
(2)embstr:如果字符串对象保存的是一个长度<=39字节字符串值。
(3)raw:如果字符串对象保存的是一个长度>39字节字符串值,字符串对象将使用一个简单动态字符串SDS来保存这个字符串值,并将对象的编码设置为raw。
* embstr与raw区别:
embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样都使用redisObject结构和sdshdr结构来表示字符串对象,但是raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。
embstr编码的字符串对象在执行命令时产生的效果和raw编码相同,使用embstr编码的字符串对象来保存短字符串有以下好处:
* embstr编码的字符串对象实际上是只读的,当我们对 embstr 编码的字符串对象执行任何修改命令时, 程序会先将对象的编码从 embstr 转换成 raw , 然后再执行修改命令。因为这个原因, embstr 编码的字符串对象在执行修改命令之后, 总会变成一个 raw 编码的字符串对象。
* 为什么是39字节
列表对象的编码可以是ziplist或者linkedlist
(1)ziplist
使用压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素,例如执行rpush命令插入三个元素
rpush numbers 1 "three" 5
(2)linkedlist
使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,每个字符串对象都保存了一个列表元素。
linkedlist编码的列表对象在底层的双端链表结构中包含了多个字符串对象,字符串对象是redis五种类型的对象中唯一一种会被其他四中类型对象嵌套对象。
上图StringObject字样的格子表示一个字符串对象,完整字符串对象表示如下
(3)编码转换
当列表对象可以同时满足以下两个条件时,列表对象使用 ziplist 编码,否则使用linkedlist编码:
* 区别:
哈希对象的编码可以是ziplist或者hashtable。
(1)ziplist
每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾。因此:
(2)hashtable
hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:
(3)编码转换
当哈希对象可以同时满足一下两个条件时,哈希对象使用ziplist编码,不能满足这两个条件的哈希对象需要使用hashtable编码
集合对象的编码可以是intset或者hashtable
(1)intset
intset 编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。
(2)hashtable
hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL。
(3)编码转换
当集合对象可以同时满足以下两个条件时,对象使用intset编码,否则使用hashtable编码
有序集合对象的编码可以是ziplist或者skiplist
(1)ziplist
每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),第二个元素保存元素的分值(score)。
集合元素按分值从小到大进行排序,分值较小的元素靠近表头,而分值较大的元素靠近表尾。
(2)skiplist
skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表:
typedef struct zset {
zskiplist *zsl;
dict *dict;
} zset;
(3)编码转化
当有序集合对象可以同时满足以下条件时,对象使用ziplist编码,否则使用skiplist编码:
(4)为什么有序集合需要同时使用跳跃表和字典来实现?
Redis中用于操作键的命令基本上可以分为两种类型,一种命令可以对任何类型的键执行,比如说del命令、expire命令、rename命令、type命令、object命令等,另一种命令只能对特定类型的键执行。
Redis除了会校验值对象的类型,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。
因为C语言并不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(reference counting)技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
每个对象的引用计数信息由redisObject结构的refcount属性记录:
typedef struct redisObject {
// ...
//引用计数
int refcount;
// ...
} robj;
对象的引用计数信息会随着对象的使用状态而不断变化:
对象的整个生命周期可以划分为创建对象、操作对象、释放对象三个阶段。
(1)除了用于实现引用计数内存回收机制之外, 对象的引用计数属性还带有对象共享的作用。在 Redis 中, 让多个键共享同一个值对象需要执行以下两个步骤:
例如包含整数值100的字符串对象同时被键A和键B共享之后,除了对象的引用计数从之前的1变成了2之外,其他属性都没有变化。
共享对象机制对于节约内存非常有帮助,数据库中保存的相同值对象越多,对象共享机制就能节约越多的内存。
(2)
(3)为什么Redis不共享包含字符串的对象?
当服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需的复杂度就会越高,消耗的CPU时间也会越多:
因此,尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值的字符串对象进行共享。
其中在使用时间复杂度为O(n)的命令时应特别注意
(1)字符串
(2)哈希
(3)列表
(4)集合
(5)有序集合
标签:int l命令 sign 跳跃表 void type属性 unsigned 字符串长度 tin
原文地址:https://www.cnblogs.com/zjxiang/p/12115729.html