当前位置: 首页 > 网络应用技术

2. SD,ListNode,Ziplist。第十,

时间:2023-03-06 01:53:20 网络应用技术

  2.RDIS数据结构

  Redis运行非常快。除了它是一个内存数据库外,所有操作的内存都有一个重要因素。它实现的数据结构使我们更加有效。

  本文中分析的源代码是Redis的GitHub主页的GitHub首页上下载的7.2unstable版本。

  REDIS是用C语言实现的,但它不直接使用C语言的字符*字符数组来实现字符串。String,即REDIS的字符串数据类型的基础数据结构是SDS。

  C语言字符串的缺陷以及可以改进的位置:

  下图是Redis SD的数据结构:

  (图片来源:Kobayashi编码)

  结构中的每个成员变量均分别引入:

  通常,REDIS的SDS结构增加了三个元数据:Len,Alloc,标志以解决C语言字符串的缺陷。

  SDS之所以设计不同类型的结构的原因是灵活地保存不同尺寸的字符串,从而有效地保存内存空间。

  除了设计不同类型的结构外,Redis还在编程中使用特殊的汇编和优化来节省内存空间,也就是说,在结构中声明。字节的实际数量是对齐的。

  如果您熟悉C/C ++,那么您必须知道结构中的内存是对齐的,甚至是书面测试和访谈高频测试。这就是Wiki所说的:

  C结构直接引用了一个物理内存的连续块,它是按照词长度边界的定义划界(大小)。结构的内容被存储了启动。

  翻译:C结构直接引用连续的物理内存块,该块通常与长边界(大小)分开。结构的内容存储在连续内存中。

  实际上,为了更快地找到结构中的某些数据,系统将进入固定的步骤 - 。让我们看看以下示例:

  因为Double是8个字节,所以我们必须让Char A Waste 7字节和INT C WAST 4字节4字节,并以第8个方式直接以步长的长度找到每个数据。

  另外,在不同的编译器中,内存的实现是不同的。以下示例是我在VS2019和GCC 7.5.0中运行的结果:

  如果编译器不想在字节对齐方法中分配内存,则可以使用属性定义结构。通过这种方式,结构会取出多少内存空间,编译器将分配多少空间。

  关键字属性还可以设置结构(结构)或联合的属性设置。大约有六个可以设置的参数值,即:对齐,包装,透明_Union,未使用,未使用,折旧和may_alias。

  使用属性参数时,您还可以在参数之前和之后添加“(两条下线),例如使用对齐__而不是对齐。在此中,文件中有声誉。

  1.对齐(对齐)

  此属性设置指定的ALLEM格式(以字节 - by -line)设置:例如:

  该语句将被迫编译(尽可能多的)变量(在分布空间中删除S或INT32_T变量)。

  如上所述,您可以手动指定对齐方式。同样,您也可以使用默认的对齐方法。如果对齐遵循指定的数字值,则编译器将根据您的目标计算机使用最有益,最有益的对准方法。例如:例如:

  在这里,如果大小(短)的尺寸为2(字节),则s的大小为6.次要值2,以使值大于或等于6,则该值为8,因此编译器将将S类型的对齐方法设置为8个字节。

  对齐属性使设置对象能够占据更多空间。相反,使用包装可以减少对象所占据的空间。

  应该注意的是,属性属性的有效性也与您的连接器有关。如果您的连接器仅支持16个字节对齐,则此时定义32个字节对齐。

  2.包装

  使用此属性来定义结构或联合类型,并设置每个变量的每种类型的内存约束。当枚举类型定义中使用时,它暗示应使用最小和完整的类型。

  在下面的示例中,包装_struct类型的变量数组中的值将紧密地倾斜,但是内部成员变量s不会是“ pack”。您需要使用包装来进行相应的约束。

  在下面的示例中,使用属性属性定义了一些结构和变量,并给出了结果的输出结果和分析。

  这应该是每个人最熟悉的。至少已经学会了数据结构的人应该能够编写一个两条链接的列表节点结构。

  (图片来源:Kobayashi编码)

  优势:

  缺点:

  因此,当数据量相对较小时,REDIS 3.0的列表对象将使用“压缩列表”作为基础数据结构的实现。它的优势是节省内存空间,并且是紧凑的数据结构。

  但是,压缩列表中存在性能问题(以下特定问题是什么),因此Redis在版本3.2中设计了一个新的数据结构QuickList,并通过QuickList更改了列表对象的基础数据结构。

  然后在Redis 5.0中设计了一个新的数据结构列表包,遵循紧凑的紧凑型内存布局。最后,在最新的Redis版本中,哈希对象的基础数据结构的压缩列表和ZSET对象被ListPackAccomplish替换。

  想象一下,现在有两个元素:整数5和字符串“十”,数据本身说明了8个字节(4+4)。如果您使用一个链接列表,则需要添加两个返回指针以占24个字节(每个字节(每个单位都有int,char和next4+4+4 = 12)。链接节点数据占16个字节,均为加倍在数据本身的大小中。如果使用Ziplist,则只需要记录每个节点节点(1个字节)节点(1个字节)的长度(1个字节)(1个字节)(1个字节),总共占用10个字节,以节省内存!

  压缩列表是由Redis开发的,以节省内存。它是由连续内存块组成的顺序数据结构,与数组有点相似。

  (图片来源:Kobayashi编码)

  压缩列表中有三个字段:

  在压缩列表中,如果我们想找到第一个元素和最后一个元素,我们可以直接定位头部三个字段的长度,复杂性为o(1)。找到其他元素时,不是这样高效,只能一个人找到。目前,复杂性为O(n),因此压缩列表不适合节省太多元素。

  此外,压缩列表节点(条目)的组成如下:

  (图片来源:Kobayashi编码)

  压缩列表节点包含三个部分:

  当我们将数据插入压缩列表中时,压缩列表将使用不同空间的两个元素和数据大小保存的信息,并根据字符串或整数的数据以及数据大小。这是基于数据大小的,根据数据大小的设计思想,将不同的空间分配分配给该类型的想法被Redis使用以节省内存。

  换句话说,根据数据的大小和类型,如何分配prev和编码。

  压缩列表中的每个节点中的everlen属性记录“上一个节点的长度”,而everlen属性空间的大小与上一个节点的长度值有关,例如:

  编码属性的空间大小与数据字符串或整数的数据以及字符串的长度有关。这是由源代码中的匹配表确定的。特定的匹配规则未详细扩展:

  ZiplistNew

  Intev32ifbe函数是一个大而小的转换函数,它均匀地转换为小端存储。压缩列表的操作中涉及许多位操作,如果它不统一,将会有混乱。基于小端存储。Wikipedia的解释如下:

  在几乎所有计算机上,多字节对象被存储为连续字节序列。记忆。[1]

  存储地址中有两个一般规则。数量符号将根据存储地址的最小或最大字节进行安排。如果最低有效位置位于最高效率位置的前面,则称为小端订单;否则,它称为大端订单。在网络应用程序中,字节顺序是必须考虑的因素。由于不同的机器类型可能会采用不同的标准字节序列,因此它们会根据网络标准进行转换。

  例如,假设上述变量类型位于地址,其十六进制是,地址范围为字节,其内部布置顺序取决于机器的类型。从第一个地方,第一定律将是:。小管将是:。

  Zlentry结构和宏定义

  (图片来源:掘金社区 @knight0xffff)

  为什么我应该在宏中使用{}语句在此处使用do {},这意味着在这里,由于宏定义的特征,{0}句子都用do {}实现了所有宏定义 - 直接替换原始代码,如下所示,如下所示,如下所示,如下所示,以下情况将是一个错误:

  当将压缩列表添加到某个元素或修改某个元素时,如果空间不够元素可能会改变,这将导致“链更新”问题,从而导致每个元素的空间被重新分配,从而导致访问压缩列的性能下降。

  假设一个在压缩列表中具有多个连续和长度在250和253之间的节点,如下所示:

  (图片来源:Kobayashi编码)

  由于这些节点的长度值小于254个字节,因此需要使用1个字节来保存该长度值以保存此长度值。

  目前,如果将长度大于254字节的新节点添加到压缩列表的头节点,即,新节点将成为E1的前节点,如下所示:

  (图片来源:Kobayashi编码)

  由于E1节点的extlen属性仅为1个字节大小,因此无法保留新节点的长度,此时,有必要重新分配压缩列表的空间,并扩展E1的EverLen属性节点从原始的1个字节大小到5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至5至55至5至5 Byte尺寸。

  多米诺骨牌的效果开始了。

  (图片来源:Kobayashi编码)

  E1的原始长度在250到253之间。由于现在的扩展空间,E1的长度大于254。因此,最初保存E1的EverLen属性也必须从1个字节扩展到5个字节。

  正如E1触发E2扩展一样,E2的扩展也将导致E3的扩展,并且扩展E3将导致E4的扩展为E4 ...它一直持续到最后。

  在特殊情况下生成的这种连续的空间扩展操作称为“链更新”,就像多米诺骨牌的效果一样,第一张卡片倒下,第二张卡被倒了。第二张卡倒了;倒了第二张卡。跌倒,推动第三张卡跌落...

  链更新将导致压缩列表多次占用的内存空间多次重新分配,这直接影响压缩列表的访问性能。

  因此,尽管紧凑的 - 列紧凑型内存布局可以保存内存开销,但是如果保留元素的数量增加或元素变大,则将导致内存重新分配。最糟糕的是“链更新”的问题。因此,压缩列表仅用于节省节点的数量。只要节点的数量足够小,即使链条更新,也可以接受。

  更新节点ZiplistPush,Ziplistinsert

  首先,让我们看一下Zipprevlenbytediff如何计算扩展的价值

  这是从SemgentFault @LNMPRG源代码的研究解释中的引用:

  如果以上函数计算了下一步函数,则可以看出,基于插入位置p和entrete所需的字节数的字节数

  错误维修过程首先确定NextDiff等于-4,即P位置的Prev_entry_len为5个字节,而插入的条目的长度仅需要1个字节才能保存。然后法官reqlen <4。看到读者可能是这里的读者,读者Maythere将是怀疑。由于prev_entry_len的长度已经为5个字节,因此新插入的值prev_entry_len+编码+内容字段肯定会大于5个字节。为什么少于4?在下面的构造函数中,我们可以看到,当链条更新时,以防止大量重新分配空间,如果条目的长度只需要1个字节才能保存,但是如果链已更新,可以分配给Prev_entry_len.5字节后,将不会执行收缩操作。修改错误维修代码相反。编译以下命令后,以下命令可能会导致redis崩溃(请注意,使用了上一个命令号,以下数字解释了redis内存数中redis内存中的ziplist内存的更改。

  使用6个命令插入“一个”,“两个”,252'a',250'a','',“三”,a'六个元素。目前的记忆职业如下:

  每个小矩形框都意味着占据内存字节的数量,而大矩形框则指示每个条目。每个条目都有三个项目,这些项目是prev_entry_len,编码和内容字段

  然后执行第7条的命令。图中显示了内存职业状态,这意味着:

  删除第三个条目,此时第四个条目的第一个条目从255个字节更改为5个字节(此时第二个条目是第四个条目的第一个条目),因此Prev_entry_len字段通过占用职业5来占据字节成为占据1字节的一个字节。请参阅图中的黄色框架部分。

  请注意,此时将进行链条更新,因为蓝色框架部分的Prev_entry_len从257个字节更改为253,并且也可以更新为1个字节。但是,在Redisdo的链条更新的情况下,不要缩小。

  然后执行第8条的命令,将数据插入绿色框中(请参阅第3列)。目前,蓝色篮子中的prev_entry_len为5个字节,绿色框中的数据仅占2个字节。更新为1个字节后,prev_entry_len的多余4个字节可以完全容纳绿色框中的数据。尽管插入了数据,但它降低了Renloc之后的占用内存,这会导致Ziplist的数据损害。

  修复此错误代码很容易理解,即图中第三列蓝色框的prev_entry_len仍然保留5个字节。

  它可以进一步构建另一种情况,即构造函数的第六步,作为rpush列表10,然后不会导致REDIS崩溃,但会丢失10个元素。读者可以为自我绘制记忆占用图表- 自行分析。

  链更新节点__ziplistCascadeUpdate与链链更新功能的旧版本进行了比较,在该函数的新版本中,第一个记录要扩展多少个字节,然后在末尾执行整体扩展。旧版本是在while loopevery的时间内需要扩展节点,容量将立即扩展,并且时间复杂性将更高。

  查找节点ZiplistFind

  删除Nodes ZiplistDelete,ZiplistDeleTerange有一点关注。由于节点大小会更改为删除节点,因此可能会有链条更新!