Redis阅读笔记--对象的类型与编码

Redis阅读笔记--对象的类型与编码

​ Redis使用对象来表示数据库中的键和值, 每次当我们在Redis的数据库中创建一个键值对时, 我们至少会创建两个对象, 一个对象用顾总键值对的键(键对象),另一个对象用作键值对的值(值对象)。

​ 举个例子, 一下set命令在数据库中创建了一个新的键值对, 其中键值对的键包含了字符串值msg对象,而键值对的值则是一个包含了字符串值hello world的对象:

127.0.0.1:6379> set msg "hello world"
OK

​ Redis中的每个对象都有一个redisObject结构表示, 该结构中和存储数据有关的属性分别是type属性、encoding属性和ptr属性:

typedef struct redisObject {
	//类型
	unsigned type:4;
	//编码
	unsigned encoding:4;
	//指向底层实现数据结构的指针
	void *ptr;
	// ....
} robj;

对象类型

​ 对象的type属性记录对象的类型,这个属性的值可以是下图给出常量的其中一个。

常量类型 对象的名称
REDIS_STRING 字符串对象
REDIS_LIST 列表对象
REDIS_HASH 哈希对象
REDIS_SET 集合对象
REDIS_ZSET 有序集合对象

​ 对于Redis数据库保存的键值对来说, 键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象或有序集合对象的其中一种,因此

  • 当我们称呼一个数据键为"字符串键"时, 我们指的是"这个数据库键所对应的值为字符串对象"
  • 当我们称呼一个键为"列表键"时, 我们指的是"这个数据键所对应的值为列表对象"

type命令的实现方式也与此类似,当我们对一个数据键执行type命令时, 命令返回的结果为数据键多对应的值对象的类型,而不是键对象的类型:

127.0.0.1:6379> set msg 'hello world'
OK
127.0.0.1:6379> type msg
string
127.0.0.1:6379> rpush numbers 1 3 5
(integer) 3
127.0.0.1:6379> type numbers
list
127.0.0.1:6379> hmset profile name Tom age 24 career programmer
OK
127.0.0.1:6379> type profile
hash
127.0.0.1:6379> sadd fruits apple banana cherry
(integer) 3
127.0.0.1:6379> type fruits
set
127.0.0.1:6379> zadd price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> type price
zset

​ 下图列出了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_STRING REDIS_ENCODING_EMBSTR 使用embstr编码的简单动态字符串实现的字符串对象
REDIS_STRING REDIS_ENCODING_RAW 使用简单字符串实现的字符串对象
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用双端链表实现的 列表对象
REDIS_HASH REDIS_ENCODING_ZIPIST 使用压缩列表实现的哈希对象
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 使用跳跃表和字典实现的有序集合
127.0.0.1:6379> set msg 'hello world'
OK
127.0.0.1:6379> object encoding msg
"embstr"
127.0.0.1:6379> object encoding  numbers
"quicklist"
127.0.0.1:6379> object encoding  fruits
"hashtable"

​ 通过encoding属性来设定对象所使用的编码, 而不是为特定类型的对象关联一种固定的编码,这极大地提升了Redis的灵活性和效率, 因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率。

​ 举个例子, 在列表对象包含的元素比较少时, Redis使用压缩列表作为列表对象的底层实现:

  • 因为压缩列表比双端链表更节约内存, 并且在元素数量较少时,在内存中以连续块方式保存的压缩列表比双端链表可以更快被载入到缓冲中
  • 随着列表对象包含的元素越来越多,使用压缩列表来保存的元素的优势逐渐消失时, 对象就会将底层实现从压缩列表转向功能更强、也更适合保存大量元素的双端链表上面

字符串对象

​ 字符串对象的编码可以是int、raw或embstr。

​ 如果一个字符串对象保存的是整数值, 并集这个整数值值可以用long类型表示, 那么字符串对象会将整数值保存在字符串对象结构的ptr属性中(将 void *转换成long),并且将字符串对象的编码设置成int。

​ 举个例子, 如果我们执行一下set命令, 那么服务器将创建一个如下图所示的int编码的字符串对象作为number键的值:

127.0.0.1:6379> set number 10086
OK
127.0.0.1:6379> object encoding number
"int"
redisObject
type
REDIS_STRING
encoding
REDIS_ENCODING_INT
ptr → 10086
...

​ 如果字符串对象保存的是一个字符串值,并且这个字符串值得长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值, 将对象得编码设置为raw。

​ 举个例子,如果我们执行以下命令, 那么服务器将创建一个如图所示得raw编码得字符串对象作为story键得值:

redisObject
type
REDIS_STRING
encoding
REDIS_ENCODING_RAW
ptr
sdshdr
free 0
len 37
buf
'L' 'o' 'n' 'g' ... 'k' 'i' 'n' 'g' '' '.' '.' '.' '\0'
...
127.0.0.1:6379> set story "Long, long ago there lived a king ..." 
OK 
127.0.0.1:6379> strlen story 
(integer) 37 
127.0.0.1:6379> object encoding story
"raw"

​ 如果字符串对象保存得是一个字符串值,那么这个字符串值得长度小于32字节, 那么字符串对象将使用embstr编码得方式来保存这个字符串值。

​ embstr编码是专门用于保存短字符串得一种优化编码方式, 这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象, 但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构, 而embstr编码则通过调用一次内存分配函数来分配一块连续得空间, 空间中依次包含redisObject和sdshdr结构,如下图所示:

redisObject sdshdr
type encoding ptr ... free len buf

​ embstr编码得字符串对象在执行命令时,产生得效果和raw编码得字符串对象执行命令时产生得效果是相同得,但相同embstr编码的字符串对象来保存短字符串值有以下好处。

  • embstr编码将创建字符串对象所需的内存次数从raw编码的两次将为一次

  • 是否embstr编码的字符串对象只需要调用一次内存释放函数, 而释放raw编码的字符串对象需要调用两次内存释放函数

  • 因为embstr编码的字符串对象的所以数据都保存在一块连续的内存里, 所以这种编码的字符串对象比起raw编码的字符串对象能更好地利用缓存带来地优势

    ​ 以下命令创建了一个embstr编码地字符串对象作为msg键地值, 值对象的样子如下图所示:

    127.0.0.1:6379> set msg 'hello'
    OK
    127.0.0.1:6379> object encoding msg
    "embstr"
    
redisObject sdshdr
type REDIS_STRING encoding REDIS_ENCODING_EMBSTR ptr ... free 0 len 5 buf
'h' 'e' 'l' 'l' 'o' '\0'

​ 最后要说的是,可以用long double类型表示的浮点数在Redis中也是作为字符串值来保存的。如果我们要保存一个浮点数到字符串对象里, 那么程序先将这个浮点数转换成字符串值,然后保存转换所得的字符串值。

​ 举个例子,执行以下代码将创建一个包含3.14的字符串表示"3.14"的字符串对象:

127.0.0.1:6379> set pi 3.14
OK
127.0.0.1:6379> object encoding pi
"embstr"

​ 在有需要的时候,程序会将保存在字符串对象里面的字符串值转换回浮点数值, 执行某些操作, 然后在执行操作所得的浮点数值转换回字符串值,并继续保存在字符串对象里面。

​ 举个例子, 如果我们执行以下代码:

127.0.0.1:6379> incrbyfloat pi 2.0
"5.14"
127.0.0.1:6379> object encoding pi
"embstr"

​ 程序首先会去除字符串对象里保存的字符串值"3.14",将它转换回浮点数值3.14,然后把3.14和2.0相加得到值5.14转换为字符串"5.14",并将这个"5.14"保存到字符串对象里。

​ 下图列出了字符串对象保存各种不同类型的值所使用的编码方式。

编码
可以用long类型保存的整数 int
可以用long_double类型保存的浮点数 embstr或raw
字符串值,或者因为长度太大而没办法用long类型表示的整数,又或者因为长度太大而没办法使用long double类型表示的浮点数 embstr或raw

编码的转换

​ int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。

​ 对于int编码的字符串对象来说, 如果我们向对象执行了一些命令,使得这个对象保存的不再是整数值, 而是一个字符串值,那么字符串对象的编码将从int变为raw。

​ 下面的示例中, 通过append指令, 向一个保存整数值的字符串对象追加了一个字符串值, 那么追加操作只能对字符串值执行,所以程序会先将之前保存的整数值10086转换为字符串值"10086",然后再继续追加操作, 操作的执行结果就是raw编码的、保存了字符串的对象。

127.0.0.1:6379> set number 10086
OK
127.0.0.1:6379> object encoding number
"int"
127.0.0.1:6379> append number ' is a good number!'
(integer) 23
127.0.0.1:6379> get number
"10086 is a good number!"
127.0.0.1:6379> object encoding number
"raw"

​ 另外,因为Redis没有为embstr编码的字符串对象编写任何相对应的修改程序(只有int编码的字符串对象和raw编码的字符串的对象有这些程序),所以embstr编码的字符串对象实际上是只读的。当我们对embstr编码的字符串执行任何修改命令时, 程序会先将对象的编码从embstr转换为raw,然后再执行修改命令。因为这个原因, embstr编码的字符串对象再执行修改命令后,总会变成一个raw编码的字符串对象。

​ 展示一个embstr编码的字符串对象再执行append命令后,对象的编码从embstr变为raw的例子:

127.0.0.1:6379> set msg 'hello world'
OK
127.0.0.1:6379> object encoding msg
"embstr"
127.0.0.1:6379> append msg ' again!'
(integer) 18
127.0.0.1:6379> object encoding msg
"raw"

posted @ 2020-10-12 17:12  phper-liunian  阅读(99)  评论(0编辑  收藏  举报