0%

redis02_对象

redis对象

概述

redis基于简单动态字符串、双端链表等基本数据结构,创建了一个对象系统。
包含字符串对象,列表对象,哈希对象,集合对象,有序集合对象。

Redis的对象系统还实现了基于引用计数技术的内存回收机制

对象的类型和编码

redis对象包含如下属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct redisObj{

//类型
unsigned type;

//编码
unsigned ecoding;

//指向底层实现数据结构的指针
void *ptr;

//lru属性记录了对象最后一次被访问的时间
unsigned lru;
} redisObj;

类型

type命令可以查看对象的类型,对应关系如下:

编码和底层实现

一个对象的底层实现可能有多种方式,比如列表对象底层可以是压缩链表或者双端链表实现,对应关系如下。

object encoding命令可以查看一个数据库键的值对象编码.
OBJECT ENCODING对不同编码的输出

字符串对象

字符串对象的编码可以是:

  • int:字符串对象保存的是整数值,并且这个整数值可以用long类型表示。
  • raw:字符串对象保存的是一个长度大于39字节
  • embstr:字符串对象保存的是一个长度小于等于39字节,此编码是专门用于保存字段字符串的一种优化编码方式。

raw编码会调用两次内存分配函数分别创建redisObj结构和sdshdr结构,而embstr只调用一次内存分配函数来分配一块连续的空间,空间中一次包含redisObj和sdshdr。
使用embstr保存短字符串好处如下:

  1. 减少一次内存分配函数的调用

  2. 减少一次内存释放函数的调用

  3. 所有数据在一块连续的内存里面,能够更好地利用缓存带来的优势

    int编码的字符串对象

    raw编码的字符串对象

    embstr编码的字符串对象

ps:可以用long double类型表示的浮点数在redis中使用字符串值来保存的。(c中的long double类型为128位,范围比double大)

字符串对象保存各类型的编码方式

编码的转换

int编码的字符串对象,当这个对象保存的不在是整数值,而是字符串,编码会变为raw。
embstr编码的字符串对象是只读的,当对它进行任何修改命令的时候,程序会把编码编程raw,再执行修改命令。

字符串对象是唯一一个会被其他对象嵌套的对象

列表对象

列表对象的编码可以是ziplist或者linkedlist。

当列表对象同时满足一下两个条件时,列表对象用ziplist编码

  • 列表对象保存的所有字符串元素的长度都小于等于64字节(阈值可修改)
  • 列表对象保存的元素小于等于512个(阈值可修改)

列表对象3.2版本之后

Redis中的列表对象在版本3.2之前,列表底层的编码是ziplist和linkedlist实现的,但是在版本3.2之后,重新引入了一个 quicklist 的数据结构,列表的底层都由quicklist实现。

哈希对象

哈希对象的编码可以是ziplist或者hashtable。

压缩列表作为哈希对象的底层实现,每当有新的键值对要加入到哈希对象时,程序会将键压入压缩列表表尾,再将值压入压缩列表的表尾。

hashtable编码底层用字典实现。

编码转换

当哈希对象同时满足以下两个条件是,哈希对象使用ziplist编码

  • 所保存的所有键值对的键和值的字符串长度都小于等于64字节
  • 所保存的键值对数量小于等于512个(非键和值的总和小于512)

集合对象

集合对象的编码可以是intset或者hashtable。

编码转换

当集合对象同时满足以下两个条件时,对象使用intset编码(整数集合),否则用hashtable编码。

  • 保存的元组都是真数值
  • 保存元素的数量不超过512个

有序集合对象

有序集合的编码可以是ziplist或者skiplist。

ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个保存元素成员,第二个保存元素的分值,压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的位置。

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset同时包含一个字典和一个跳跃表

程序可以对集合进行范围型操作,ZRANK、ZRANGE等命令是基于跳跃表的api实现的。

字典的键保存了元素成员,值保存了元素的分值,通过这个字典,可以用O(1)的时间复杂度查找给定成员的分支。

为什么有序集合需要同时使用跳跃表和字典实现?

  1. 单独使用跳跃表,当需要根据成员查找分值的时候,需要遍历整个跳跃表,时间复杂度从O(1)上升到O(logN)。
  2. 单独使用字典,则对元素进行排序等操作,至少需要O(NlogN)时间复杂度和额外的O(N)内存空间。

同时使用跳表和字典,则兼容了两种数据结构的优点,并且两种数据结构通过指针共享相同元素的成员和分值不会因此浪费额外的内存

编码转换

当有序集合对象同时满足以下两个条件时,对象使用ziplist编码。

  • 保存的元素数量小于128个
  • 保存元素的成员的长度小于64字节

内存回收

通过引用计数法,适当的时候自动释放对象并进行内存回收。

对象共享

redis4.0 为了支持惰性删除,redis彻底废弃了对象共享机制

Redis在初始化服务器的时候,创建一万个字符串对象,这些字符串对象包含了0-9999的所有整数值,这些对象是会被共享的,不会重复创建。

Redis不共享其他字符串的对象?

一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需要的复杂度就越高,消耗的CPU时间越多。

  • 如果共享对象是保存整数值的字符串对象,验证操作的时间复杂度为O(1)
  • 如果共享对象保存的是字符串值的字符串对象,验证操作的时间复杂度为O(n)
  • 如果是共享对象是复杂对象,例如列表对象,那么验证操作的时间复杂度为O($n^2$)

对象空转时长

lru属性记录了对象最后一次被访问的时间。

OBJECT IDLETIME命令可以打印给定键的空转时长,且不会更新它的lru时间。