本章内容
Redis对象
Redis使用对象来表示数据库中的键和值。在数据库中创建一个键值对,至少需要创建两个对象:1)键对象;2)值对象。每个对象通过redisObject结构来表示。
redisObject结构:
typedef struct redisObject{
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 指向底层数据结构的指针
void *ptr;
// 引用计数
int refcount;
// 记录最后一次被访问的时间
unsigned lru:22;
} robj;
其中:
- type:对象类型。
- encoding:对象编码。
- ptr:指向底层数据结构的指针。
- refcount:引用计数。
- lru:最后一次被访问的时间。
Redis对象属性
对象类型
对象类型(type属性)查看命令:
type key
对象类型种类:
对象编码与ptr指针
对象编码(encoding属性)查看命令:
object encoding key
对象编码用来指定数据结构,而ptr指针用来指向底层数据结构。
对象编码与数据结构对应关系:
在Redis对象中,每种对象类型至少对应两种不同的编码:
引用计数
1)内存回收
Redis对象中的refcount属性(引用计数)用于实现内存回收。
Redis采用引用计数法实现内存回收:
- 1)创建一个新对象,refcount属性值初始化为1。
- 2)对象被一个程序使用,refcount属性值加1。
- 3)对象不再被一个程序使用,refcount属性值减1。
- 4)当对象的引用计数值变为0时,对象所占用的内存就会被释放。
2)内存共享
Redis对象中的refcount属性除了能实现内存回收外,还可以用于内存共享。
如:执行命令set k1 100,创建一个key为k1、值为100的字符串对象,再执行命令set k2 100。
Redis的处理方式:
- 1)将k2的值指针指向一个已经存在的值对象(k1的值:100);
- 2)将被共享的值对象(redisObject)的refcount属性值+1。
注意:Redis的共享对象目前只支持整数值的字符串对象。
最后一次被访问时间
1)计算对象空转时长
Redis对象中的lru属性记录了对象最后一次被命令程序访问的时间。
对象空转时长查看命令:
127.0.0.1:6379> object idletime prices
(integer) 2863
对象空转时长=当前时间-lru属性记录的时间。
2)配合内存回收
Redis对象中的lru属性除了计算对象空转时长以外,还可以配合内存回收。
当Redis开启maxmemory选项,且内存回收算法为volatile-lru或allkeys—lru时,如果内存占用超过maxmemory参数设定的值,那么Redis会优先释放空转时间最长的对象。
字符串对象
字符串是Redis中最基本的数据类型,所有的key以及其他几种数据类型构成的元素都是字符串。字符串的长度不能超过512M。
字符串对象对应的编码有三种:
- int:保存long类型的整数值。
- embstr:保存长度小于44个字节的字符串。
- raw:保存长度大于44个字节的字符串。
embstr和raw的区别:
- 对象编码为embstr时,只需为对象分配一次内存空间(redisObject和sds连续存储)。
- 对象编码为raw时,需要为对象分配两次内存空间(redisObject和sds各分配一次内存空间)。
如图所示:
embstr编码的优点:
- 创建时少分配一次内存空间,删除时少释放一次内存空间。
- 对象的所有数据连在一起,寻找方便。
embstr编码的缺点:
- 当字符串长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配内存。因此,Redis中embstr为只读。
编码转换:
- 1)使用int编码时,若保存的值不再是整数或者整数大小超过long的范围,Redis会自动将int编码转化为raw编码。
- 2)使用embstr编码时,由于Redis中embstr为只读,若修改embstr编码对应的对象,Redis会先将embstr编码转化为raw编码,再进行修改。因此,只要修改embstr编码对应的对象,无论长度是否达到44个字节,修改后对象的编码都会变成raw。
列表对象
列表对象是一个按顺序排序、可以从头部或尾部添加元素的列表,它的底层是一个链表结构。
列表对象对应的编码有两种:
- ziplist:压缩列表。
- linkedlist:双端链表。
例如:创建一个key为java,value为hello world的列表。执行命令:
127.0.0.1:6379> rpush java hello world
(integer) 2
127.0.0.1:6379> lpop java 2
1) "hello"
2) "world"
如图所示:
当列表对象同时满足以下两个条件时,使用ziplist编码,否则使用linkedlist编码:
- 1)列表中每个元素长度小于64字节。
- 2)列表中保存元素个数小于512。
以上两个条件通过设置redis.conf配置文件中list-max-ziplist-value和list-max-ziplist-entries参数值进行配置。
哈希对象
哈希对象是一个键值对集合,键是一个字符串,值是一个健值对集合。
哈希对象对应的编码有两种:
- ziplist:压缩列表。
- hashtable:字典。
例如:创建一个key为java,value为name Json,age 27的哈希表。执行命令:
127.0.0.1:6379> hset java name Json
(integer) 1
127.0.0.1:6379> hset java age 27
(integer) 1
127.0.0.1:6379> hget java name
"Json"
127.0.0.1:6379> hget java age
"27"
如图所示:
当哈希对象同时满足以下两个条件时,使用ziplist编码,否则使用hashtable编码:
- 1)哈希表中每个元素长度小于64字节。
- 2)哈希表中保存元素个数小于512。
以上第二个条件通过设置redis.conf配置文件中set-max-intset-entries参数值进行配置。
集合对象
集合对象是一个字符串类型的无序集合。
集合与列表的区别:
- 集合中的元素无序,不能通过索引来操作元素;列表中的元素有序,可以通过索引操作元素。
- 集合中的元素不能有重复;列表中的元素可重复。
集合对象对应的编码有两种:
- intset:整数集合。
- hashtable:字典。
例如:创建一个key为people,value为Json 27 male的无序集合。执行命令:
127.0.0.1:6379> sadd nums 1 2 3
(integer) 3
127.0.0.1:6379> smembers nums
1) "1"
2) "2"
3) "3"
127.0.0.1:6379>
如图所示:
例如:创建一个key为people,value为Json 27 male的无序集合。执行命令:
127.0.0.1:6379> sadd people Json 27 male
(integer) 3
127.0.0.1:6379> smembers people
1) "27"
2) "male"
3) "Json"
127.0.0.1:6379>
如图所示:
当集合对象同时满足以下两个条件时,使用intset编码,否则使用hashtable编码:
- 1)集合中所有元素都是整数。
- 2)集合中所有元素数量小于512。
以上第二个条件通过设置redis.conf配置文件中set-max-intset-entries参数值进行配置。
有序集合对象
有序集合对象是一个字符串类型且有序的集合,每个元素关联一个分数(score),其中元素不允许重复,但分数可以重复。
有序集合对象对应的编码有两种:
- ziplist:压缩列表。
- skiplist:zset结构。
zset结构:
typedef struct zset{
//跳跃表
zskiplist *zsl;
//字典
dict *dice;
} zset;
其中:
- 跳跃表节点的object属性保存元素,跳跃表节点的score属性保存元素关联的的分数。
- 字典的键保存元素,字典的值保存元素关联的分数。
这两种数据结构通过指针来共享相同元素和分数,因此,不会产生重复元素。
有序集合单独使用跳跃表或字典实现,同时使用跳跃表和字典的原因:
- 如果单独使用字典,虽然能以O(1)的时间复杂度查找元素关联的分数,但是字典是以无序的方式来保存元素,每次进行范围操作时都需要进行排序。
- 如果单独使用跳跃表,虽然能执行范围操作,但是查找元素关联的分数时复杂度由O(1)变成了O(logN)。
例如:创建一个key为price,value为6.5 apple,5.0 banana的有序集合。执行命令:
127.0.0.1:6379> zadd prices 6.5 apple 5.0 banana
(integer) 2
127.0.0.1:6379> zrangebyscore prices 5.0 7.0
1) "banana"
2) "apple"
如图所示:
注意:有序集合中的元素会按照元素关联的分数从小到大进行存储。
当有序集合对象同时满足以下两个条件时,使用ziplist编码,否则使用skiplist编码:
- 1)有序集合中每个元素长度小于64字节。
- 2)有序集合中所有元素数量小于128。
以上两个条件通过设置redis.conf配置文件中zset-max-ziplist-entries和zset-max-ziplist-value参数值进行配置。