《redis设计与实现》初版 阅读笔记(未看完)

1、文档介绍html

本文仅做为本人读书笔记使用,不对其中内容作解释,记录以本人能够看懂为标准redis

原书连接:https://redisbook.readthedocs.io/en/latest/index.html算法

该书以简明的方式主要介绍了Redis内部的运行机制,从数据结构到服务器构造,值得推荐数据库

                                                                                      第一部分  内部数据结构数组

Redis内存所使用的数据结构与算法缓存

1、SDS(Simple Dynamic String) 简单动态字符串安全

1. SDS做用服务器

  • 实现字符串对象。Redis内字符串对象并不表明就是字符串值,字符串对象还能够保存lang类型的值,包含字符串的字符串对象包含的才是SDS值
  • 取代C默认的char*类型。char*类型有不少限制,好比说数据追加与长度计算。在Redis内,客户端传递给服务器的aof缓存、协议内容、回复等都是SDS类型的存储

2. Redis中的字符串数据结构

Redis内的字符串不只包含\0结尾的字符串,还包含简单的字节数组,还包括其余格式的数据等内容app

Redi使用SDS类替换C语言默认字符串考虑到两点:高效、二进制安全(程序不对字符串里面保存的数据进行任何假设)

3. SDS实现

typedef char * sds;

struct sdshdr { len = 11; free = 0; buf = "hello world\0"; // buf 的实际长度为 len + 1 };

sds 是 char* 的一个别名

结构体里包含了三个属性:len、free、buffer三个属性

经过len属性能够O(1)的进行长度计算;经过buf分配额外的空间,并使用free记录未使用空间的大小,sds可让执行追加操做所需的内存重分配次数大大减小;在char *实现中,追加只能经过重分配内存实现

于此同时对SDS的操做必须正确处理len与free属性

4. 如何减小内存重分配次数

建立:当调用set命令建立SDSHDR时,BUF很少申请,恰好给够所需的,free=0

追加:当再次追加的时候,若是free的长度大于所需,就直接append进去,否则的话,会给二倍的内存,可是若是大于SDSHDR容许的MAX_Preallocation,最大预分配,不会翻倍,会再给一个MAX_Preallocation

释放时间:当键值被删除时预分配空间会被删除;当重启Redis时,预分配的空间也会被释放,每一个SDS对应的SDSHDR不存在预分配空间,BUFF大小等于所需空间

SDS是Redis对应的字符串表示,SDSHDR是对应的存储类型

2、双端链表

1. 双端链表做用

  • 双端链表是Redis列表(List)结构的底层实现之一,另外一个是压缩列表,由于压缩列表占用的额内存更少,在须要的时候才会从压缩列表转换为双端链表
  • 事务模块使用双端链表依序保存输入的命令
  • 服务器模块使用双端链表来保存多个客户端
  • 订阅/发送模块使用双端链表来保存订阅模式的多个客户端
  • 事件模块使用双端链表来保存时间事件(time event)

2. 双端链表的实现

 双端链表是由两部分组成的,list与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);        //方法
} list;

listNode的value值的类型是void *,方法返回值的类型也是void *,表明对值得类型不作限制

3. 迭代器

typedef struct listIter {

    // 下一节点
    listNode *next;

    // 迭代方向
    int direction;

} listIter;

迭代器内保存一个listNode,而且指明迭代的方向

3、字典

 1. 字典的做用

  • 实现数据库键空间
  • 用做hash类型键的底层实现之一

Redis是一个键值对数据库,数据库中的键值对由字典保存,每一个数据库都有一个字典,这个字典称为键空间(Key Space),当用户添加一个键值对到数据库中时,不管键值对是什么类型,程序就会将该键值对添加到键空间

hash类型键的底层实现除了字典外就是压缩列表

2. 字典的实现

字典的实现方式有不少种,例如链表与数组,优势:简单   缺点:只适用于元素很少的状况

                                          哈希表,      优势:高效简单

                                          平衡树,      优势:稳定,排序操做更高效  缺点:实现更复杂

Redis采用哈希表实现字典,哈希表的子结构是dictEntry

dictEntry的实现

/*
 * 哈希表节点
 */
typedef struct dictEntry {

    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;          //union关键字 三种中只能包含其中一种

    // 链日后继节点
    struct dictEntry *next;

} dictEntry;

next指针指向另外一个dircEntry节点,之因此存在链是由于有的键值可能会哈希成同一值,因而采用链地址法来处理哈希值碰撞问题,当不一样的键拥有相同的哈希值时,哈希表就将这些键连接起来

dircht(dirc hash table)的实现

/*
 * 哈希表
 */
typedef struct dictht {

    // 哈希表节点指针数组(俗称桶,bucket)
    dictEntry **table;

    // 指针数组的大小
    unsigned long size;

    // 指针数组的长度掩码,用于计算索引值
    unsigned long sizemask;

    // 哈希表现有的节点数量
    unsigned long used;

} dictht;

**table是一个数组,俗称桶(bucket),每个值对应一个dictEntry结构的指针

size表明数组大小,sizemask意思不清楚,used就是哈希表现有的键的数量

字典的定义

/*
 * 字典
 *
 * 每一个字典使用两个哈希表,用于实现渐进式 rehash
 */
typedef struct dict {

    // 特定于类型的处理函数
    dictType *type;

    // 类型处理函数的私有数据
    void *privdata;

    // 哈希表(2 个)
    dictht ht[2];

    // 记录 rehash 进度的标志,值为 -1 表示 rehash 未进行
    int rehashidx;

    // 当前正在运做的安全迭代器数量
    int iterators;

} dict;

字典的实现使用了两个hash table,0号哈希表是字典主要使用的哈希表,1号哈希表只有当程序对0号哈希表进行rehash的时候才会使用

3. rehash

rehash:当键很是多,远大于table数组长度时,数组内的每一个值将退化成一条链,hash Table的优点将不复存在,因而须要进行rehash操做,对hash table进行扩容,将比率尽可能维持在1:1左右

rehash触发的条件有两种:1. 键与数组长度比率ratio>=1 && dict_can_resize为真

                                    2. ratio>=dict_force_resize_ratio dict_force_resize_ratio是强制改变大小的比率

当数据库执行后台持久化任务时,为了最大化利用系统的copy on write机制,程序会暂时将dict_can_resize置为假,避免执行天然resize,总而言之就是为了效率

4. 字典的收缩

与rehash相反

收缩操做是程序手动执行的,扩展操做是自动执行的,收缩程序决定填充率是多少的时候来执行收缩程序

对哈希表的扩展和收缩都是分屡次、渐进式的进行的

4、跳跃表

跳跃表是一个有层次的链表,增删改查的时间复杂度都是O(logN)

跳跃表解释:http://blog.jobbole.com/111731/ 

2019-03-07  11:13:52  有时间再看吧 得有输出才行啊 干点活吧

跳跃表是为了提升链表的增删改查,对于一个链表来说,选出一些领导者在上一层,因而在增删改查的时候,首先在上一层进行选择区间以后再下沉到下一层,提升了效率

1. 跳跃表的实现

跳跃表的实现

typedef struct zskiplist {

    // 头节点,尾节点
    struct zskiplistNode *header, *tail;

    // 节点数量
    unsigned long length;

    // 目前表内节点的最大层数
    int level;

} zskiplist;

 跳跃表节点(层)的实现

typedef struct zskiplistNode {

    // member 对象
    robj *obj;

    // 分值
    double score;

    // 后退指针
    struct zskiplistNode *backward;

    // 层
    struct zskiplistLevel {

        // 前进指针
        struct zskiplistNode *forward;

        // 这个层跨越的节点数量
        unsigned int span;

    } level[];

} zskiplistNode;

 

Q&A:

为何Redis要使用C而不是C++的STL容器?

猜测:多是基于内存或者效率的考虑吧

SDS如何实现二进制安全的?

跳跃表与平衡树比较?

相关文章
相关标签/搜索