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取值  最大值  最小值  int16_t  INTSET_ENC_INT16   32767 (2^15 - 1) - 32768 (2^15) int32_t  INTSET_ENC_INT32  2147483647 (2^31 - 1) - 2147483648 (2^31) int64_t  INTSET_ENC_INT64 9223372036854775807 (2^63 - 1) -9223372036854775808 (-2^31)

        每当将一个新元素添加到整数集合里边时,如果该元素类型比目前集合的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属性保存的值  00bbbbbb  1字节  长度小于64(2^6)字节的字节数组  01bbbbbb xxxxxxxx  2字节  长度小于16384(2^14)字节的字节数组  10______ aaaaaaaa bbbbbbbb cccccccc dddddddd  5字节  长度小于4294967296(2^32)字节的字节数组 

整数编码 编码  编码长度  content属性保存的值  11000000  1字节  int16_t类型的整数  11010000  1字节  int32_t类型的整数  11100000  1字节  int64_t类型的整数 11110000  1字节  24位有符号整数  11111110 1字节  8位有符号整数  1111xxxx  1字节  介于0和12之间的值,无需content属性 

        content:保存节点的值,可以是一个“字节数组”或者整数
    6.3 连锁更新

        每个节点的previous_entry_length属性保存了前一个节点的长度,该属性本身长度为1字节或者5字节。

        调整当前节点或者增删节点对后边节点的影响:如果一个节点的previous_entry_length属性由1个字节变成5个字节,那么下一个节点的previous_entry_length属性会调整为5个字节长度。且下一个节点调整属性后刚好大于等于254字节,那么下一个节点也会影响再下一个节点。。。

        发生的条件:(满足一条即可)

            ①改变节点使得长度大于等于254字节;

            ②在节点后边插入一个大于等于254字节的新节点;

            ③删除一个长度小于254字节而其上一个节点大于等于254字节长的节点)

0 0
原创粉丝点击