欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

《Redis设计与实现》[第一部分]数据结构与对象-C源码阅读(一)

程序员文章站 2022-03-21 08:51:09
...

一、简单动态字符串SDS 关键字:空间预分配,惰性空间释放,二进制安全 C字符串不易更改,所以Redis中把C字符串用在一些无须对字符串值进行修改的地方,作为字符串字面量(String literal),比如打印日志: redisLog(REDIS_WARING, “Redis is now ready to

一、简单动态字符串SDS

关键字:空间预分配,惰性空间释放,二进制安全

C字符串不易更改,所以Redis中把C字符串用在一些无须对字符串值进行修改的地方,作为字符串字面量(String literal),比如打印日志:
redisLog(REDIS_WARING, “Redis is now ready to exit, bye bye…”);

在Redis数据库中,包含字符串的键值对在底层都是由SDS实现的。

SDS还被用作缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是SDS实现的。

源码

SDS结构的定义在sds.h中:

    /*
     * 保存字符串对象的结构
     */
    struct sdshdr {     
        // buf 中已占用空间的长度
        int len;
        // buf 中剩余可用空间的长度,即未使用空间
        int free;
        // 数据空间
        char buf[];
    };

获取一个SDS长度的复杂度为O(1),由SDS的API在执行时自动设置和更新SDS长度,使用SDS无须进行任何手动修改长度的工作。

空间分配

SDS的空间分配策略是:当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,若不满足,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,杜绝了发生缓冲区溢出的可能性。

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

  • 空间预分配

空间预分配用于减少连续执行字符串增长操作所需的内存分配次数。
通过这种预分配策略,SDS将连续增长N次字符串所需的内存重分配次数从必定N次降低为最多N次。
其中额外分配的未使用空间数量由以下公式决定:

1. 如果对SDS进行修改后,SDS的长度(即len属性的值)小于1MB,就分配和len属性同样大小的未使用空间,即len属性的值和free属性的值相同   
2. 如果对SDS进行修改之后,SDS的长度大于等于1MB,就分配1MB的未使用空间。  
  • 惰性空间释放

惰性空间释放用于优化SDS字符串缩短操作的内存重分配操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。

SDS的API都是二进制安全的(binary-safe),所有SDS API都会以二进制的方式处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,被读取时就是什么样。

Redis用SDS的buf数组保存二进制数据而不是字符。

SDS可以兼容部分C字符串函数。

二、链表

关键字:多态

当一个列表键包含了数量比较多的元素,或是列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。

integers列表键的底层实现就是一个链表,链表中的每个结点都保存了一个整数值。

除了链表之外,发布与订阅、慢查询、监视器等功能也用到了链表,Redis服务器本身还使用链表保存多个客户端的状态信息,以及使用链表来构建客户端输出缓冲区(output buffer)。

源码

链表结构的定义在adlist.h中:

    /*
     - 双端链表节点
     */
    typedef struct listNode {
        // 前置节点
        struct listNode *prev;
        // 后置节点
        struct listNode *next;
        // 节点的值
        void *value;
    } listNode;
    /*
     *双端链表迭代器
     */
    typedef struct listIter {
        // 当前迭代到的节点
        listNode *next;
        // 迭代的方向
        int direction;
    } listIter;
    /*
     - 双端链表结构
     */
    typedef struct list {
        // 表头节点
        listNode *head;
        // 表尾节点
        listNode *tail;
        // 节点值复制函数
        void *(*dup)(void *ptr);
        // 节点值释放函数
        void (*free)(void *ptr);
        // 节点值对比函数
        int (*match)(void *ptr, void *key);
        // 链表所包含的节点数量
        unsigned long len;
    } list;  

list结构为链表提供了表头指针head、表尾指针tail,以及链表长度计数器len,dup、free和match成员则是用于实现多态链表所需的类型特定函数:

  • dup函数用于复制链表结点所保存的值
  • free函数用于释放链表结点所保存的值;
  • match函数则用于对比链表结点所保存的值和另一个输入值是否相等。

Redis的链表实现的特性如下:

  • 双端、无环、带表头指针和表尾指针、带链表长度计数器、多态

三、字典

关键字:多态,渐进式rehash,murmurhash2

Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、改、查也是构建在对字典的操作之上的。

字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,或是键值对中的元素都是比较长的字符串时,Redis就使用字典作为哈希键的底层实现。

Redis的字典使用哈希表作为底层实现,一个哈希表里可以有多个哈希表结点,每个哈希表结点就保存了字典中的一个键值对。

源码

字典所使用的哈希表在dict.h中定义:

    /*
     * 哈希表
     * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
     */
    typedef struct dictht {    
        // 哈希表数组,数组中的每个元素都是一个指向dictEntry结构的指针
        dictEntry **table;
        // 哈希表大小
        unsigned long size;      
        // 哈希表大小掩码,用于计算索引值
        // 总是等于 size - 1
        unsigned long sizemask;
        // 该哈希表已有节点的数量
        unsigned long used;

    } dictht;
  • table属性是一个数组,数组中的每个元素都是一个指向dictEntry结构的指针,每个dictEntry结构保存着一个键值对。
  • size属性记录了哈希表的大小,即是table数组的大小。
  • used属性则记录了哈希表目前已有结点(键值对的数量)
  • sizemask属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面
    /*
     * 哈希表节点
     */
    typedef struct dictEntry {      
        // 键
        void *key;
        // 值
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
        } v;
        // 指向下个哈希表节点,形成链表
        struct dictEntry *next;

    } dictEntry;
  • key属性保存着键值对中的键
  • v属性保存键值对中的值,其中键值对中的值可以是一个指针,或是一个uint64_t整数,或是一个int64_t整数
  • next属性指向另一个哈希表结点的指针,使用链地址法解决键冲突问题。
    /*
     * 字典
     */
    typedef struct dict {
        // 类型特定函数
        dictType *type;

        // 私有数据
        void *privdata;

        // 哈希表
        dictht ht[2];

        // rehash 索引
        // 当 rehash 不在进行时,值为 -1
        int rehashidx; /* rehashing not in progress if rehashidx == -1 */

        // 目前正在运行的安全迭代器的数量
        int iterators; /* number of iterators currently running */

    } dict;

type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的:

  • type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
  • 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 (*valDestructor)(void *privdata, void *obj);

} dictType;
  • ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
  • rehashidx属性记录了rehash目前的进度,如果目前没有在进行rehash,那么它的值为-1.
    /*
     * 字典迭代器
     *
     - 如果 safe 属性的值为 1 ,那么在迭代进行的过程中,
     - 程序仍然可以执行 dictAdd 、 dictFind 和其他函数,对字典进行修改。
     *
     - 如果 safe 不为 1 ,那么程序只会调用 dictNext 对字典进行迭代,
     - 而不对字典进行修改。
     */
    typedef struct dictIterator {            
        // 被迭代的字典
        dict *d;

        // table :正在被迭代的哈希表号码,值可以是 0 或 1 。
        // index :迭代器当前所指向的哈希表索引位置。
        // safe :标识这个迭代器是否安全
        int table, index, safe;

        // entry :当前迭代到的节点的指针
        // nextEntry :当前迭代节点的下一个节点
        //             因为在安全迭代器运作时, entry 所指向的节点可能会被修改,
        //             所以需要一个额外的指针来保存下一节点的位置,
        //             从而防止指针丢失
        dictEntry *entry, *nextEntry;

        long long fingerprint; /* unsafe iterator fingerprint for misuse detection */
    } dictIterator;

哈希

Redis计算哈希值和索引值的方法如下:

    // 使用字典设置的哈希函数,计算键key的哈希值
    hash = dict->type->hashFunction(key);

    // 使用哈希表的sizemask属性和哈希值,计算出索引值
    // 根据情况不同,ht[x]可以是ht[0]或ht[1]
    index = hash & dict->ht[x].sizemask;
/* ------------------------- hash functions ------------------------------ */

/* Thomas Wang's 32 bit Mix Function */
unsigned int dictIntHashFunction(unsigned int key)
{
    key += ~(key 15