| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> Redis3-底层数据结构:对象机制 -> 正文阅读 |
|
[大数据]Redis3-底层数据结构:对象机制 |
底层数据结构:Redis作为Key-Value存储系统,结构如下: RedisDB结构Redis中存在“数据库”的概念,该结构由redis.h中的redisDb定义。 当redis 服务器初始化时,会预先分配 16 个数据库; 所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中 redisClient中存在一个名叫db的指针指向当前使用的数据库 RedisDB结构体源码:
RedisObject结构(重点)在前文已经阐述了Redis 5种基础数据类型和Stream;那么这些基础类型的底层是如何实现的呢? Redis的每种对象其实都由对象结构(redisObject)?与?对应编码的数据结构组合而成, 而每种对象类型对应若干编码方式,不同的编码方式所对应的底层数据结构是不同的。 redisObject结构信息概览
redisObject # type属性对象类型 六种对象类型 REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合) REDIS_STREAM 。 当我们执行 type 命令时,便是通过读取 RedisObject 的 type 字段获得对象的类型 例如:type key 返回 string redisObject # encoding属性编码类型 encoding 表示对象的内部编码,占 4 位,每个对象有不同的实现编码 Redis 可以根据不同的使用场景来为对象设置不同的编码,大大提高了 Redis 的灵活性和效率。 通过 object encoding 命令,可以查看对象采用的编码方式 redisObject # ptr 属性 指向底层实现数据结构的指针ptr是一个指针,指向实际保存值的数据结构,这个数据结构由type和encoding属性决定。 举个例子, 如果一个redisObject 的type 属性为OBJ_LIST , encoding 属性为OBJ_ENCODING_QUICKLIST ,那么这个对象就是一个Redis 列表(List), 它的值保存在一个QuickList的数据结构内,而ptr 指针就指向quicklist的对象; 下图展示了redisObject 、Redis 所有数据类型、Redis 所有编码方式以及底层数据结构之间的关系(从6.0版本中梳理而来): redisObject #refcount属性redisObject中有refcount属性,是对象的引用计数,显然计数0那么就是可以回收;
Redis命令的类型检查和多态那么Redis是如何处理一条命令的呢?
例如执行LPOP命令: 底层数据结构详解简单动态字符串 - sdsRedis 使用了 SDS(Simple Dynamic String)。用于存储字符串和整型数据。具有动态扩容的特点
SDS的优势:1、SDS 在 C 字符串的基础上加入了 free 和 len 字段,获取字符串长度:SDS 是 O(1),C 字符串是O(n)。buf数组的长度=free+len+1 2、 SDS 由于记录了长度,在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。 3、可以存取二进制数据,以字符串长度len来作为结束标识C: \0 空字符串 二进制数据包括空字符串,所以没有办法存取二进制数据 SDS : 一般来说,SDS 除了保存数据库中的字符串值以外,SDS 还可以作为缓冲区(buffer),包括 AOF 模块中的AOF缓冲区以及客户端状态中的输入缓冲区。 跳表 - ZSkipList(重点)跳跃表是有序集合(Zset)的底层实现,效率高,实现简单。 跳跃表的基本思想:将有序链表中的部分节点分层,每一层都是一个有序链表 , 它具有二分查找的功能 跳跃表特点:
对于于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高,是 O(n)。比如查找12,需要7次查找 跳跃表查找 在查找时优先从最高层开始向后查找,当到达某个节点时,如果next节点值大于要查找的值或next指针指向null,则从当前节点下降一层继续向后查找。 如果我们增加如下两级索引,那么它搜索次数就变成了3次
zskiplist的核心设计要点
redis为什么不用平衡树或者哈希表,而是选择跳跃表
skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。 因此,在哈希表上只能做单个key的查找,不适宜做范围查找。 在做范围查找的时候,平衡树比skiplist操作要复杂。 在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。 如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。 从算法实现难度上来比较,skiplist比平衡树要简单得多。 字典/哈希表 - Dict本质上就是哈希表, 这个在很多语言中都有,对于开发人员人员来说比较熟悉 哈希表结构定义:1、hash表的数组初始容量为4,随着k-v存储量的增加需要对hash表数组进行扩容,新扩容量为当前量的一倍,即4,8,16,32 2、索引值=Hash值&掩码值(Hash值与Hash表容量取余)
哈希表是由数组 table 组成,table 中每个元素都是指向 dict.h/dictEntry 结构,dictEntry 结构定义如下: key 用来保存键,val 属性用来保存值,值可以是一个指针,也可以是uint64_t整数,也可以是int64_t整数。
注意这里还有一个指向下一个哈希表节点的指针next,我们知道哈希表最大的问题是存在哈希冲突,如何解决哈希冲突,有开放地址法和链地址法。这里采用的便是链地址法,通过next这个指针可以将多个哈希值相同的键值对连接在一起,用来解决哈希冲突。例如: 扩容和收缩:上面提到哈希表的初始大小为4当哈希表保存的键值对太多或者太少时,就要通过 rehash(重新散列)来对哈希表进行相应的扩展或者收缩。 具体步骤:
相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。
触发扩容的条件:1、服务器目前没有执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于1。 2、服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于5。 ps:负载因子 = 哈希表已保存节点数量 / 哈希表大小。 什么叫渐进式 rehash?也就是说扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的。 如果保存在Redis中的键值对只有几个几十个,那么 rehash 操作可以瞬间完成, 但是如果键值对有几百万,几千万甚至几亿,那么要一次性的进行 rehash,势必会造成Redis一段时间内不能进行别的操作。 所以Redis采用渐进式 rehash,这样在进行渐进式rehash期间,字典的删除查找更新等操作可能会在两个哈希表上进行, 第一个哈希表没有找到,就会去第二个哈希表上进行查找。但是进行 增加操作,一定是在新的哈希表上进行的 压缩列表 - ZipList压缩列表(ziplist)是由一系列特殊编码的连续内存块组成的顺序型数据结构节省内存是一个字节数组,可以包含多个节点(entry)。每个节点可以保存一个字节数组或一个整数。 压缩列表的数据结构如下:
entryX元素的编码结构:
ziplist结构体如下:
应用场景 sorted-set和hash元素个数少且是小整数或短字符串(直接使用) list用快速链表(quicklist)数据结构存储,而快速链表是双向列表与压缩列表的组合。(间接使用) 整数集合 - IntSet整数集合(intset)是一个有序的(整数升序)、存储整数的连续存储结构。
当Redis集合类型的元素都是整数并且都处在64位有符号整数范围内(2^64),使用该结构体存储。 intset的结构图如下: typedef struct intset{ uint32_t encoding; //编码方式 uint32_t length; //集合包含的元素数量 int8_t contents[]; //保存元素的数组 }intset; 应用场景 可以保存类型为int16_t、int32_t 或者int64_t 的整数值,并且保证集合中不会出现重复元素 快表 - QuickList后续补充 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 20:18:46- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |