Redis核心结构字典

Redis 是支持多key-value数据库,并用 RedisDb 来表示一个key-value数据库. redisServer 中有一个 redisDb *db成员变量, RedisServer 在初始化时,会根据配置文件的 db 数量来创建一个 redisDb 数组. 客户端在连接后,通过 SELECT 指令来选择一个 reidsDb,如果不指定,则缺省是redisDb数组的第1个(即下标是 0 ) redisDb. 一个客户端在选择 redisDb 后,其后续操作都是在此 redisDb 上进行的. 下面是redisDb结构体

  1: 
  2: /*
  3:  * 数据库结构
  4:  */
  5: typedef struct redisDb {
  6:     // key space,包括键值对象
  7:     dict *dict;                 /* The keyspace for this DB */
  8:     // 保存 key 的过期时间
  9:     dict *expires;              /* Timeout of keys with a timeout set */
 10:     // 正因为某个/某些 key 而被阻塞的客户端
 11:     dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */
 12:     // 某个/某些接收到 PUSH 命令的阻塞 key
 13:     dict *ready_keys;           /* Blocked keys that received a PUSH */
 14:     // 正在监视某个/某些 key 的所有客户端
 15:     dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
 16:     // 数据库的号码
 17:     int id;
 18: } redisDb;
 19: 

 

Image011710[1]

其中redisDb结构中包含指向dict结构的指针,这个指针指向了字典结构,下面我们具体分析字典结构的用途、组成和一些操作

Redis中dict的主要应用

dict的主要用途有两个:

1.实现数据库的键空间

2.用作hash类型键的一种实现

实现数据库键空间
Redis 是一个键值对数据库,数据库中的键值对就由字典保存:每个数据库都有一个与之相对
应的字典,这个字典被称之为键空间(key space)。
当用户添加一个键值对到数据库时(不论键值对是什么类型),程序就将该键值对添加到键空
间;当用户从数据库中删除一个键值对时,程序就会将这个键值对从键空间中删除;等等

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

dict的实现

Redis用hash表作为字典的底层实现

下面给出dict结构的定义

  1: 
  2: /*
  3:  * 字典
  4:  *
  5:  * 每个字典使用两个哈希表,用于实现渐进式 rehash
  6:  */
  7: typedef struct dict {
  8: 
  9:     // 特定于类型的处理函数
 10:     dictType *type;
 11: 
 12:     // 类型处理函数的私有数据
 13:     void *privdata;
 14: 
 15:     // 哈希表(2个)
 16:     dictht ht[2];       
 17: 
 18:     // 记录 rehash 进度的标志,值为-1 表示 rehash 未进行
 19:     int rehashidx;
 20: 
 21:     // 当前正在运作的安全迭代器数量
 22:     int iterators;      
 23: 
 24: } dict;
 25: 

其中dict结构中包含一个具有两个hash表的数组,hash表用dictht结构体表示,下面是其具体实现:

  1: 
  2: /*
  3:  * 哈希表
  4:  */
  5: typedef struct dictht {
  6: 
  7:     // 哈希表节点指针数组(俗称桶,bucket)
  8:     dictEntry **table;      
  9: 
 10:     // 指针数组的大小
 11:     unsigned long size;     
 12: 
 13:     // 指针数组的长度掩码,用于计算索引值
 14:     unsigned long sizemask; 
 15: 
 16:     // 哈希表现有的节点数量
 17:     unsigned long used;     
 18: 
 19: } dictht;
 20: 

table实际是一个数组,数组的元素是指向dictEntry的指针,而每一个dictEntry保存着一个键值对,还有一个指向另一个dictEntry结构的指针

  1: 
  2: /*
  3:  * 哈希表节点
  4:  */
  5: typedef struct dictEntry {
  6: 
  7:     // 键
  8:     void *key;
  9: 
 10:     // 值
 11:     union {
 12:         void *val;
 13:         uint64_t u64;
 14:         int64_t s64;
 15:     } v;
 16: 
 17:     // 链往后继节点
 18:     struct dictEntry *next; 
 19: 
 20: } dictEntry;
 21: 

其中多个dictEnty通过其中的next指针可以构成链表,而redis中的hash表使用链接地址法处理碰撞,当多个不同的键具有相同的哈希值时,采用链接的方法将它们连接成一个链表

下图是整个dict字典结构的图示:

image

rehash

使用链地址法来解决碰撞问题的哈希表dictht来说,哈希表的性能依赖于它的大小(size属性)和它所保存的节点的数量(used 属性)之间的比率

• 比率在 1:1 时,哈希表的性能最好;
• 如果节点数量比哈希表的大小要大很多的话,那么哈希表就会退化成多个链表,哈希表本身的性能优势就不再存在

为了在键值对不断增加的情况下继续保持高性能,字典需要对hash表进行rehash,对hash表进行扩容

那么什么情况下会进行hash表的扩容呢?

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

rehash执行过程

字典的 rehash 操作实际上就是执行以下任务:

1. 创建一个比 ht[0]->table 更大的 ht[1]->table(ht[1]的size是ht[0]的两倍) ;

2. 将 ht[0]->table 中的所有键值对迁移到 ht[1]->table

3. 将原有 ht[0] 的数据清空,并将 ht[1] 替换为新的 ht[0]

当然rehash并不是在激活或就立即执行的,而是渐进地、分多次进行的

因为服务器不能一直阻塞等待rehash完成才处理客户到达的命令,所以我们采用渐进的rehash方法:

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

每次执行 _dictRehashStep ,ht[0]->table 哈希表第一个不为空的索引上的所有节点就会全部迁移到 ht[1]->table ,在 rehash 开始进行之后(d->rehashidx 不为 -1),每次执行一次添加、查找、删除操作,_dictRehashStep 都会被执行一次:

image

dictRehashMilliseconds 可以在指定的毫秒数内,对字典进行 rehash

当 Redis 的服务器常规任务执行时(serverCrom),dictRehashMilliseconds 会被执行,在规定的时间内,尽可能地对数据库字典中那些需要 rehash 的字典进行 rehash ,从而加速数据库字典的 rehash进程(progress)

其他注意事项

在哈希表进行 rehash 时,字典还会采取一些特别的措施,确保 rehash 顺利、正确地进行:
• 因为在 rehash 时,字典会同时使用两个哈希表,所以在这期间的所有查找、删除等操作,除了在 ht[0] 上进行,还需要在 ht[1] 上进行
• 在执行添加操作时,新的节点会直接添加到 ht[1] 而不是 ht[0] ,这样保证 ht[0] 的节点数量在整个 rehash 过程中都只减不增

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值