Redis对象

前言

Redis系列博客为对黄健宏老师《Redis设计与实现》一书内容的整理

第八章 对象

Redis基于数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都至少使用了一种前面提到的数据结构。

使用对象的一个好处是:我们可以针对不同的使用场景为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

除此之外,Redis的对象系统还实现了基于引用计数计数的内存回收机制。当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外,Redis还通过引用计数计数实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。

最后,Redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长

对象的类型和编码

Redis中的每个对象都由一个redisObject结构来表示,该结构中和保存数据有关的三个属性分别是type属性、encoding属性和ptr属性。

一个类型即是一种对象,而一种对象可能有多种不同的数据结构实现方式,即不同的编码方式。

1:n

类型

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

编码和底层实现

对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。

encoding属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,这个属性的值可以是下列中任意一个:

编码对应的底层数据结构
long类型的整数
embstr编码的简单动态字符串
简单动态字符串
字典
双端链表
压缩列表
整数集合
跳跃表和字典

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

有序集合对象

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

在理论上来说, 有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现, 但无论单独使用字典还是跳跃表, 在性能上对比起同时使用字典和跳跃表都会有所降低。

举个例子, 如果我们只使用字典来实现有序集合, 那么虽然以 O(1) 复杂度查找成员的分值这一特性会被保留, 但是, 因为字典以无序的方式来保存集合元素, 所以每次在执行范围型操作 —— 比如 ZRANK 、 ZRANGE 等命令时, 程序都需要对字典保存的所有元素进行排序, 完成这种排序需要至少 O(N log N) 时间复杂度, 以及额外的 O(N) 内存空间 (因为要创建一个数组来保存排序后的元素)。

另一方面, 如果我们只使用跳跃表来实现有序集合, 那么跳跃表执行范围型操作的所有优点都会被保留, 但因为没有了字典, 所以根据成员查找分值这一操作的复杂度将从 O(1) 上升为 O(log N) 。

因为以上原因, 为了让有序集合的查找和范围型操作都尽可能快地执行, Redis 选择了同时使用字典和跳跃表两种数据结构来实现有序集合

类型检查与命令多态 

Redis中用于操作键的命令基本上可以分为两种类型。

其中一种命令可以对任何类型的键执行,比如说del命令、expire命令、rename命令、type命令等。

另一种命令只能对待特定类型的键执行,比如说set只能对字符串键执行。

类型检查的实现

类型特定命令所进行的类型检查是通过redisObject结构的type属性来实现的。

多态命令的实现

Redis除了会根据值对象的类型来判断键是否能够执行指定指令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。

一个命令可以处理多种不同的编码。

内存回收

因为C语言并不具备自动内存回收的功能,所以Redis在自己的对象系统中构建了一个引用计数(reference)技术实现的内存回收机制。通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。

对象的引用计数信息会随着对象的使用状态而不断变化:

* 在创建一个新对象时,引用计数的值会被初始化为1

* 当对象被一个新程序使用时,它的引用计数值会被增1

* 当对象不再被一个程序使用时,它的引用计数值会被减一

* 当对象的引用计数值变为0时,对象所占用的内存会被释放

对象的整个生命周期可以分为创建对象操作对象释放对象三个阶段。

对象共享

除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有对象共享的作用。举个例子,假设A创建了一个包含整数值100的字符串对象作为值对象,如果B也要创建一个整数值100的字符串对象,则可以让键A和键B共享同一个字符串对象,这样可以更省内存

在Redis中,让多个键共享同一个值对象需要执行以下两个步骤:

1、将数据库键的指针指向一个现有的值对象

2、将被共享的值对象的引用计数加1

共享对象机制对于节约内存非常有帮助,数据库中保存的相同值对象越多,对象共享机制就能节约越多的内存。

Redis会在初始化服务器的时候创建整数值0-9999的字符串对象,以便后续共享对象。

为什么Redis不共享包含字符串的对象?

答:共享对象保存的值越复杂,严重共享目标和目标对象是否一致的时间复杂度越高,消耗的CPU时间越高,因此取舍之后选择放弃。

对象的空转时长

除了type、encoding、ptr和refcount外,redisObject还包括lru属性,该属性记录了对象最后一次被命令程序访问的时间。

通过将当前时间减去键对象的lru时间就能计算出给定键的空转时间。

如果设置了maxmemory和一定的内存分配策略,当内存达到上限时,空转时长较高的那部分键会优先被服务器释放,从而回收内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值