带着问题去学习,事半功倍~
一:背景
Redis5 引入了一个新的数据结构,为raxNode。
Redis1 是基础
Redis2 提供高可用方案
Redis3 集群实现
Redis4 持久化机制
Redis5 新类型和模块
取自B站Redis大神的:参考代码可看:https://www.bilibili.com/video/BV1mN411B7gU/?spm_id_from=333.788.top_right_bar_window_history.content.click&vd_source=282955649f92cc8793739db9b1769bef:
写了一份读书笔记
raxNode和listpack可阅读博客:https://blog.youkuaiyun.com/cainiao1412/article/details/122565048
raxNode是为了stream数据结构,实现消息队列数据结构。
本期先介绍Redis5的新特性。
二:C/Redis的基础知识
2.1 结构体声明当中 attribute ((packed))关键字
参考博客:https://blog.youkuaiyun.com/tianya_lu/article/details/108747234
redis的sds就是用到了这样的数据结构。能起到压缩内存大小的作用。
2.2 Redis对象
参考博客:
https://www.cnblogs.com/MrLiuZF/p/15005424.html
https://zhuanlan.zhihu.com/p/382296964?utm_id=0
redis所有对象都是由redisObject进行包装。每一个value都是由redisObject封装而成的。key是字符串。
2.3 size_t
size_t 是一个 unsigned X 类型,这里的 X 可以是 char ,int ,long ,long long 等等,因此 size_t 的最大值是根据实际情况而改变的,定义是他能容纳当前系统所能定义的数据的最大尺寸值,比如定义一个包含 4G 个元素的 char 数组。
2.4 (raxNode*)n+1
这里指针+1,其实加的是sizeof(raxNode)的长度
2.5 C++内存对齐
参考博客:https://zhuanlan.zhihu.com/p/600487301?utm_id=0
三:raxNode学习
https://www.cnblogs.com/cquccy/p/14268049.html
https://blog.youkuaiyun.com/cainiao1412/article/details/122565048
问题1:raxStack如果分配元素超过了max_items,会怎么处理?
调用rax_malloc(sizeof(void*)ts->max_items2)
memcpy(ts->stack, ts->static_items,sizeof(void)ts->max_items*2)
问题2:分配内存后,能保证原来的数组地址和当前的数组地址是同一个吗?
不一定。如果是rax_realloc就是同一个,如果rax_malloc是不同一个。
问题3:raxStack栈里面每个元素存储的是什么?
对象指针。
问题4:为什么要一开始复制ts->stack = ts->static_items
static_items地址和ts->stack一开始一样,达到32长度后会判断,认为需要重新开辟地址空间。
问题5:raxNode存储的大小有多大?
4B,柔性数据评估不出来。
问题6:rax/raxNode数据结构是什么?
typedef struct rax {
raxNode* head; //头结点
uint64_t numele; //元素个数
uint64_t numnodes //节点个数
}
typedef struct raxNode {
uint32_t iskey:1
uint32_t isnull:1
uint32_t iscompr:1;
uint32_t size:29 //子节点数或者是压缩字符串的长度
usigned char[] data;
}
问题7:元素个数和节点个数分别指的是什么?
rax是一颗树
numele代表有多少k-value数据。
numnodes代表有多少个树结构的节点。
问题8:rax结构占用多少个字节?
64位寻址,指针8B
总和为24B
问题9:raxNewNode中的nodesize是怎么计算的,含义是什么?
创建一个rax节点。分配空间给raxNode(基数树节点)
raxNode本身头结点长度(4B) + children字符大小(size_t)+ children的raxpadding(字节对齐) + sizeof(raxNode*)children(指向子节点的指针)数量,和保存value值指针的(void)(8B)
假设有三个子节点 a b c
那么node -> data = “abc”
每一个子节点数就等于data的字符数。
总体四个部分:
第一部分:4个字节
第二部分:数据部分n->data + rax_padding(n->data)
第三部分:子节点的地址数组 Aptr Bptr Cptr
第四部分:额外数据 sizeof(void)(value值)
sizeof(raxNode) + n->size + rax_padding(n->size) + sizeof(void) + sizeof(raxNode)*(n->size)
是否有值,通过n->iskey来判断。
问题10:raxNewNode和raxNew什么区别?
假设有三个子节点 a b c
那么node -> data = “abc”
每一个子节点数就等于data的字符数。
问题11:一个整数v,v + raxpadding(v) 占用多少字节?
字节对齐,保证都是8字节为单位,
4+8*n。
问题12:压缩节点和普通节点有什么区别?
压缩节点只有一个子节点。
通过n->iscompr来判断是否压缩。
并且n->size不代表子节点个数。代表压缩的字符串的长度
问题13:n->data长度怎么计算?
n->data = n->size + rax_padding(n->size)
问题14:raxNodeCurrentLength,这个宏的含义是什么?
就是计算四个部分的长度。
问题15:raxNodeLastChildPtr,这个宏的含义是什么?
求raxNode最后一个节点的地址
(char*)(n) + raxNodeCurrentLength(n) - sizeof(raxNode*) - ((n) - >iskey && !(n -> isnull)) ? sizeof(void*) : 0
问题16:raxNodeFirstChildPtr,这个宏的含义是什么?
第一个子节点
n->data + n->size + rax_padding(n->size)
问题17:*raxReallocForData(raxNoden, voiddata) 宏是做什么的?
分配一个sizeof(void*)即可
rax_realloc(n, curlen + sizeof(void*))
问题18:n->data保存的是什么值
n->data 保存指针的指针
问题19:raxSetData和raxGetData的作用是什么?
比较简单,直接对raxNode的最末尾指针添加数据。
问题20:raxAddChild添加子节点的操作是什么?
1.计算n的原始长度和增加1个子节点的长度
2.给子节点分配空间
3.给该空间分配更多空间
4.将字符插入n->data,按字典序排序
5.将n+curlen-sizeof(void*)的地址,挪动到n+newlen-sizeof(void*)的地址,这个是为了挪动辅助地址值。
6.确定插入点,插入值4 + n->data + pax_padding(n->data)
挪动了src = n->data + n->size + raxpadding(n->size)+sizeof(raxnode*)pos
到dst = src + shift + sizeof(raxNode*)
7.如果shift>0,将,多余的空间用来补充n->data
8.插入字符到n->data
9.保存子节点的指针
用memcpy保存地址值
问题21:newlen - curlen - sizeof(void*)是什么含义?shift值的可能有多少?
raxpadding后变更的大小。
有0或8
问题22:parentlLink,childptr是什么含义?
指向保存子节点地址的父节点的位置
childptr是子节点位置
问题23:压缩节点怎么创建?
压缩节点最多只有一个子节点,其他创建方式和普通节点无区别。
问题24:raxLowWalk作用是什么?
raxLowWalk是根据key查找对应value。
问题25:raxLowWalk中的stopNode,plink,splitpos作用是什么?
stopNode指向终止节点
plink指向终止节点的父节点
splitpos作用是压缩节点终止在哪个字符串位置
问题26:raxLowWalk中寻找key一共分为几步?
1.根据key查找字符串匹配?(压缩节点只要有一个匹配不上就Break,非压缩节点遍历i)
2.对于普通节点raxNode + j 移动,获得指向下一个节点的指针。
问题27:raxFind()和raxLowWalk什么区别?
raxFind内调用了raxLowWalk,他是直接获取key的对应的数据。
raxLowWalk返回对应的节点的地址
问题27:raxFind()为什么raxNotFound有一个条件:h->iscompr && splitpos !=0 ?
是因为它是压缩节点,压缩字符串匹配第一个字符成功,splitpos代表是开始,只要存在匹配>0,说明压缩节点匹配了一半,不符合条件。
问题28:raxGenericInsert函数分为哪几步
1.raxLowWalk判断raxTree中是否含有该key
1.1 如果有该key,但是没有value,那么分配一个空间。分配到parentlink当中。
1.2 如果有,用新的地址覆盖掉原来的key-value数据。raxSetData
后面有5种情况
第一种情况,原本key中间是一个压缩节点。此时1压缩节点=3压缩+1普通
第二种情况,匹配的key是最后一个节点。此时变成1压缩节点=2压缩+1普通
第三种情况,插入key是最后一个节点,此时变成1压缩节点=3压缩+1普通
第四种情况,插入的key是一开始就匹配不到,此时1压缩节点 = 2压缩+1普通
第五种情况,插入的key是包含在已有的key之后,此时1压缩节点=2压缩
问题29:为什么需要保存*next指针?
保存原有的下个子节点的地址
问题30:假设有一个字符串:“abcdef”,从字符d开始异化,该如何切分?
j = 3
h->size=6
postfixlen = h->size - j - 1,代表需要再建一个压缩节点长度
所以由一个压缩节点,变为1压缩(长度为3) + 1普通(长度为2) + 2压缩(长度2、和添加长度)的节点。如果从f开始切割,那么只有1压缩+1普通。对应第一种情况。
如果是从f开始异化,但是只异化一个字符,那么 变为1压缩(长度为5) + 1普通(长度为2)+ 1压缩(长度为添加长度)
j代表切割点。
问题31:splitNode、trimmed、postifx含义是?
splitNode切割节点就是问题30的异化处节点,用raxNewNode(1, split_node_is_key)来进行创建。切割节点一定会有。
trimmed代表切割前字符。如果j=0则不会创建,对应情况四。1普通(长度为2) + 2压缩(原长度-1 + 添加长度-1)
postifx代表切割后字符
问题32: splitNode用raxNewNode(1, split_node_is_key)来创建,这两个入参的含义是什么?
切割节点后面永远只有一个子节点。
split_node_is_key代表分割节点是否为一个key,只有当trimmed_len = 0
问题33:trimmed节点需要分配多少空间?
(压缩节点长度)sizeof(raxNode) + trimmed_len(data数据长度) + sizeof(raxNode*)(指向下一节点)+sizeof(void*)(如果该节点是含有value值)
问题34: parentLink作用是?
保存该压缩节点的上一个节点。将splitnode的地址赋值给parentLinK。此时parentLink不是指向原压缩节点,而是指向分割节点。
问题35: trimed节点一定会构建吗?
不一定,如果j==0,分割点是在第一个,那么就不会有trimmed节点。
trimmed节点有可能为压缩节点,也有可能为普通节点。
问题36:为什么h(整个节点)->data有数据,那么trimmed节点->data的数据和h一样?为什么Postfix一定不是key?
问题37: postfixlen=0,对应的是哪种情况?
对应情况2。
postfix一定iskey=0;
问题38:next节点的含义是什么?
原压缩节点的下一个子节点。
问题39:触发了第五种情况(ABCD => AB + CD)
h->data赋给AB原因?
data赋给CD原因?
问题40:raxNode *newh = raxAddChild(h, s[i], &child, &new_parentlink); h=newh; memcpy(parentlink,&h,sizeof(h));parentlink=new_parentlink;这一段的含义是什么?
s[i] = ‘E’,
h->data = ‘B’ 添加后变成h->data = ‘BE’,并且有Eptr指针。
newh是Eptr。 h = newh代表指向下一个节点。
memcpy(parentlink,&h,sizeof(h))代表parentlink指针的内容,变成了h,也就是parentlink保存指向h节点。
new_parentlink保存了Eptr的指向下一个子节点的指针。
问题41:[f]->“oot”->[e] -> "r"删除e后,获得的子节点样式是?
[f] -> “ootr”
问题42:给你一个footxkm,调用radixNoWalk,分析解析过程:
结构树:
i代表匹配到某一个值,所以到k就不行了,那么i=5。j是匹配压缩节点位置,y不对,那么j=0;
h代表终止节点h->data = "yz” h为“yz”压缩节点的地址。
parentlink代表终止节点的前一个指向 parentlink= [ex]的x指针。
【rax的迭代】
问题43:raxStart(raxIterator *it , rax* rt)的作用是什么?
问题44:redis源码中的raxSeekGreatest作用是什么?
raxSeekGreatest函数的作用是在给定的radix树中寻找最大的节点。
具体来说,它会从给定的节点开始,沿着右子树一直向下遍历,直到遇到一个没有右子节点的节点为止。这个节点就是最大的节点。
该函数主要用于在radix树中进行迭代遍历时,找到最大的节点作为停止条件。