• redis学习笔记二


    双链表

    双端链表作为一种通用的数据结构,在Redis 内部使用得非常多:它既是Redis 列表结构的底
    层实现之一,还被大量Redis 模块所使用,用于构建Redis 的其他功能。

    应用

    • 实现Redis 的列表类型;

    双端链表还是Redis 列表类型的底层实现之一,当对列表类型的键进行操作——比如执行
    RPUSH 、LPOP 或LLEN 等命令时,程序在底层操作的可能就是双端链表。

    Redis 列表使用两种数据结构作为底层实现:

    1. 双端链表

    2. 压缩列表

    因为双端链表占用的内存比压缩列表要多,所以当创建新的列表键时,列表会优先考虑使用压
    缩列表作为底层实现,并且在有需要的时候,才从压缩列表实现转换到双端链表实现。

    • Redis 自身功能的构建

      除了实现列表类型以外,双端链表还被很多Redis 内部模块所应用:

    1. 事务模块使用双端链表来按顺序保存输入的命令;
    2. 服务器模块使用双端链表来保存多个客户端;
    3.  订阅/发送模块使用双端链表来保存订阅模式的多个客户端;
    4.  事件模块使用双端链表来保存时间事件(time event);
    • 双端链表的实现

    双端链表的实现由listNode 和list 两个数据结构构成,下图展示了由这两个结构组成的一
    个双端链表实例:

    迭代器

    Redis 为双端链表实现了一个迭代器,这个迭代器可以从两个方向对双端链表进行迭代:
    • 沿着节点的next 指针前进,从表头向表尾迭代;
    • 沿着节点的prev 指针前进,从表尾向表头迭代;

    typedef struct listIter {
    // 下一节点
    listNode *next;
    // 迭代方向
    int direction;

    } listIter;

    direction 记录迭代应该从那里开始:
    • 如果值为adlist.h/AL_START_HEAD ,那么迭代器执行从表头到表尾的迭代;
    • 如果值为adlist.h/AL_START_TAIL ,那么迭代器执行从表尾到表头的迭代;

    字典

    它是一种抽象数据结构,由一集键值对(key-value pairs)组成,各个键值对的键各不相同,程序可以将新的键值对添加到字典中,或者基于键进行查找、更新或删除等操作。

    字典的应用

    1. 实现数据库键空间(key space);

    Redis 是一个键值对数据库,数据库中的键值对就由字典保存:每个数据库都有一个与之相对
    应的字典,这个字典被称之为键空间(key space)。

    当用户添加一个键值对到数据库时(不论键值对是什么类型),程序就将该键值对添加到键空
    间;当用户从数据库中删除一个键值对时,程序就会将这个键值对从键空间中删除;等等。

    2. 用作Hash 类型键的其中一种底层实现;

    Redis 的Hash 类型键使用以下两种数据结构作为底层实现:
    1. 字典;
    2. 压缩列表;

    因为压缩列表比字典更节省内存,所以程序在创建新Hash 键时,默认使用压缩列表作为底层
    实现,当有需要时,程序才会将底层实现从压缩列表转换到字典。

    Redis 选择了高效且实现简单的哈希表作为字典的底层实现。

    字典的实现:

    typedef struct dictht {
    // 哈希表节点指针数组(俗称桶,bucket)
    dictEntry **table;
    // 指针数组的大小
    unsigned long size;
    // 指针数组的长度掩码,用于计算索引值
    unsigned long sizemask;
    // 哈希表现有的节点数量
    unsigned long used;
    } dictht;

    table 属性是一个数组,数组的每个元素都是一个指向dictEntry 结构的指针。

    每个dictEntry 都保存着一个键值对,以及一个指向另一个dictEntry 结构的指针:

    /*
    * 哈希表节点
    */
    typedef struct dictEntry {
    // 键
    void *key;
    1.3. 字典15
    Redis 设计与实现, 第一版
    // 值
    union {
    void *val;
    uint64_t u64;
    int64_t s64;
    } v;
    // 链往后继节点
    struct dictEntry *next;
    } dictEntry;

    在上图的字典示例中,字典虽然创建了两个哈希表,但正在使用的只有0 号哈希表,这说明字典未进行rehash 状态。

    碰撞的处理方法:

    假设现在有三个借点的哈希表如下:

    对于一个新的键值对key4 和value4 ,如果key4 的哈希值和key1 的哈希值相同,那么它们
    将在哈希表的0 号索引上发生碰撞。通过将key4-value4 和key1-value1 两个键值对用链表连接起来,就可以解决碰撞的问题:

    添加新键值对时触发了rehash 操作:

    对于使用链地址法来解决碰撞问题的哈希表dictht 来说,哈希表的性能依赖于它的大小(size
    属性)和它所保存的节点的数量(used 属性)之间的比率:
    • 比率在1:1 时,哈希表的性能最好;
    • 如果节点数量比哈希表的大小要大很多的话,那么哈希表就会退化成多个链表,哈希表
    本身的性能优势就不再存在;

    为了在字典的键值对不断增多的情况下保持良好的性能,字典需要对所使用的哈希表(ht[0])进行rehash 操作:在不修改任何键值对的情况下,对哈希表进行扩容,尽量将比率维持在1:1

    左右。

    dictAdd 在每次向字典添加新键值对之前,都会对哈希表ht[0] 进行检查,对于ht[0] 的
    size 和used 属性,如果它们之间的比率ratio = used / size 满足以下任何一个条件的话,
    rehash 过程就会被激活:
    1. 自然rehash :ratio >= 1 ,且变量dict_can_resize 为真。
    2. 强制rehash : ratio 大于变量dict_force_resize_ratio (目前版本中,
    dict_force_resize_ratio 的值为5 )。

    Note: 什么时候dict_can_resize 会为假?

    一个数据库就是一个字典,数据库里的哈希类型键
    也是一个字典,当Redis 使用子进程对数据库执行后台持久化任务时(比如执行BGSAVE
    或BGREWRITEAOF 时), 为了最大化地利用系统的copy on write 机制, 程序会暂时将
    dict_can_resize 设为假,避免执行自然rehash ,从而减少程序对内存的触碰(touch)。
    当持久化任务完成之后,dict_can_resize 会重新被设为真。

    字典的rehash 操作实际上就是执行以下任务:
    1. 创建一个比ht[0]->table 更大的ht[1]->table ;
    2. 将ht[0]->table 中的所有键值对迁移到ht[1]->table ;
    3. 将原有ht[0] 的数据清空,并将ht[1] 替换为新的ht[0] ;
    经过以上步骤之后,程序就在不改变原有键值对数据的基础上,增大了哈希表的大小。

    渐进式rehash

    渐进式rehash 主要由_dictRehashStepdictRehashMilliseconds 两个函数进行:
    • _dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动rehash ;
    • dictRehashMilliseconds 则由Redis 服务器常规任务程序(server cron job)执行,用
    于对数据库字典进行主动rehash ;

    字典的收缩

    收缩rehash 和上面展示的扩展rehash 的操作几乎一样,它执行以下步骤:
    1. 创建一个比ht[0]->table 小的ht[1]->table ;
    2. 将ht[0]->table 中的所有键值对迁移到ht[1]->table ;
    3. 将原有ht[0] 的数据清空,并将ht[1] 替换为新的ht[0] ;
    扩展rehash 和收缩rehash 执行完全相同的过程,一个rehash 是扩展还是收缩字典,关键在于
    新分配的ht[1]->table 的大小:

    字典的收缩规则由redis.c/htNeedsResize 函数定义:
    /*
    * 检查字典的使用率是否低于系统允许的最小比率
    **
    是的话返回1 ,否则返回0 。
    */
    int htNeedsResize(dict *dict) {
    long long size, used;
    // 哈希表已用节点数量
    size = dictSlots(dict);
    // 哈希表大小
    used = dictSize(dict);
    // 当哈希表的大小大于DICT_HT_INITIAL_SIZE
    // 并且字典的填充率低于REDIS_HT_MINFILL 时
    // 返回1
    return (size && used && size > DICT_HT_INITIAL_SIZE &&
    (used*100/size < REDIS_HT_MINFILL));
    }

    在默认情况下,REDIS_HT_MINFILL 的值为10 ,也即是说,当字典的填充率低于10% 时,
    序就可以对这个字典进行收缩操作了。

    字典的其他操作和字典的迭代.....(略)

    跳跃表(很牛逼的一种表)....(略)

    操作有ZADD ZRANGE等。

  • 相关阅读:
    MySQL 数据库中 ceil()、floor()、round()、trunc() 函数用法
    BUG 的几种状态
    MySQL 数据库基本知识点
    测试用例设计方法之等价类和边界值
    时间同步(NTP/PTP)技术原理介绍
    智慧校园网络架设GPS北斗时钟同步系统
    制药行业SCADA系统架设NTP时钟服务器(网络时间服务器)
    关于M1 Mac如何设置打开终端的快捷键
    关于M1Mac每次都要执行source ~/.bash_profile后,配置的环境变量才生效
    关于Mac M1版安装中国蚁剑
  • 原文地址:https://www.cnblogs.com/lukexwang/p/4629240.html
Copyright © 2020-2023  润新知