Redis对象机制 (Redis Object)

1、Redis基础数据类型

        Redis的key都是字符串,这里所讨论的数据类型都是value。主要常见的数据类型分别是String、List、Set、Zset、Hash。

结构类型

结构存储的值

结构的读写能力

String

可以是字符串、整数或浮点数

对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;

List

一个双端链表,链表上的每个节点都包含一个字符串、整数、浮点数

对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;

Set

包含字符串、整数、浮点数的无序集合

字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等;

Hash

包含键值对的无序散列表

包含方法有添加、获取、删除单个元素;

Zset

和hash一样,用于存储键值对

字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素

1.1 Redis数据结构

Redis底层数据结构如下:

简单动态字符串 - SDS

压缩列表 - ZipList

快表 - QuickList

字典/哈希表 - Dict

整数集 - IntSet

跳表 - ZSkipList

2、 Redis 对象机制

        不同的数据类型是由对象结构(redisObject)与对应编码的数据结构组合而成,每种对象类型对应若干编码方式,不同的编码方式所对应的底层数据结构是不同的。

2.1 RedisObject数据结构

其中type、encoding和ptr是最重要的三个属性。

type记录了对象所保存的值的类型,它的值可能是以下常量中的一个:

encoding记录了对象所保存的值的编码,它的值可能是以下常量中的一个:

2.2 为什么Redis会设计RedisObject对象?

        Redis用于对键进行处理的命令占了很大一部分,不同类型键能执行的命令又各不相同。比如LPUSHLLEN 只能用于列表键, 而 SADDSRANDMEMBER 只能用于集合键; 另外一些命令, 比如 DEL、 TTL 和 TYPE, 可以用于任何类型的键;但是要正确实现这些命令, 必须为不同类型的键设置不同的处理方式,比如说,删除一个列表键和删除一个字符串键的操作过程就不太一样。Redis 必须让每个键都带有类型信息, 使得程序可以检查键的类型。

操作数据类型的命令除了要对键的类型进行检查之外, 还需要根据数据类型的不同编码进行多态处理。

2.3 Redis 构建了自己的类型系统

2.3.1 RedisObject数据结构

  • ptr: 指向实际保存值的数据结构的指针,这个数据结构由type和encoding属性决定。举个例子, 如果一个redisObject 的type 属性为OBJ_LIST , encoding 属性为OBJ_ENCODING_QUICKLIST ,那么这个对象就是一个Redis 列表(List),它的值保存在一个QuickList的数据结构内,而ptr 指针就指向quicklist的对象;
  • lru属性: 记录了对象最后一次被命令程序访问的时间。
  • 空转时长:当前时间减去键的值对象的lru时间,就是该键的空转时长。Object idletime命令可以打印出给定键的空转时长。如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存。

2.4 命令的类型检查和多态

Q: Redis是如何处理一条命令的呢?

A: 当执行一个处理数据类型命令的时候,redis执行以下步骤

  • 根据给定的key,在数据库字典中查找和他相对应的redisObject,如果没找到,就返回NULL;
  • 检查redisObject的type属性和执行命令所需的类型是否相符,如果不相符,返回类型错误;
  • 根据redisObject的encoding属性所指定的编码,选择合适的操作函数来处理底层的数据结构;
  • 返回数据结构的操作结果作为命令的返回值。

2.5 对象共享

        Redis一般会把一些常见的值放到一个共享对象中,这样可使程序避免了重复分配的麻烦,也节约了一些CPU时间。

redis预分配的值对象如下:

  • 各种命令的返回值,比如成功时返回的OK,错误时返回的ERROR,命令入队事务时返回的QUEUE,等等
  • 包括0 在内,小于REDIS_SHARED_INTEGERS的所有整数(REDIS_SHARED_INTEGERS的默认值是10000)

注意:共享对象只能被字典和双向链表这类能带有指针的数据结构使用。像整数集合和压缩列表这些只能保存字符串、整数等内存数据结构。

2.6 引用计数以及对象的消毁

redisObject中有refcount属性,是对象的引用计数,显然计数0那么就是可以回收。

  • 每个redisObject结构都带有一个refcount属性,指示这个对象被引用了多少次;
  • 当新创建一个对象时,它的refcount属性被设置为1;
  • 当对一个对象进行共享时,redis将这个对象的refcount + 1;
  • 当使用完一个对象后,或者消除对一个对象的引用之后,程序将对象的refcount - 1;
  • 当对象的refcount降至0 时,这个RedisObject结构,以及它引用的数据结构的内存都会被释放。

2.7 Redis数据结构

为了理解对象机制,这里简单介绍集中底层数据结构

2.7.1 简单动态字符串 - SDS

SDS对比C字符串的优点:

1、二进制安全;

2、O(1)复杂度获取字符串长度;

3、不会发生缓冲区溢出;

2.7.2 压缩列表

zlbytes: 记录整个压缩列表占用的内存字节数

zltail: 到达 ziplist 表尾节点的偏移量

zllen: 记录压缩列表包含的节点数量

zlend: 标记压缩列表的结束点

prevlen: 记录了「前一个节点」的长度,目的是为了实现从后向前遍历;

encoding: 记录了当前节点实际数据的「类型和长度」,类型主要有两种:字符串和整数。

data: 记录了当前节点的实际数据,类型和长度都由 encoding 决定;

优点:

1 内存紧凑型的数据结构

2 连续的内存空间,利用 CPU 缓存

3 节省内存开销

2.7.2.1 压缩列表的连锁更新

        当压缩列表新增或者修改某个元素时,如果元素较大,可能会导致后续元素的prevlen占用空间都发生变化,从而导致每个元素空间都需要重新分配。

需要注意,这里有2个前提如下:

如果前一个节点的长度小于 254 字节,那么 prevlen 属性需要用 1 字节的空间来保存这个长度值;

如果前一个节点的长度大于等于 254 字节,那么 prevlen 属性需要用 5 字节的空间来保存这个长度值;

        因为 e1 节点的 prevlen 属性只有 1 个字节大小,需要从原来的1字节大小扩展为 5 字节大小。后续节点类似。

因此,压缩列表只会用于保存的节点数量不多的场景,只要节点数量足够小,即使发生连锁更新,也是能接受的。

2.7.3 listpack (取代ziplist)

encoding:定义该元素的编码类型

data:实际存放的数据

len:encoding+data的总长度

保留了ziplist的优点,listpack 只记录当前节点的长度,当我们向 listpack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值