结构:
typedef struct redisObject{
//类型 REDIS_STRING(字符串)、REDIS_LIST(列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)
unsigned type:4;
//编码 具体的底层数据结构类型
unsigned encoding:4;
//指向底层实现数据结构的指针
void *ptr;
//……
}robj;
一、字符串对象
type:
REDIS_STRING
encoding:
object | 编码常量 |
int | REDIS_ENCODING_INT |
embstr | REDIS_ENCODING_EMBSTR |
raw | REDIS_ENCODING_RAW |
使用场景:
整数值:int
字符串:
字符串长度<32字节时,使用embstr
字符串长度>=32字节时,使用raw
注:
1、int编码的字符串是只读的。执行任何修改命令都会转换为编码类型。
2、当值发生变化,当前的编码类型不能满足时,会发生转换。如:原来是整数类型,append了一条字符串,那么编码会从int转换为raw。
为什么字符串类型不只使用raw类型呢?
使用embstr的好处:
1、embstr编码的字符串对象所有数据都保存在一块连续的内存里面,所以比起raw类型能更好的利用缓存带来的优势。
2、分配内存、释放内存时,raw类型需要时两次,embstr类型只需一次。
结构图:
二、 列表对象
type:
REDIS_LIST
encoding:
object | 编码常量 |
ziplist | REDIS_ENCODING_ZIPLIST |
linkedlist | REDIS_ENCODING_LINKEDLIST |
使用场景:
以下两个条件 都满足 时使用ziplist:
1、列表对象保存的所有字符串元素的长度都小于64字节。
2、列表对象保存的元素数量小于512个。
其他情况使用linkedlist。
注:
上面两个条件的上限值是可以修改的。通过list-max-ziplist-value和list-max-ziplist-entries选项。
结构图:
三、哈希对象
type:
REDIS_HASH
encoding:
object | 编码常量 |
ziplist | REDIS_ENCODING_ZIPLIST |
hashtable | REDIS_ENCODING_HT |
使用场景:
以下两个条件 都满足 时使用ziplist:
1、哈希对象保存的所有键值对的 键和值 的字符串长度都小于64字节。
2、哈希对象保存的键值对数量小于512个。
其他情况使用hashtable。
注:
上面两个条件的上限值是可以修改的。通过hash-max-ziplist-value和hash-max-ziplist-entries选项。
结构图:
四、集合对象
type:
REDIS_SET
encoding:
object | 编码常量 |
intset | REDIS_ENCODING_INTSET |
hashtable | REDIS_ENCODING_HT |
使用场景:
以下两个条件 都满足 时使用intset:
1、集合对象保存的所有元素都是整数值。
2、集合对象保存的元素数量不超过512个。
其他情况使用hashtable。
注:
上面第二个条件的上限值是可以修改的。通过set-max-intset-entries选项。
结构图:
五、有序集合对象
type:
REDIS_ZSET
encoding:
object | 编码常量 |
ziplist | REDIS_ENCODING_ZIPLIST |
skiplist | REDIS_ENCODING_SKIPLIST |
使用场景:
以下两个条件 都满足 时使用ziplist:
1、有序集合保存的元素数量小于128个。
2、有序集合保存的所有元素成员的长度都小于64字节。
其他情况使用skiplist。
注:
1、上面两个条件的上限值是可以修改的。通过zset-max-ziplist-entries和zset-max-ziplist-value选项。
2、使用skiplist时,实际上是同时使用了跳跃表和字典来实现的。
原因:
只使用字典,zrank、zrange等命令需要对所有元素排序,时间复杂度至少O(logN)、额外空间O(N)。
只使用跳跃表,zscore命令时间复杂从O(1)上升为O(logN)。
所以范围性操作使用跳跃表,点操作使用字典。
结构图:
注:字典和跳跃表会共享元素的成员和分值,所以不会造成任何数据重复,也不会浪费任何内存。
六、类型检查和命令多态
命令分类:
1、对任何类型的键执行,如:DEL、EXPIRE、RENAME、TYPE、OBJECT等命令。
2、对特定类型的键执行,如:SET、GET、APPEND等只能对字符串键执行。
类型检查:
在执行一个类型特定的命令之前,Redis会先检查输入键的类型(即type属性的值)是否正确,然后再决定是否执行。
过程图:
命令多态:
列表值对象是ziplist或者linkedlist编码,LLEN命令都可以正常执行。
DEL、EXPIRE是基于类型(type)的多态,LLEN是基于编码(encoding)的多态。
过程图:
七、内存回收
typedef struct redisObject{
//类型 REDIS_STRING(字符串)、REDIS_LIST(列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)
unsigned type:4;
//编码 具体的底层数据结构类型
unsigned encoding:4;
//指向底层实现数据结构的指针
void *ptr;
//引用计数
int refcount;
//……
}robj;
创建一个新对象,refcount=1;
该对象被一个新程序使用时,refcount+1;
该对象不再被一个程序使用时,refcount-1;
该对象refcount=0时,该对象所占内存释放。
API:
函数 | 作用 |
incrRefcount | 将对象的引用计数值增一。 |
decrRefcount | 将对象的引用计数值值减一,当对象的引用计数值等于0时,释放对象。 |
resetRefcount | 将对象的引用计数值设置为0,但并不释放对象,这个函数通常在需要重新设置对象的引用计数值时使用。 |
八、对象共享
refcount属性还带有对象共享的作用。
目的:节约内存
对象共享的对象只有 整数值 。
共享结构如图:
Redis会在初始化服务器时,创建一万个字符串对象(0~9999),当用到值为0~9999的字符串对象时,服务器就会使用这些共享对象。
注:创建共享字符串对象的数量可以通过redis.h/REDIS_SHARED_INTEGERS常量修改。
为什么Redis不共享包含字符串的对象?
验证操作:共享对象和目标对象完全相同。
1)共享对象为保存整数值的字符串对象,验证操作复杂度为O(1);
2)共享对象为保存字符串值的字符串对象,验证操作复杂度为O(N);
3)共享对象为包含了多个值(或者对象的)对象(如列表对象、哈希对象),验证操作复杂度为O(N^2);
所以尽管能节约内存,但受到CPU时间的限制。
九、空转时长
typedef struct redisObject{
//类型 REDIS_STRING(字符串)、REDIS_LIST(列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)
unsigned type:4;
//编码 具体的底层数据结构类型
unsigned encoding:4;
//指向底层实现数据结构的指针
void *ptr;
//引用计数
int refcount;
//最后一次被命令程序访问的时间
unsigned lru:22;
}robj;
操作命令:
object idletime <key>
计算规则:
空转时长=当前时间-lru时间
注:
object idletime命令的实现是特殊的,在访问键的值对象时,不会修改值对象的lru属性。
总结
string | int、embstr、raw |
list | ziplist、linkedlist |
hashtable | ziplist、ht |
set | intset、ht |
zset | ziplist、skiplist |
重点回顾
Redis数据库中的每个键值对的键和值都是一个对象。