Redis中的字典——《Redis设计与实现》读书笔记

字典

简介

因为C语言没有内置字典结构,所以Redis自己构建了字典的实现。字典在Redis应用十分广泛,对数据库的增、删、改操作都是在字典的操作之上的,比如:SET msg “hello world”。

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

组成

字典在Redis中由 dict.h/dict 定义
字典的结构体:

typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

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

} dict;

解释:

type保存了针对字典不同用途的函数
privdata针对上面的函数的参数列表
哈希表 ht[0] 是日常插入删除应用的哈希表
哈希表 ht[1] 是对ht[0]进行rehash时才使用的

字典中的哈希表

字典使用哈希表作为底层实现
一个哈希表包含多个哈希节点
每个哈希表节点保存一个键值对

哈希表在 dict.h/dictht 中定义,结构体如下:

typedef struct dictht {

    // 哈希表数组
    dictEntry **table;

    // 哈希表大小
    unsigned long size;

    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;

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

} dictht;

一个空哈希表例子:
在这里插入图片描述

哈希表中的哈希表节点

结构体定义

typedef struct dictEntry {

    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;

} dictEntry;

解释:

v值可以是指针、无符号64位整数、有符号64位整数 next是一个指向下一个哈希表节点的指针在拉链法中解决冲突

一个哈希表及哈希表节点例子:
在这里插入图片描述

哈希算法

算法流程:

在对字典添加新的键值对时:

  1. 根据键的值算出哈希值 :hash = dict->type->hashFunction(key);
  2. 根据哈希值算出索引值:index = hash & dict->ht[x].sizemask;
  3. 将键值对插入到哈希表中对应索引的哈希节点处

解决冲突:
使用链地址法解决冲突

文字描述:当要添加新键值对(哈希表节点)时,计算出该键值对的索引值后,发现哈希表对应的索引处已经有一个哈希表节点,那么就把新的哈希表节点对next指针指向该哈希表节点,并且让哈希表索引指向新的哈希表节点

图片描述:
在这里插入图片描述
在这里插入图片描述

Rehash

扩展或者收缩哈希表

扩展

扩展条件:

  • 服务器目前没有在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 1 ;
  • 服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 5 ;

原因:在执行 BGSAVE 命令或 BGREWRITEAOF 命令的过程中, Redis 需要创建当前服务器进程的子进程, 而大多数操作系统都采用写时复制(copy-on-write)技术来优化子进程的使用效率, 所以在子进程存在期间, 服务器会提高执行扩展操作所需的负载因子, 从而尽可能地避免在子进程存在期间进行哈希表扩展操作, 这可以避免不必要的内存写入操作, 最大限度地节约内存。

扩展过程:

  1. 为ht[1]分配空间,大小为第一个大于ht[0]已有节点的数量2的2的n次幂
    比如:ht[0]已经有的节点数量为7,则为ht[1]分配空间16,因为16为2的4次方而且大于7
    2
  2. 将保存在ht[0]中的所有键值对重新计算哈希值和索引值转移到ht[1]中
  3. 迁移完毕,释放ht[0],将ht[0]的指针指向ht[1],并且为ht[1]新建一个空哈希表

收缩:

收缩条件:

当哈希表的负载因子小于 0.1 时

收缩过程:

  1. 为ht[1]分配空间,大小为第一个大于ht[0]已有节点的数量的2的n次幂
    比如:ht[0]已经有的节点数量为7,则为ht[1]分配空间8,因为8为2的3次方而且大于7
  2. 将保存在ht[0]中的所有键值对重新计算哈希值和索引值转移到ht[1]中
  3. 迁移完毕,释放ht[0],将ht[0]的指针指向ht[1],并且为ht[1]新建一个空哈希表

渐进式rehash

在Redis中的rehash不是一步完成的,如果哈希表很大,那么rehash过程很耗时,一步完成会阻塞数据库的增删改查。

过程:

  1. 在进行rehash时,将rehash值从-1置为0
  2. 每次转移一个哈希表节点,rehash+1
  3. 直到rehash完毕,将rehash值置为-1

渐进式rehash过程中新增的节点都会添加到ht[1]中

主要API

在这里插入图片描述

本章思维导图

PNG文件
Xmind文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值