标签:在服务器 跳跃表 删除 set 效率 lru ring car 分配
对象
前面我们介绍了Redis的主要数据结构,如:简单动态字符串SDS、双端链表、字典、压缩列表、整数集合等。Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们之前介绍的数据结构
通过这五种不同类型的对象,Redis可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处是,我们可以根据不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率
除此之外,Redis的对象系统还实现了基于引用计数的内存回收机制,当程序不再使用某个对象时,这个对象所占用的内存就会被自动释放;另外,Redis还通过引用计数实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享一个对象来节约内存
最后,Redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时间,在服务器启用了maxmemory功能的情况下,空转时间较大的那些键可能会优先被服务器删除
对象的类型编码
Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值 对象)
举个栗子,以下SET命令在数据库将创建一个新的键值对,其中键值对的键是一个包含了字符串"msg"的对象,而键值对的值则是一个包含了字符串"hello world"的对象
127.0.0.1:6379> SET msg "hello world" OK
Redis中的每个对象都由一个redisObject结构表示,该结构中保存数据相关的三个属性分别是:type、encoding、ptr
redis.h
typedef struct redisObject { //类型 unsigned type:4; unsigned notused:2; //编码 unsigned encoding:4; unsigned lru:22; //引用计数 int refcount; //指向底层实现数据结构的指针 void *ptr; } robj;
类型
对象的type属性记录了对象的类型,这个属性的值可以是表1-1列出对的常量中的一个
类型常量 | 对象的名称 |
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 Tome age 25 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
表1-2列出了TYPE命令在面对不同类型的值对象时所产生的输出
对象 | 对象type属性的值 | TYPE命令的输出 |
字符串对象 | REDIS_STRING | string |
列表对象 | REDIS_LIST | list |
哈希对象 | REDIS_HASH | hash |
集合对象 | REDIS_SET | set |
有序集合对象 | REDIS_ZSET | zset |
编码和底层实现
对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。encoding属性记录了对象使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,这个属性的值可以是表1-3列出的常量的其中一个
编码常量 | 编码所对应的底层数据结构 |
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 | 跳跃表和字典 |
每种类型的对象都至少使用了两种不同的编码,表1-4列出了每种类型的对象可以使用的编码
类型 | 编码 | 对象 |
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命令可以查看一个数据库键的值对象的编码:
127.0.0.1:6379> SET msg "hello wrold" OK 127.0.0.1:6379> OBJECT ENCODING msg "embstr" 127.0.0.1:6379> SADD numbers 1 3 5 (integer) 3 127.0.0.1:6379> OBJECT ENCODING numbers "intset" 127.0.0.1:6379> SADD numbers "seven" (integer) 1 127.0.0.1:6379> OBJECT ENCODING numbers "hashtable"
表1-5列出了不同编码的对象所对应的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使用压缩列表作为列表对象的底层实现:
其他类型的对象也会通过使用多种不同的编码来进行类似的优化,在接下来的内容中,我们将分别介绍Redis中的五种不同类型的对象,说明这些对象底层所使用的编码方式,列出对象从一种编码转换成另一种编码所需的条件,以及同一个命令在多种不同编码上的实现方法
字符串对象
字符串对象的编码可以是int、raw或者embstr。如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串结构的ptr属性中(将void *转换成long),并将字符串对象的编码设置为int
举个栗子,如果我们执行以下SET命令,那么服务器将创建一个如图1-1所示的int编码的字符串对象作为number键的值:
127.0.0.1:6379> SET number 10086 OK 127.0.0.1:6379> OBJECT ENCODING number "int"
图1-1 int编码的字符串对象
如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw。举个栗子,如果我们执行以下命令,那么服务器将创建一个如图1-2所示的raw编码的字符串作为store键的值
127.0.0.1:6379> SET story "Long, long, long, long, long ago there lived a king ..." OK 127.0.0.1:6379> STRLEN story (integer) 55 127.0.0.1:6379> OBJECT ENCODING story "raw"
图1-2 raw编码的字符串对象
如果字符串对象保存的是一个字符串,并且这个字符串长度小于等于44字节,那么字符串将使用embstr编码,看下面的示例
127.0.0.1:6379> SET story "Long, long, long, long, long ago there lived" OK 127.0.0.1:6379> STRLEN story (integer) 44 127.0.0.1:6379> OBJECT ENCODING story "embstr" 127.0.0.1:6379> SET story "Long, long, long, long, long ago there lived " OK 127.0.0.1:6379> STRLEN story (integer) 45 127.0.0.1:6379> OBJECT ENCODING story "raw"
embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码方式和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr结构,如图1-3所示
图1-3 embstr编码创建的内存块结构
embstr编码的字符串对象在执行命令时,产生的效果和raw编码的字符串对象执行命令时产生的效果是相同的,但使用embstr编码的字符串来保存短字符串值有以下好处:
作为例子,以下命令创建一个embstr编码的字符串对象作为msg键的值,值对象的样子如图1-4所示
127.0.0.1:6379> SET msg "hello world" OK 127.0.0.1:6379> OBJECT ENCODING msg "embstr"
图1-4 embstr编码的字符串对象
最后要说的是,可以用long double类型表示的浮点数在Redis中也是作为字符串值来保存的。如果我们要保存一个浮点数到字符串对象里面,那么程序先将这个浮点数转换成字符串值,然后将其保存。举个栗子,执行以下代码将创建一个包含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> SET pi 3.14 OK 127.0.0.1:6379> INCRBYFLOAT pi 3.0 "6.14" 127.0.0.1:6379> OBJECT ENCODING pi "embstr
程序首先会取出字符串对象中保存的字符串值"3.14",将它转换回浮点数值3.14,然后把3.14和2.0相加得到5.14后在转换回字符串,并将字符串"5.14"保存到字符串对象中。表1-6总结并列出字符串对象保存各种不同类型的值所使用的编码方式
值 | 编码 |
可以用long类型保存的整数 | int |
可以用long double类型保存的浮点数 | embstr或者raw |
字符串值,或者因为长度太大而没办法用long类型表示的整数,又或者因为长度太大而没办法用long double类型表示的浮点数 | embstr或者raw |
编码的转换
int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。对于int编码的字符串对象来说,如果我们向对象执行了一些命令,使得对象保存的不再是整数值,而是一个字符串值,那么字符串对象将从int变为raw
下面的示例中,我们通过APPEND命令,向一个保存整数值的字符串追加一个字符串值,因为追加操作只能对字符串值执行,所以程序会将之前保存的整数值转换为字符串,然后再执行追加操作,操作的执行结果就是一个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"
字符串命令的实现
因为字符串键的值为字符串对象,所以用于字符串键的所有命令都是针对字符串对象来构建的,表1-7例举了其中一部分字符串命令,以及这些命令在不同编码的字符串对象下的实现方法
命令 | int编码的实现方法 | embstr编码的实现方法 | raw编码的实现方法 |
SET | 使用int编码保存值 | 使用embstr编码保存值 | 使用raw编码保存值 |
GET | 拷贝对象所保存的整数值,将这个拷贝转换成字符串值,然后向客户端返回这个字符串值 | 直接向客户端返回字符串值 | 直接向客户端返回字符串值 |
APPEND | 将对象转换成raw编码,然后按raw编码的方式执行此操作 | 将对象转换成raw编码,然后按raw编码的方式执行此操作 | 调用sdscatlen函数,将给定字符串追加到现有字符串的末尾 |
INCRBYFLOAT |
取出整数值并将其转换成long double类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来 |
取出字符串值并尝试将其转换成long double类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。 如果字符串值不能被转换成浮点数,那么向客户端返回一个错误 |
取出字符串值并尝试将其转换成long double类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。 如果字符串值不能被转换成浮点数,那么向客户端返回一个错误 |
INCRBY | 对整数值进行加法计算,得出的计算结果会作为整数被保存起来 | embstr编码不能执行此命令,向客户端返回一个错误 | raw编码不能执行此命令,向客户端返回一个错误 |
DECRBY | 对整数值进行减法计算,得出的计算结果会作为整数被保存起来 | embstr编码不能执行此命令,向客户端返回一个错误 | raw编码不能执行此命令,向客户端返回一个错误 |
STRLEN | 拷贝对象所保存的整数值,将这个拷贝转换成字符串值,计算并返回这个字符串值的长度 | 调用sdslen函数,返回字符串的长度 | 调用sdslen函数,返回字符串的长度 |
SETRANGE | 将对象转换成raw编码,然后按raw编码的方式执行此命令 | 将对象转换成raw编码,然后按raw编码的方式执行此命令 | 将字符串特定索引上的值设置为给定的字符 |
GETRANGE | 拷贝对象所保存的整数值,将这个拷贝转换成字符串值,然后取出并返回字符串指定索引上的字符 | 直接取出并返回字符串指定索引上的字符 | 直接取出并返回字符串指定索引上的字符 |
标签:在服务器 跳跃表 删除 set 效率 lru ring car 分配
原文地址:https://www.cnblogs.com/beiluowuzheng/p/9736269.html