• redis学习笔记《redis设计与实现》及源码阅读


    1.2 章节编排

    • 数据结构对象
    • 单机数据库的实现
    • 多机数据库的实现
    • 独立功能的实现

    2简单动态字符串

    2.1 SDS的定义

    struct sdshdr{
      int len;  //记录buf中已经使用字节的数量,等于sds所保存字符串的长度(不包含)
      int free;  //记录buf数组中未使用字节的数量
      char buf[];
    }
    

    2.2 SDS与C字符串的区别

    2.2.1 常数复杂度获取字符串长度

    2.2.2 杜绝缓冲区溢出

    2.2.3 减少修改字符串带来的内存分配次数

    通过未使用空间,SDS实现了空间预分配和惰性空间释放这两种优化策略
    1.空间预分配
    预分配的策略如下:

    • 如果修改之后,SDS的len小于1MB,那么分配和len同样大小的free空间。
    • 如果修改之后,len大于1MB,那么程序分配1MB的未使用空间
      2.惰性空间释放

    2.2.4 二进制安全

    SDS 可以保存一系列二进制数据,因为它不是用而是使用len来判断字符的结束

    2.2.5 兼容部分C字符串函数

    SDS 虽然是二进制安全的,但是总会以c字符串的方式,遵循空字符结尾的方式,以便重用<string.h>定义的含数

    2.3 SDS API

    函数 作用 时间复杂度
    sdsnew 创建一个包含给定C字符串的SDS O(N),N为给定字符串的长度
    sdsempty 创建一个不包含任何内容的空SDS O(1)
    sdsfree 释放给定的SDS O(N),N为被释放的长度
    sdslen 返回SDS的已使用空间字节数 O(1)
    sdsavail 返回SDS的未使用空间字节数 O(1)
    sdsup 创建一个给定SDS的副本 O(N),N为给定SDS的长度
    sdsclear 清空SDS保存的字符串内容 因为惰性空间释放策略,复杂度为O(1)
    sdscat 将给定字符串拼接到SDS字符串的末尾 O(N),N为被拼接C字符串的长度
    sdscatsds 将给定SDS字符串拼接到另一个字符串的末尾 O(N),N为被拼接SDS字符串的长度
    sdscpy 将给定的C字符串复制到SDS里面,覆盖原有的SDS字符串 O(N),N为被复制的C字符串的长度
    sdsgrowzero 用空字符串将SDS扩展至给定长度 O(N),N为扩展新增的字节数
    sdsrange 保留SDS给定区间内的数据,不在区间内的数据会被覆盖或清除 O(N),N为被保留的字节数
    sdstrim 接受一个SDS和一个c字符串作为参数,从SDS左右两端分别移除所有在C字符串中出现过的字符 O(M*N),M为SDS的长度,N为给定C字符串的长度
    sdscmp 对比两个SDS字符串是否相同 O(N),N为两个SDS中较短的那个SDS的长度

    3 链表

    3.1 链表和链表节点的实现

    typedef struct listNode{
      struct listNode* prev;
      struct listNode* next;
      void* value;
    }listNode;
    
    typedef struct list{
      listNode *head;//头节点
      listNode *tail;//尾节点
      unsigned long len;//链表所包含的节点数量
      void *(*dup) (void *ptr);//节点值复制函数
      void (*free)(void *ptr);//节点值释放函数
      int (*match)(void *ptr,void *key);//节点值对比函数
    }
    

    3.2 链表和链表节点的API

    4 字典

    4.1 字典的实现

    4.1.1 哈希表

    typedef struct dictht{
      dictEntity **table;//哈希表数组
      unsigned long size;//哈希表大小
      unsigned long sizemask;//哈希表大小掩码,总是等于size-1
      unsigned long used;//该哈希表已有节点的数量
    }
    

    4.1.2 哈希表节点

    typdef struct dictEntity{
      void *key;//键
      union {
        void *val;
        uint64_t u64;
        int64_t s64;
      }
      struct dictEntity *next;//指向下个哈希节点,形成链表
    }
    

    4.1.3 Redis中的字典由dict.h/dict结构表示

    typedef struct dict{
      dictType *type;//类型特定函数
      void *privdata;//私有数据
      dictht ht[2];//哈希表
      int trehashidx; //rehash索引,当rehash不再进行时,值为-1
    

    type 和privadata 是针对不同类型的键值对,为创建多态字典而设置的。

    • type属性是一个指向dictType的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数。
    • privdata 属性则保存了需要传给那些函数特定函数的可选参数
    typedef struct dictType{
      unsigned int (*hashFunction)(const void *key);//计算哈希值的函数
      void *(*keyDup) (void *privdata,const void *key);//复制键的函数
      void *(*valDup)(void *privdata,const void *obj);//复制值的函数
      int (*keyCompare) (void *privdata,const void *key1, const void *key2);//对比键的函数
      void (*keyDestructor)(void *privdata,void *key);//销毁键的函数
      void (*valueDestructor)(void *privdata,void *obj);//销毁值的函数
    }
    

    ht[1]哈希表只会在ht[0]哈希表进行rehash的时候使用。

    4.2 哈希算法

    计算hash索引的方式为:

    //使用字典设置的哈希函数,计算键key的哈希值
    hash = dict->type->hashFunction(key);
    //使用哈希表sizemask属性和哈希值,计算出索引值
    //根据情况不同,ht可以是ht[0]或者ht[1]
    index = hash&dict->ht[x].sizemask;
    

    当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值。

    4.3 解决键冲突

    当有两个以上的数量的键被分配到哈希数组的同一个索引上面时,使用拉链法来解决冲突,并使用头插法。

    4.4 rehash

    redis 对字典的哈希表执行rehash的步骤如下:

    1. 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht[0].used属性的值):
    • 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n(2的n次方幂)
    • 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n
      2.将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上
      3.将ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]表为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

    哈希表的扩展与收缩

    当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:

    1. 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的装载因子大于等于1
    2. 服务器目前正在执行BGSAVEH或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5
      负载因子=哈希已保存节点数量/哈希表大小
      load_factor = ht[0].used/h[0].size

    4.5 渐进式rehash

    渐进式rehash执行期间的哈希表操作

    5 跳跃表

    redis只在两个地方使用了跳表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。

    5.1 跳跃表的实现

    5.1.1 跳跃表节点

  • 相关阅读:
    结对作业(测试版)
    回答自己的提问
    阅读一个程序员的生命周期有感
    阅读13到17章提出问题
    读8 9 10章提出问题
    5.2 5.3测试与封装
    5.1 四则运算单元测试j
    阅读5.5章6章7章提出疑问
    做汉堡
    阅读第1到第5章过程的疑问
  • 原文地址:https://www.cnblogs.com/zhouyu0-0/p/14414383.html
Copyright © 2020-2023  润新知