一、对象的类型与编码
Redis 的键总是字符串类型,值是五种对象类型之一。
字符串键:这个键对应的值为字符串对象。
列表键:这个键对应的值为列表对象。
1、对一个数据库的键执行 type 命令时,返回的结果是值对象的类型。
对象 | 对象 type 属性的值 | type 命令的输出 |
---|---|---|
字符串对象 | REDIS_STRING | "string" |
列表对象 | REDIS_LIST | "list" |
哈希对象 | REDIS_HASH | "hash" |
集合对象 | REDIS_SET | "set" |
有序集合对象 | REDIS_ZSET | "Zset" |
Redis 构造了五种对象类型:
字符串对象,列表对象,哈希对象,集合对象和有序集合对象。
每一个对象都由一个 RedisObject 结构表示。
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层实现数据结构的指针
void *ptr;
}robj;
2、编码和底层实现
encoding 属性决定了对象的底层数据结构,ptr指针指向该数据结构。总共有8 种编码
编码常量 | 对应的底层数据结构 |
---|---|
REDIS_ENCODING_INT | long 类型整数 |
REDIS_ENCODING_EMBSTR | emstr 编码的动态字符串 |
REDIS_ENCODING_RAW | 简单动态字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 |
每种类型的对象至少使用了两种不同的编码。每种类型可以使用的类型如下:
RE类型 | 编码 | 对象 |
---|---|---|
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 命令可以查看一个数据库值对象的编码。
二、字符串对象
字符串对象的编码可以是 int、raw 或者 embstr。
embstr 编码是专门用于保存短字符串的一种优化编码方式。
区别是:
- raw 编码会调用两次内存分配函数来创建 RedisObject 结构和 sdshdr 结构
- 而 embstr 编码只需要调用一次内存分配函数,释放时也是一样
long double类型表示的浮点数,可以用字符串来保存的。
编码的转换
- int 加上一个字符串,就会整体转换为 raw 编码
- embstr 编码实际是只读的,修改之后总会变成 raw
三、列表对象
列表对象的编码可以是 ziplist 或者 linkedlist。
linkedlist 编码的列表对象,在底层双端列表中包含了多个字符串对象。
当 ziplist 使用条件不被满足时,对象就会转换编码成 linkedlist。
四、哈希对象
哈希对象的编码可以是ziplist或者 hashtable。
ziplist 编码:
hashtable 编码:
两种编码可以转换。
五、集合对象
集合对象的编码可以是 intset 或者 hashtable。
intset 编码:
六、有序集合对象
有序集合的编码可以是 ziplist 或者skiplist。
ziplist 编码:
- 每个集合元素使用两个相邻的压缩列表节点来保存。第一个保存成员,第二个保存分值。
- 集合按照分值从小到大排序。
skiplist编码的有序集合使用zset 结构作为底层实现,为了能快速实现查找和范围类型操作,一个 zset 同时包含一个字典和一个跳跃表。
zset 中的跳跃表按照分值从小到大保存了所有集合元素。每个跳跃表节点都保存了一个集合元素:object 保存成员,score 保存元素分值。跳跃表使得程序可以对有序集合进行范围操作。
zset结构中的字典为有序集合创建了一个从成员到分值的映射。键保存成员,值保存元素的分值。字典使得程序可以用 O(1)复杂度查找给定成员的分值。
有序集合的成员都是字符串对象,分值都是 double 类型的浮点数。
zset 中的跳跃表和字典通过指针来共享元素和分值,所以不会浪费内存。
七、类型检查与命令多态
- DEL、EXPIRE、RENAME、TYPE、OBJECT 适用所有类型键
- SET、GET、APPEND、STRLEN 适用字符串键
- HDEL、HSET、HGET、HLEN 适用哈希键
- RPUSH、LPOP、LINSERT、LLEN 适用列表键
- SADD、SPOP、SINTER、SCARD 适用集合键
- ZADD、ZCARD、ZRANK、ZSCORE 适用有序集合键
类型检查是通过 RedisObject 的 type 属性来实现的。
八、内存回收
- 在创建一个新对象时,引用计数的值会被初始化为 1
- 当对象被一个新程序使用时,他的引用计数值会被增 1
- 当对象不再被一个程序使用时,引用计数值减 1
- 当引用计数值变为 0 时,对象所占用的内存会被释放
九、对象共享
- 将数据库键的值指针指向一个现在的值对象
- 将被共享的值对象引用计数加 1
共享对象对于节约内存非常有帮助。
Redis 会共享0-9999 的字符串对象。
十、对象的空转时长
对象的 lru 属性记录了对象最后一次被命令程序访问的时间。
OBJECT IDLETIME可以打印出给定键的空转时长。这一空转时长就是通过将当前时间减去键对象的 lru 时间计算得出的。