Redis Source Code Read Log( 6. dict details)

介绍

dict 内部实现机制,具体什么是hashmap,这里不介绍。

Redis 内部的 dict 桶采用的是向量,然后拉链采用的是 单链表。关键是桶个数bucket_sizesize 必须是2的次方数。index 获取方式不是取模,而是取低位即按位与 sizemask (由于 size 是2的次方数,那么size减一之后,得到的是一个低位全为1的sizemask),按位与上 sizemask 之后的结果作为 bucket_index

1. hash 算法为 SipHash, 底层实际都是对 string 进行hash

2. 插入方式是: idx = hash(key) & sizemask; buckets[idx] insert head, 单链表头部插入节点的方式; 整体复杂度 0(1)

3. 查询,获取,删除等等,复杂度也都是常量级别。最多遍历一个单链表。Redis没有对拉链长度做特别的控制。比如拉链长度超过 8 或者 16,由单链表转为 红黑树 这样的操作是没有的

4. Redis 内部每个dict都提供了两张 hash table. 并且,每个 dict 内部有个 rehashidx 的变量,作为 rehash过程状态变量,除非在 rehash的过程中,否则,两张hash表其中只有一张表持有有效数据

5. rehash过程实际是在遍历0号 hash table,逐个节点处理的,迁移至 1 号 hash table。Redis的内部实现来看,hash 值应该不会有改变,但是由于 size 的变化,sizemask的变化,会导致取低位结果有变化。如果2倍扩容的情况,原先的拉链可能会被一分为二(新增的一个高位bit是0 或 1 两种情况)。

,扩容, Rehash

当存在 aof 或者 rdb child 子进程存在时,此时不允许 resize。

aof 或者 rdb child 子进程退出,均不存在时,更新 resize 策略,允许 resize。

rehash 过程中,不允许 resize。

初始 SLOTS 4,即 size = 4

Redis dict 并没有提供明显的缩容接口,而是针对 redisDb 中的 dict 以及 expires 两个 dict 进行缩容条件的判断。

dict size 大于 4,并且

利用率:used * 100 / size  < 10.  也就是 利用率 低于 10% 就会触发 resize 操作,此时的 resize,实际进行的是缩容操作

如果利用率 高于 6.25%, 此时 缩容 至原先 size 的四分之一,否则缩容至八分之一或者更少。

最少,SLOT size 也要保持在 4.

相应的检查,在 databasesCron 的定时任务中,周期的执行。

扩容 ( dict, redisDb dict, expires )

前提,不在 rehash 状态中,某则,此时不允许扩容。(其他 dict 数据结构同样有效)

每次 add entry 的时候,都有可能触发扩容,条件如下:

条件一:已经存储的 entry 数量 used,已经达到 SLOT 数量 size,并且此时无 aof/rdb 子进程,触发扩容。

条件二:已经存储的 entry 数量 used,已经达到 SLOT 数量 size,并且即便有子进程,但是 used / size  > 5,即平均每个槽中都已经存储了5个以上entry,强制触发扩容。

此时扩容策略:新的 size 必须是 大于或等于 used * 2 2 的次方数。(4,8,16,32,64,128 ... ...)

条件一与条件二任意一条满足,都会触发扩容操作

databasesCron 定时任务触发的扩容。(只针对 redisDb: dict, expires )

这个触发的条件就是

1. 无子进程

2. 已经存储的 entry 数量 used 数量已经达到了 SLOT 数量的一半以上,新的 size 必须是 大于或等于 used * 2 2 的次方数。(4,8,16,32,64,128 ... ...),新的 size 一般是当前 size的两倍,除非add entry请求激增

这种情况下的扩容,必须以上1. 2. 必须同时满足,才会触发。

 

Rehash

当 dict resize 了之后,就会触发 rehash 过程。一旦触发 rehash,那么 resize 功能就会被 disable 掉,直到 rehash 过程结束。

Redis 每个 dict 有两个 hash table。非 rehash 过程中,只使用0hash table,当 rehash 开始,1hash table 启用,内部开始将 entry 0号往1hash table 进行逐个迁移。每迁移一个,称之为一个 step

迁移发生条件

1. 当每次操作dict,如addfinddelete等,都会触发一个批次的1step的操作 (针对所有 dict)

2. databasesCron 定时任务中(只针对redisDb: dict, expires域的dict),在无子进程的前提下,进行循环,每批次会进行100step ,当循环操作计时累计超过 1 毫秒,那么本次循环结束,否则,一直循环下去。另外,当 dict 与 expires同时需要 rehash 时,优先执行 dict的 rehash 直到 dict rehash 状态结束,才会继续进行 expires的 rehash 的操作。Redis 还为 rehash 还做了一个总开关配置,当 activerehashing 配置设置为 no,即关闭在此 cron 任务中的 rehash 操作,rehash 操作就只能依赖 1 中的方式。默认为 yes, 开启此开关。

Note:

1. 以上1. 2. 是同时进行的。

2. 每批次进行 n (1个或100)step 的操作中,都有有一个打断条件,就是累计遍历 n * 10 的空 SLOT,如果满足这个条件,那么本轮 step 的操作结束。

3. 以上的任务,当所有的 0 号中的 entry 全部迁入 1 table 中,rehash 状态结束。以上任务分支便无法再进入。

4. rehash 结束之后redis进行如下操作:

0 号 hash table free,1 号置为 0号,1号 reset。rehashidx 置为 -1,标志着这一轮 rehash 过程的结束。

rehash 过程中的 dict 访问

新增 entry,此时是加入到 1 号 hash table中的,查询,修改value,delete等操作,都是0号1号可能均会访问到(其实很容易理解)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值