深入浅出Redis之对象

  1. 对象

  1. 对象的类型与编码

Redis使用对象来表示数据库中的键和值,每次当我们在Redis 的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)

类型

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

  • 当我们称呼一个数据库键为“字符串键”时,我们指的是“这个数据库键所对应的值为字符串对象”;

  • 当我们称呼一个键为“列表键”时,我们指的是“这个数据库键所对应的值为列表对象”

编码和底层实现

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

encoding属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,这个属性的值可以是表8-3列出的常量的其中一个。

每种类型的对象都至少使用了两种不同的编码,表8-4列出了每种类型的对象可以使用的编码。

  1. 字符串对象P81

字符串对象的编码可以是int、raw或者embstr

如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long ),并将字符串对象的编码设置为int。

  1. 列表对象P85

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

ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点(entry)保存了一个列表元素。

  1. 哈希对象P90

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

ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾,因此:

  • 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;

  • 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向

  1. 集合对象P92

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

intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。

hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL。

  1. 有序集合对象P94

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

ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member ),而第二个元素则保存元素的分值( score )。

压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向。

  1. 类型检查与命令多态

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

其中一种命令可以对任何类型的键执行,比如说DEL命令、EXPIRE命令、RENAME命令、TYPE命令、OBJECT命令等。

另一种命令只能对特定类型的键执行,比如说:

  • SET、GET、APPEND、STRLEN等命令只能对字符串键执行;

  • HDEL、HSET、HGET、HLEN等命令只能对哈希键执行;

  • RPUSH、LPOP、LINSERT、LLEN等命令只能对列表键执行;

  • SADD、SPOP、SINTER、SCARD等命令只能对集合键执行;

  • ZADD、ZCARD、ZRANK、ZSCORE等命令只能对有序集合键执行;

类型检查的实现

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

  • 在执行一个类型特定命令之前,服务器会先检查输人数据库键的值对象是否为执行命令所需的类型,如果是的话,服务器就对键执行指定的命令;

  • 否则,服务器将拒绝执行命令,并向客户端返回一个类型错误。举个例子.对于LLEN命令来说:

  • 在执行LLEN命令之前,服务器会先检查输入数据库键的值对象是否为列表类型,也即是,检查值对象redisobject结构type属性的值是否为REDIS_LIST,如果是的话,服务器就对键执行LLEN命令;

  • 否则的话,服务器就拒绝执行命令并向客户端返回一个类型错误;图8-18展示了这一类型检查过程。

多态命令的实现

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

现在,考虑这样一个情况,如果我们对一个键执行LLEN命令,那么服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的LLEN命令实现:

  • 如果列表对象的编码为ziplist,那么说明列表对象的实现为压缩列表,程序将使用ziplistLen函数来返回列表的长度;

  • 如果列表对象的编码为linkedlist,那么说明列表对象的实现为双端链表,程序将使用listLength函数来返回双端链表的长度;

  1. 内存回收

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

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

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

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

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

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

  1. 对象共享

除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有对象共享的作用。举个例子,假设键A创建了一个包含整数值100的字符串对象作为值对象,如图8-20所示。

如果这时键B也要创建一个同样保存了整数值100 的字符串对象作为值对象,那么服务器有以下两种做法:

  1. 为键B新创建一个包含整数值100的字符串对象;

2)让键A和键B共享同一个字符串对象;

以上两种方法很明显是第二种方法更节约内存。

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

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

2)将被共享的值对象的引用计数增一。

对象的空转时长

除了type、encoding、ptr和refcount 四个属性之外,redis0bject结构包含的最后一个属性为lru属性,该属性记录了对象最后一次被命令程序访问的时间。

OBJECT IDLETIME命令可以打印出给定键的空转时长,这一空转时长就是通过将当前时间减去键的值对象的lru时间计算得出的:

键的空转时长另外一项作用:如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存

11.重点

  • Redis数据库中的每个键值对的键和值都是一个对象。

  • Redis 共有字符串、列表、哈希、集合、有序集合五种类型的对象,每种类型的对象至少都有两种或以上的编码方式,不同的编码可以在不同的使用场景上优化对象的使用效率。

  • 服务器在执行某些命令之前,会先检查给定键的类型能否执行指定的命令,而检查一个键的类型就是检查键的值对象的类型。

  • Redis 的对象系统带有引用计数实现的内存回收机制,当一个对象不再被使用时,该对象所占用的内存就会被自动释放。

  • Redis会共享值为0到9999的字符串对象。

  • 对象会记录自己的最后一次被访问的时间,这个时间可以用于计算对象的空转时间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值