1. 概念
Redis使用对象表示数据库键和值,每创建一个键值对,至少创建了两个对象,键对象和值对象。每个对象用redisObject
表示,该结构中有三个和保存数据有关的属性type
、encoding
、ptr
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//...
}
2. 类型
type
记录了对象的类型,值如下:
对象 | 对象 type 属性的值 | TYPE 命令的输出 |
---|---|---|
字符串对象 | REDIS_STRING | “string” |
列表对象 | REDIS_LIST | “list” |
哈希对象 | REDIS_HASH | “hash” |
集合对象 | REDIS_SET | “set” |
有序集合对象 | REDIS_ZSET | “zset” |
对于Redis来说,键总是字符串对象,值可以是上面的任何一种,因此:当成一个数据库键为“字符串键”,指的是这个键对应的值是字符串,以此类推,查看类型命令:type [key]
3. 对象的底层编码
对象所使用的底层数据结构 | 编码常量 | OBJECT ENCODING 命令输出 |
---|---|---|
整数 | REDIS_ENCODING_INT | “int” |
embstr 编码的简单动态字符串(SDS) | REDIS_ENCODING_EMBSTR | “embstr” |
简单动态字符串 | REDIS_ENCODING_RAW | “raw” |
字典 | REDIS_ENCODING_HT | “hashtable” |
双端链表 | REDIS_ENCODING_LINKEDLIST | “linkedlist” |
压缩列表 | REDIS_ENCODING_ZIPLIST | “ziplist” |
整数集合 | REDIS_ENCODING_INTSET | “intset” |
跳跃表和字典 | REDIS_ENCODING_SKIPLIST | “skiplist” |
通过encding
设定对象使用的编码,而不是为特定类型的对象关联一种特定的编码,极大提高了Redis灵活性和效率,因为Redis可以根据不同的场景为每一个对象设置不同的编码。举个例子,在列表对象包含元素较少时使用压缩列表作为对象的底层实现,因为:
- 压缩列表比双端列表省内存,且在元素较少时,在内存中可以以连续块方式保存压缩列表,可以更快被载入到缓存中
- 随着元素增多,压缩列表优势就逐渐消失了,对象底层实现会转向功能更强、更适合保存大量数据的双端列表上
每种类型的对象都至少可以使用两种不同的编码:
对象类型 | 对象编码 | 对象 | 时机 |
---|---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | 使用整数值实现的字符串对象。 | 字符串为整数值,如:set number 10086 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 使用 embstr 编码的简单动态字符串实现的字符串对象。 | 字符串,且长度小于39 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用简单动态字符串实现的字符串对象。 | 字符串,且长度大于39 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的列表对象。 | 元素长度小于64字节,且元素数量小于512 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用双端链表实现的列表对象。 | 有元素长度大于64字节,或者元素数量大于512 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的哈希对象。 | 键和值字符串长度都小于64,且键值对数量小于512 |
REDIS_HASH | REDIS_ENCODING_HT | 使用字典实现的哈希对象。 | 有键或者值字符串长度大于64,或者键值对数量大于512 |
REDIS_SET | REDIS_ENCODING_INTSET | 使用整数集合实现的集合对象。 | 所有元素是整数值,且元素不大于512 |
REDIS_SET | REDIS_ENCODING_HT | 使用字典实现的集合对象。 | 有元素不是整数值,或者有元素不大于512 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的有序集合对象。 | 元素数小于128个,且元素长度都小于64字节 |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 使用跳跃表和字典实现的有序集合对象。 | 元素数大于128个,或者有元素长度都大于64字节 |
3.1 字符串
embstr
和raw
在对象执行命令时效果一样,但是使用前者保存短字符串有以下好处
embstr
编码将创建字符串所需内存分配次数从raw
两次降为一次,所以释放内存次数也响应减少embstr
编码的字符串都保存在一块连续的内存中,所以能更好的的利用缓存带来的优势
浮点数使用字符串保存的,保存时程序先将浮点数转换为字符串在保存,使用时,程序现将字符串转换为浮点数执行运算后在转换为字符串存起来
Redis没有修改embstr
字符串对象的方法,因此对embstr
执行修改命令时,辉县将其转换为raw
然后执行修改命令,因此embstr
执行修改命令后,无论长度多少都会编程raw
4. 类型检查和命令多态
Redis命令可以分为两类:
- 可以执行任何类型的键:
DEL
、EXPIRE
、RENAME
、TYPE
、OBJECT
… - 针对特定类型的,如:
SET
、GET
、APPEND
、STRLEN
只针对字符串HDEL
、HSET
、HGET
、HLEN
只针对哈希键- …
4.1 类型检查的实现
通过redisObject的type
属性实现的,执行一个特定命令之前,服务器先检查输入数据库键的值是否为执行命令所需的类型,是的话执行,反之返回一个类型错误,如LLEN
4.2 多态命令实现
Redis除了会根据值对象判断是否能够执行特定的命令外,还会根据值对象的编码方式,选择正确的命令实现来执行命令。
如:列表对象由ziplist
和linkedlist
两种编码可用,前者使用压缩列表API实现列表命令,后者使用双端链表API来实现列表命令。
现在如果要对一个键执行LLEN
,服务器除了确保执行的是列表键之外,还需要根据值编码选择正确的命令实现:
- 如果对象编码是
ziplist
,说明是压缩列表,程序将使用ziplistLen
函数返回列表长度- 如果对象编码是
linkedlist
,说明是双端列表,程序将使用listLenth
函数返回列表长度
借用面向对象术语,可以认为LLEN
命令是多态的,只要执行该命令,无论是压缩列表还是双端列表,命令都可以正确执行。
与DEL
、EXPIRE
区别在于,DEL
、EXPIRE
是基于类型的多态,LLEN
是基于编码的多态