Redis的键值对都是由对象来表示的,即每次创建一个新的键值对时,我们会至少创建两个对象,一个对象用作键(键对象),另一个用作值(值对象)。
Redis的每一个对象都是由一个 redisObject
结构表示,该结构中和保存数据有关的三个属性分别是 type
、encoding
、ptr
属性:
1 | typedef struct redisObject{ |
类型
对象的 type
属性记录了对象的类型,属性值对应的常量如下:
类型常量 | 对象的名称 |
---|---|
REDIS_STRING |
字符串对象 |
REDIS_LIST |
列表对象 |
REDIS_HASH |
哈希对象 |
REDIS_SET |
集合对象 |
REDIS_ZSET |
有序集合对象 |
对于Redis数据库保存的键值对,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种,因此:
所以当我们队一个数据库键执行 TYPE
命令时,命令返回的结果为数据库键对应的值对象的类型,而不是键对象的类型;
1 | # 键为字符串对象,值为字符串对象 |
下表为不同类型值对象的TYPE命令输出:
对象 | 对象 type 属性的值 |
TYPE命令的输出 |
---|---|---|
字符串对象 | REDIS_STRING |
“string” |
列表对象 | REDIS_LIST |
“list” |
哈希对象 | REDIS_HASH |
“hash” |
集合对象 | REDIS_SET |
“set“ |
有序集合对象 | REDIS_ZSET |
“zset” |
编码和底层实现
对象的 ptr
指针指向对象的底层实现数据结构,而这些数据结构由对象的 encoding
属性决定。
encoding
属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,这个属性的值在下表中列出:
编码常量 | 编码所对应的底层数据结构 |
---|---|
REDIS_ENCODING_INT |
long 类型的整数 |
REDIS_ENCODING_EMBSTR |
embstr 编码的简单动态字符串 |
REDIS_ENCODING_RAW |
简单动态字符串 |
REDIS_ENCODING_HT |
字典 |
REDIS_ENCODING_LINKEDLIST |
双端链表 |
REDIS_ENCODING_ZIPLIST |
压缩列表 |
REDIS_ENCODING_INTSET |
整数集合 |
REDIS_ENCODING_SKIPLIST |
跳跃表和字典 |
每种类型的对象都至少使用了两种不同的编码,下表列出了每种类型的对象可以使用的编码。
类型 | 编码 | 对象 |
---|---|---|
REDIS_STRING |
REDIS_ENCODING_INT |
使用整数值实现的字符串对象 |
REDIS_ENCODING_EMBSTR |
使用 embstr 编码的简单动态字符串实现的字符串对象 |
|
REDIS_ENCODING_RAW |
使用简单动态字符串实现的字符串对象 | |
REDIS_LIST |
REDIS_ENCODING_ZIPLIST |
使用压缩列表实现的列表对象 |
REDIS_ENCODING_LINKEDLIST |
使用双端链表实现的哈希对象 | |
REDIS_HASH |
REDIS_ENCODING_ZIPLIST |
使用压缩列表实现的哈希对象 |
REDIS_ENCODING_HT |
使用字典实现的哈希对象 | |
REDIS_SET |
REDIS_ENCODING_INTSET |
使用整数集合实现的集合对象 |
REDIS_ENCODING_HT |
使用字典实现的集合对象 | |
REDIS_ZSET |
REDIS_ENCODING_ZIPLIST |
使用压缩列表实现的有序集合对象 |
REDIS_ENCODING_SKIPLIST |
使用跳跃表和字典实现的有序集合对象 |
使用OBJECT ENCODING 命令可以查看一个数据库键的值对象的编码:
1 | redis> SET msg "hello wrold" |
OBJECT ENCODING对不同编码的输出:
对象所使用的底层数据结构 | 编码常量 | 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" |
通过 encoding
属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大的提升了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某场景下的使用效率。
举个例子:
在列表对象包含的元素比较少时,Redis使用压缩列表作为列表对象的底层实现:
- 因为压缩列表比双端链表更节约内存,并且在元素数量较少时,在内存中以连续块方式保存的压缩列表比起双端链表可以更快的被载入到缓存中;
- 随着列表对象包含的元素越来越多,使用压缩列表来保存元素的优势逐渐消失时,对象就会将以底层实现从压缩列表转向功能更强,也更适合保存大量元素的双端链表上面;
字符串对象
字符串对象的编码可以是 int
、raw
、embstr
。
- 如果一个字符串对象保存的是整数值,并且这个整数值可以用
long
类型表示,那么字符串对象会将整数值保存在字符串对象结构的ptr
属性里面(void*
->long
),并将字符串对象的编码设置为int
。 - 如果字符串对象保存的是字符串值,并且这个字符串的长度大于
39
字节,那么字符串对象将会使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw
。 - 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于
39
字节,那么字符串对象将使用embstr
编码的方式来保存这个字符串值。
embstr
编码是专门用于保存短字符串的一种优化编码方式,这种编码和 raw
编码一样,都使用 redisObject
结构和 sdshdr
结构来表示字符串对象,但 raw
编码会调用两次内存分配函数来分别创建 redisObject
结构和 sdshrd
结构,而 embstr
编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含 redisObject
和 sdshdr
结构。
embstr
编码的字符串对象在执行时,产生的效果和 raw
编码的字符串对象执行命令时产生的效果是相同的,但使用 embstr
编码的字符串对象来保存短字符串值有以下好处:
embstr
编码将创建字符串对象所需的内存分配次数从raw
编码的两次降低为一次。- 释放
embstr
编码的字符串对象只需要调用一次内存释放函数,而释放raw
变啊的字符串对象需要调用两次内存释放函数。 - 因为
embstr
编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw
编码的字符串对象能更好地利用缓存带来的优势。
可以用 long double
类型表示的浮点数在Redis中也是作为字符串值来保存的:如果保存一个浮点数到字符串对象里,那么程序会先将这个浮点数转换成字符串值,然后再保存起转换所得的字符串值。
在有需要的时候,程序会把保存在字符串对象里的字符串值转换为浮点数,执行某些操作,执行之后把所得的浮点数值转换为字符串值,继续保存在字符串对象里。(主要是体现在某些运算命令上(+-*/))
下表为字符串对象保存各类型值的编码方式:
值 | 编码 |
---|---|
long 类型保存的整数 |
int |
long doube 类型保存的浮点数 |
embstr 或 raw |
字符串值,或因为长度太大而无法用 long 类型表示的整数,或太大无法用 long double 类型表示的浮点数。 |
embstr 或者 raw |
编码的转换
int
编码的字符串对象和 embstr
编码的字符串对象在条件满足的情况下,会被转换为 raw
编码的字符串对象。
对于 int
编码的字符串对象,如果我们向对象执行了一些命令,使得这个对象保存的不再是整数值,而是一个字符串值,那么字符串对象的编码将从 int
变为 raw
示例:
1 | redis> SET number 10086 |
此外,Redis没有为 embstr
编码的字符串对象编写任何相应的修改程序(只有 int
编码和 raw
编码的字符串对象有这些程序),所以 embstr
编码的字符串对象实际上是只读的,当我们对 embstr
编码的字符串对象执行任何修改命令时,程序会先将对象的编码从 embstr
转换为 raw
,然后再执行修改命令,因此, embstr
编码的字符串对象在执行修改命令之后,总会变成一个 raw
编码的字符串对象。
字符串命令的实现:
命令 | int 编码的实现方法 |
embstr 编码的实现方法 |
raw 编码的实现方法 |
---|---|---|---|
SET | 使用 int 编码保存值。 |
使用 embstr 编码保存值。 |
使用 raw 编码保存值。 |
GET | 拷贝对象所保存的整数值,将这个拷贝转换成字符串值,然后向客户端返回这个字符串值。 | 直接向客户端返回字符串值。 | 直接向客户端返回字符串值。 |
APPEND | 将对象转换成 raw 编码,然后按 raw 编码的方式执行此操作。 |
将对象转换成 raw 编码,然后按 raw 编码的方式执行此操作。 |
调用 sdscatlen 函数,将给定欺负穿追加到现有字符串的末尾。 |
INCRBYFLOAT | 取出整数值,并将其转换成 long double 类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。 |
取出字符串并尝试将其转换成 long double 类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。如果字符串不能被转换成浮点数,那么向客户端返回一个错误。 |
取出字符串并尝试将其转换成 long double 类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。如果字符串不能被转换成浮点数,那么向客户端返回一个错误。 |
INCYBY | 对整数值进行加法计算,得到的计算结果会作为整数被保存起来。 | embstr 编码不能执行此命令,向客户端返回一个错误。 |
raw 编码不能执行此命令,向客户端返回一个错误。 |
DECRBY | 对整数值进行减法运算,得出的计算结果会作为整数被保存起来。 | embstr 编码不能执行此命令,向客户端返回一个错误。 |
raw 编码不能执行此命令,向客户端返回一个错误。 |
STRLEN | 拷贝对象所保存的整数值,将这个拷贝转换成字符串值,计算并返回这个字符串值的长度。 | 调用 sdslen 函数,返回字符串的长度。 |
调用 sdslen 函数,返回字符串的长度。 |
SETRANGE | 将对象转换成 raw 编码,然后按 raw 编码的方式执行此命令。 |
将对象转换成 raw 编码,然后按 raw 编码的方式执行此命令。 |
将字符串特定索引上的值设置为给定的字符。 |
GETRANGE | 拷贝对象所保存的整数值,将这个拷贝转换成字符串值,然后取出并返回字符串指定索引上的字符。 | 直接取出并返回字符串指定索引上的字符。 | 直接取出并返回字符串指定索引上的字符。 |