对象
前面一到六节介绍了 Redis 基本数据结构,但是在 Redis 世界中,并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,包括:字符串对象、列表对象、哈希对象、结合对象和有序集合对象。
Notice: Redis 中每种数据对象至少用到一种基本数据类型。优化对象在不同场景中的使用效率。
源码
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 指向底层实现数据结构的指针
void *ptr;
// 引用计数,用于内存回收
int refcount;
// 对象空转时长
unsigned lru:22;
// ...
} robj;
对象类型
类型常量 | 对象名称 |
---|---|
REDIS_STRING | 字符串对象 |
REDIS_LIST | 列表对象 |
REDIS_HASH | 哈希对象 |
REDIS_SET | 集合对象 |
REDIS_ZSET | 有序集合对象 |
Notice: Redis 中,键总是一个字符串对象,而值可以是字符串,列表,哈希,集合和有序集合对象的其中一种。
对象编码
Redis 对象的 prt 指针指向对象底层实现的数据结构,而这些数据结构由 encoding 属性决定。
编码常量 | 编码对应的底层数据结构 |
---|---|
REDIS_ENCODING_INT | long 类型的整数 |
REDIS_ENCODING_EMBSTR | embstr 编码的SDS |
REDIS_ENCODING_RAW | raw 编码的SDS |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳表 |
对象类型和编码对照表
Redis 一种对象类型,可以有多种编码
对象类型 | 对象编码 | 对象描述 |
---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | 使用整数值实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 使用 embstr 编码的SDS实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用 raw 编码的SDS实现的字符串对象 |
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 | 使用跳表实现的有序集合对象 |
举个栗子
redis> set k1 "v1"
OK
redis> object encoding k1
"embstr"
redis> set k2 "1"
OK
redis> object encoding k2
"int"
redis> set k3 "hello redis.hello redis.hello redis.hello redis.hello redis.hello redis.hello redis.hello redis.hello redis.hello redis."
redis> object encoding k3
"raw"
根据值的不同,Redis 会选择不同的编码类型,以提高效率。
字符串对象
字符串对象的编码可以是 int、embstr、raw。
举个栗子
编码:REDIS_ENCODING_INT
redis> set num 10000
OK
redis> object encoding num
"int"
此时字符串的内部结构是:
-------------------------------
|redisObject |
-------------------------------
|type = REDIS_STRING |
-------------------------------
|encoding = REDIS_ENCODING_INT|
-------------------------------
|ptr = 10086 |
-------------------------------
|...... |
-------------------------------
编码:REDIS_ENCODING_EMBSTR
redis> set pi 3.14
OK
redis> object encoding pi
"embstr"
此时字符串的内部结构是:
--------------------------------------------------------------------
|redisObject |
--------------------------------------------------------------------
|type = REDIS_STRING |
--------------------------------------------------------------------
|encoding = REDIS_ENCODING_EMBSTR |
--------------------------------------------------------------------
|ptr = {sdshdr = {free = 0, len = 4, buf = |'3'|'.'|'1'|'4'|'\0'|}}|
--------------------------------------------------------------------
编码:REDIS_ENCODING_RAW 和 REDIS_ENCODING_EMBSTR 类似
列表对象
列表对象的编码可以是 ziplist 或者 linkedlist。
编码转换
满足以下两个条件时,使用 ziplist 作为编码,否则使用 linkedlist 编码:
- 保存的字符串长度小于64字节。
- 列表元素数量小于512个。
哈希对象
哈希对象的编码可以是 ziplist 或者 hashtable。
编码转换
满足以下两个条件时,使用 ziplist 作为编码,否则使用 hashtable 编码:
- 哈希对象保存的键和值的字符串长度小于64字节。
- 键值对数量小于512个
集合对象
集合对象的编码可以是 intset 或者 hashtable。
编码转换
满足以下两个条件时,使用 intset 编码,否则使用 hashtable:
- 集合对象保存的元素都是整数类型。
- 集合对象保存的元素数量小于512个。
有序集合对象
有序集合对象的编码可以是 ziplist 或者 skiplist。
编码转换
满足以下两个条件时,使用 ziplist 编码,否则使用 skiplist:
- 有序集合对象保存的元素数量小于128个。
- 有序集合对象保存的所有元素的长度都小于64字节。
内存回收
C 语言没有自动内存回收功能,Redis 在对象中维护 refcount 属性来实现内存回收。
对象状态的变化和引用计数器的关系:
- 创建一个新对象,引用计数器初始化为 1;
- 对象被程序引用,计数器加 1;
- 不再被程序使用,计数器减 1;
- 对象的引用计数器变 0 时,对象所占用的内存被释放。
对象共享
a 保存一个整数值 10 的值对象;这时 b 也创建一个保存整数值 10 的值对象;此时 a 和 b 共享同一个字符串对象;并且 a 的引用计数器加 1 。
Notice:Redis 只对包含整数的字符串对象进行共享
redis> set a 10
OK
redis> object refcount a
"2"
redis> set b 10
OK
redis> object refcount a
"3"
redis> incr b
(integer) 11
redis> object refcount a
"2"
Notice:第一个 “2”,一个是持有这个对象的服务器程序,一个是共享这个值的对象键 a 。
对象的空转时长
redisObject 对象维护 lru 属性记录对象的空转时长;空转时长较大的,会优先被回收。
redis> set k1 "v1"
OK
// 等10秒
redis> object idletime k1
"10"
// 访问
redis> get k1
"v1"
// k1 刚刚被访问过,此时空转为 0
redis> object idletime k1
"0"