Redis学习总结_1_底层数据结构
来源:互联网 发布:淘宝无人机 编辑:程序博客网 时间:2024/06/02 04:14
最近读了“Redis设计与实现”,对Redis有了简单的认识与理解。类似于大学时候学完了“数据库概述xxx”,只会在客户端进行简单的CRUD。故对Redis的数据结构、对象、单机数据库的实现、以及其他的功能进行记录。多机数据库暂时略过毕竟没有实践就没有发言权~
一 数据结构
1.简单动态字符串
1.1 SDS数据结构
如下图是SDS的数据结构,free表示目前未使用字节数,len表示当前字符串长度且不计算最后的'\0'在内,buf是是字节数组用于保存数据,下图中保存着“Redis”。
图1
1.2 扩容
如果对上述字符串追加内容“ Cluster”,那么它会执行扩容,比如追加前长为5,追加后字符串长为13,那么它会增加8个字节供追加字符串,然后再增加13个空位,下次追加时候如果空闲空间够用就不需要再扩容。
图1执行追加“Cluster”之后如图2示:
图2
图2执行追加一个字符e之后,如图3示:
图3
1.3 惰性空间释放
SD缩短字符串时,程序不会立即进行内存重分配来收回多出来的字节。
比如:如果图3的字符串减掉" Clustere"字符串之后,如图4示:
图4
2.链表
2.1 链表数据结构,head指向头节点,tail指向尾节点,len表示目前链表中有三个节点,dup函数示用于复制链表节点所保存的值,free函数用于释放链表节点保存的值,match函数用于比较链表节点所保存的值和另一个输入值是否相等。其中每个节点都有指向前一个节点跟后一个节点的指针。如图5示:
图5
3.字典
3.1 哈希表数据结构
table是一个哈希表数组,存储哈希键值对节点(字典是使用哈希表作为底层实现的),size表示哈希表大小,sizemark是哈希表大小掩码总等于size-1,used表示该哈希表目前有几个节点,哈希表数据结构如图6
图6
3.2 哈希表节点
哈希表节点使用dictEntry结构表示,保存着一个键值对,dictEntry结构还有一个属性是next,指向下一个哈希表节点以形成链表,从而将多个哈希值相同的键值对连接在一起,解决键冲突的问题。
3.3 字典数据结构
上边介绍了哈希表数据结构,Redis中字典是由哈希表维护着的。数据结构如图7
图7
type属性是执行dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis为不同用途字典设置不同类型特定函数。
privdata属性保存了需要传给那些类型特定函数的可选参数。
dictType各种类型函数见一下定义
typedef struct dictType { // 计算哈希值的函数 unsigned int (*hashFunction) (const void *key); // 复制键的函数 void *(*keyDup) (void *privdata, const void *key); // 复制值的函数 void *(*valDup) (void *privdata, const void *oj); // 对比键的函数 int (*keyCompare) (void *privdata, void *key); // 销毁键的函数 void (*keyDestructor) (void *privdata, const void *key1, const void *key2); // 销毁值的函数 void (*valDestructor) (void *privdata, void *obj);}3.4 哈希算法及解决键冲突
假设新增一个节点key/value,可以通过上边的hashFunction函数计算到key的哈希值,然后跟哈希表结构中的sizemark进行与操作得到索引值,如果该索引值对应的位置中有哈希节点,那么新增节点链接在该索引值对应的哈希节点链表的表头位置
3.5 rehash(重新散列)
当哈希表中哈希节点增加或者减少,负载因子不在设定范围之内时,将执行rehash操作对哈希表进行扩展或者收缩。记得字典的ht是有两个项的一维数组,数据存储在ht[0]么,ht[1]的作用便是rehash时候用。
条件:
①服务器没有在执行BGSAVE、BGREWRITEAOF命令,并且哈希表负载因子大于等于1
②服务器在执行BGSAVE、BGREWRITEAOF命令,并且哈希表负载因子大于等于5
③负载因子小鱼0.1时
步骤如下:
①为字典的ht[1]哈希表分配空间,大小取决于要执行的操作,及当前ht[0]包含的键值对数量(ht[0].used)。
如果是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n;
如果是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n
②将ht[0]中所有键值对rehash到ht[1]上边。
③当ht[0]都迁移到ht[1]后,释放ht[0],将ht[1]设置为ht[0],新创建一个空哈希表在ht[1]上,为下次rehash做准备。
3.6 渐进式rehash
rehash不是一次性完成的,而是分多次、渐进式完成。我理解的有三个点:
①在rehash过程中,删除、查找、更新,这三个操作会先在ht[0]执行,找不到节点的话去ht[1]中执行
②在rehash过程中,增加操作值对ht[1]执行,即ht[0]不会再执行添加操作
③从图7可以看到有个属性rehashidx,不进行rehash时为-1。当rehash开始时,rehash置0,然后迁移哈希值为0的哈希节点,然后rehashidx加1,迁移哈希值为1的哈希节点,不停执行下去直到ht[0]成为空表。释放ht[0],将ht[1]设置为ht[0],创建一个空的哈希表在ht[1]上。
4.跳跃表
4.1 跳跃表数据结构
跳跃表结构含义:header指向跳跃表的表头节点,表头节点没有实际数据,第一个有实际意义的数据是o1,只有三十二层高;tail指向跳跃表的尾结点;level表示跳跃表中层数最高的那个节点的层数(不包括头结点);length表示跳跃表的长度(不包括头结点);
跳跃表节点结构含义:level即L1L2...标记节点的各个层,每层有两个属性(前进指针(指向同一层次的下一个节点,如果没有则指向NULL。比如头结点的L1指向第一个节点的L1,L32后续节点没有则指向NULL;o1节点的L4指向o3节点的L4)、跨度(即某个节点的某层跟最近的同一层次的节点的距离,指向NULL的跨度为0。比如头结点的L1到o1节点的L1为1,头结点的L5根节点o3的L5距离为3,头结点的L32指向NULL所以跨度为0));BW指向节点的前一个节点;分值可以理解为节点的权重,跳跃表各个节点按照分值排序,如图8中的o1节点分值为1.0;成员对象用于保存节点存储的对象,如o1节点的o1.
如图8所示:
图8
5.整数集合
5.1 整数结合伪码
typedef struct intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[];}encoding属性有三种取值(INTSET_ENC_INT16, INTSET_ENC_INT32, INTSET_ENC_INT64),表示contents数组的类型。三种类型占用空间大小不一样。
length属性记录了集合中元素数量。
contents数组存放整数集合中的元素,按照值的大小从小到大有序排列且不包含重复项。
5.2 整数集合数据结构
图9展示了编码为INTSET_ENC_INT16,包含五个元素的整数集合:
图9
5.3 升级
先说明整数集合的encoding属性,有三种取值,分别表示元素类型为int16_t、int32_t、int64_t,这么做的目的是为了节约内存、提升整数集合灵活性。括弧 不支持降级。
每当将一个新元素添加到整数集合里边时,如果该元素类型比目前集合的encoding属性值要长时(比如现在集合时16位的int16_t类型,要添加一个int32_t类型的整数),就会执行升级操作。
步骤:
①根据新元素类型,扩展整数集合底层数组空间大小,并为新元素分配空间
②将现有的元素的类型调整为新元素类型,将类型转换后的元素放置到正确的位置
③将新元素放置到正确位置
以int16_t升级为int32_t为例:
图10
6.压缩列表
压缩列表存储小整数值与较短字符串。
6.1 压缩列表数据结构
zlbytes长4字节,记录整个压缩列表占用的内存字节数;
zltail长4字节,记录压缩列表表位节点举例压缩列表起始地址有多少字节即entryN的偏移量;
zllen长2字节,记录压缩列表包含的节点数量;
entryX为列表节点;
zlend长1字节,为特殊值0xFF(十进制255),用于标记压缩列表的末端。
图11
6.2 压缩列表节点数据结构
图12
previous_entry_length:固定长度为一个字节或者五个字节,记录前一个节点多少字节长。
一个字节长:前一个节点长度小于0xFE(254);
五个字节长:属性第一个字节为0xFE(254),后四个字节用于记录前一个节点多少字节长度。
encoding:记录了节点的content属性所保存数据的类型以及长度:
content:保存节点的值,可以是一个“字节数组”或者整数
6.3 连锁更新
每个节点的previous_entry_length属性保存了前一个节点的长度,该属性本身长度为1字节或者5字节。
调整当前节点或者增删节点对后边节点的影响:如果一个节点的previous_entry_length属性由1个字节变成5个字节,那么下一个节点的previous_entry_length属性会调整为5个字节长度。且下一个节点调整属性后刚好大于等于254字节,那么下一个节点也会影响再下一个节点。。。
发生的条件:(满足一条即可)
①改变节点使得长度大于等于254字节;
②在节点后边插入一个大于等于254字节的新节点;
③删除一个长度小于254字节而其上一个节点大于等于254字节长的节点)
- Redis学习总结_1_底层数据结构
- Redis底层数据结构总结
- Redis数据结构底层知识总结
- redis学习笔记1--底层数据结构与对象
- C++ Primer学习总结_1_开始
- C++ Primer学习总结_1_开始(续)
- 深入理解Redis:底层数据结构
- Redis底层数据结构之字典
- 深入理解Redis:底层数据结构
- REDIS系列之底层数据结构
- 数据结构_1_多项式运算
- Redis底层数据结构之简单动态字符串
- Redis底层数据结构之链表
- Redis底层数据结构之跳跃表
- redis数据结构底层(个人记忆使用)
- 并发基础_1_并发_底层实现
- UML学习_1_模型
- Redis数据结构总结
- friend
- 数据类型和变量
- STM32 SPI DMA 的使用
- matlab中如何使用外部工具箱
- Spring学习之缓存机制EhCache---Key
- Redis学习总结_1_底层数据结构
- 2016 Al-Baath University Training Camp Contest-1 I. March Rain —— 二分
- uml类图的图示方法
- NOI 题库 4.5之动态规划算法 3368 Sanguo
- BootStrap-全局样式
- Android框架之路——ToolBar的使用
- 关于输入后接收回车符问题
- kudu源码分析之async_logger
- 控制EditText是否弹出键盘的方法