您的位置:首页 > 理论基础 > 数据结构算法

【Redis设计与实现】 读书笔记 数据结构与对象 对象

2018-08-25 11:30 609 查看

对象

简介

  • 在之前的笔记中,陆续学习了Redis用到的所有主要数据结构,其中省略了一下,大多是基本的数据结构。Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序结合对象这五种类型的对象,每种对象都用到了之前学过的数据结构。
  • 使用对象的好处是,Redis可以再执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。另一个好处是,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的效率。
  • 除此之外,Redis的对象系统还实现了基于引用计数技术的内存回收机制,当程序不在使用某个对象的时候,这个对象锁占用的内存就会被自动释放;另外,Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。
  • 最后,Redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启动了maxmemory功能的情况下,空转时长较大的那些键可能会优先被服务器删除。

对象的类型与编码

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

redis > SET msg "hello world”
其中键值对的键是一个包含了字符串值”msg”的对象,而键值对的值则是一个包含了字符串值”hello world”的对象。
Redis对象的结构:

typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 指向底层实现数据结构的指针
void *ptr;
// 引用计数
int refcount;
//最近义词被程序访问的时间
unsigned lru:22;
  • 类型
类型常量 对象的名称
REDIS_STRING 字符串对象
REDIS_LIST 列表对象
REDIS_HASH 哈希对象
REDIS_SET 集合对象
REDIS_ZSET 有序结合对象

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

编码常量 编码所对应的底层数据结构
REDIS_ENCODING_INT long类型的整数
REDIS_ENCODING_EMBSTR 列embstr编码的简单动态字符串
REDIS_ENCODING_RAW 简单动态字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 双端链表
REDIS_ENCODING_ZIPLIST 压缩列表
REDIS_ENCODING_INTSET 整数集合
REDIS_ENCODING_SKIPLIST 跳跃表和字典

每个类型的对象都至少使用了两种不同的编码。

类型 编码 对象
REDIS_STRING REDIS_ENCODING_INT 使用整数值实现的字符串对象
REDIS_STRING REDIS_ENCODING_EMBSTR 使用embstr编码的简单动态字符串实现的字符串对象
REDIS_STRING REDIS_ENCODING_RAW 使用简单动态字符串实现的列表对象
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用双端链表列表实现的列表对象
REDIS_HASH REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象
REDIS_HASH REDIS_ENCODING_HT 使用字典实现的哈希对象
REDIS_SET REDIS_ENCODING_INTSET 使用整数结合实现的结合对象
REDIS_SET REDIS_ENCODING_HT 使用字典实现的集合对象
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用压缩列表实现的有序集合对象
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳跃表和字典实现的有序集合对象

字符串对象

如果一个字符串的长度小于等于39字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。反之则使用raw对象。
embstr编码的有点:

  • embstr编码将创建字符串对象锁需要的内存分配次数从raw编码的两次降低为一次。
  • 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数。
  • 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面所以这种编码的字符串对象比起raw编码的字符串对象能够更好地利用缓存带来的优势。

列表对象

同时满足以下两个条件时,列表对象使用ziplist编码:

  • 列表对象保存的所有字符串元素的长度都小于64字节;
  • 列表对象保存的元素数量小于512个;不能满足这两个条件的列表对象需要使用linkedlist编码。

哈希对象

同时满足以下两个条件时,哈希对象使用ziplist编码(两个条件的上限值是可以修改的具体看hash-max-ziplist-value和hash-max-ziplist-entries选项的说明):

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
  • 哈希对象保存的键值对数量小于512个;不能满足这两个条件的哈希对象需要使用hashtable编码。

集合对象

同时满足以下两个条件是,对象使用intset编码(第二个条件的上线值是可以修改的,查看set-max-intset-entries选项的说明):

  • 集合对象保存的所有元素都是整数值;
  • 集合对象保存的元素数量不超过512个;

对象共享

对象的引用计数属性除了实现内存回收机制之外还有对象共享的作用。
假设键A创建了一个包含整数值100的字符串对象作为值对象,如果这时键B创建一个同样保存了整数值100的字符串对象作为值对象,那么服务器有一下两种做法:

  • 为键B新创建一个包含整数值100的字符串对象;
  • 让键A和键B共享同一个字符串对象;
    以上两种方法很明显,第二种方法更节约内存。
    在Redis中,让多个键共享同一个值对象需要执行一下两个步骤:

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

  • 将被共享的值对象的引用计数增一;

延伸阅读 《为什么Redis不共享包含字符串的对象?》
当服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的对象的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象,而一个共享对象保存的值越复杂,验证共享对象单核目标对象是否相同所需的时间复杂度就会越高,消耗的CPU时间也会越多:

  • 如果共享对象是保存整数值的字符串对象,那么验证操作的复杂度为O(1);
  • 如果共享对象是保存字符串值的字符串对象,那么验证操作的复杂度为O(N);
  • 如果共享对象是包含了多个值(或者对象)的对象,比如列表对象或者哈希对象,那么验证操作的复杂度将会是O(N^2)。

因此,尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值的字符串对象进行共享。

总结

Redis数据库中的每个键值对的键和值都是一个对象。
Redis公有字符串、列表、哈希、集合、有序集合五种类型的对象,每种类型的对象至少都有两种或以上的编码方式,不同的编码可以再不同的使用场景上优化对象的使用效率。
服务器在执行某些命令之前,会先检查给定键的类型能否执行制定的命令,而检查一个键的类型就是检查键的值对象的类型。
Redis的对象系统带有引用计数实现的内存回收机制,当一个对象不再被使用时,改对象所占用的内存就会被自动释放。
Redis会共享值为0到9999的字符串对象。
对象会记录自己的最后一次被访问的时间,这个时间可以用于计算对象的空转时间,还可以用于回收内存用。

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: