天天看点

Redis的字典(dict)rehash过程源码解析

redis的内存存储结构是个大的字典存储,也就是我们通常说的哈希表。redis小到可以存储几万记录的cache,大到可以存储几千万甚至上亿的记录(看内存而定),这充分说明redis作为缓冲的强大。redis的核心数据结构就是字典(dict),dict在数据量不断增大的过程中,会遇到hash(key)碰撞的问题,如果dict不够大,碰撞的概率增大,这样单个hash 桶存储的元素会越来愈多,查询效率就会变慢。如果数据量从几千万变成几万,不断减小的过程,dict内存却会造成不必要的浪费。redis的dict在设计的过程中充分考虑了dict自动扩大和收缩,实现了一个称之为rehash的过程。使dict出发rehash的条件有两个:

1)总的元素个数 除 dict桶的个数得到每个桶平均存储的元素个数(pre_num),如果 pre_num > dict_force_resize_ratio,就会触发dict 扩大操作。dict_force_resize_ratio = 5。

2)在总元素 * 10 < 桶的个数,也就是,填充率必须<10%,

dict便会进行收缩,让total / bk_num 接近 1:1。

dict rehash扩大流程:

Redis的字典(dict)rehash过程源码解析

源代码函数调用和解析:

dictaddraw->_dictkeyindex->_dictexpandifneeded->dictexpand,这个函数调用关系是需要扩大dict的调用关系,

_dictkeyindex函数代码:

_dictexpandifneeded函数代码解析:

dict rehash缩小流程:

Redis的字典(dict)rehash过程源码解析

servercron->tryresizehashtables->dictresize->dictexpand

servercron函数是个心跳函数,调用tryresizehashtables段为:

tryresizehashtables函数代码分析:

htneedsresize函数是判断是否可以需要进行dict缩小的条件判断,填充率必须>10%,否则会进行缩小,具体代码如下:

dictresize函数代码:

以上两个过程最终调用了dictexpand函数,这个函数主要是产生一个新的hash表(dictht),并让将dict.rehashidx= 0。表示开始进行rehash动作。具体的rehash动作是将ht[0]的数据按照hash隐射的规则重新隐射到 ht[1]上.具体代码如下:

字典dict的rehashidx被设置成0后,就表示开始rehash动作,在心跳函数执行的过程,会检查到这个标志,如果需要rehash,就行进行渐进式rehash动作。函数调用的过程为:

servercron->incrementallyrehash->dictrehashmilliseconds->dictrehash

incrementallyrehash函数代码:

dictrehashmilliseconds函数是按照指定的cpu运算的毫秒数,执行rehash动作,每次一个100个为单位执行。代码如下:

总结,redis的rehash动作是一个内存管理和数据管理的一个核心操作,由于redis主要使用单线程做数据管理和消息效应,它的rehash数据迁移过程采用的是渐进式的数据迁移模式,这样做是为了防止rehash过程太长堵塞数据处理线程。并没有采用memcached的多线程迁移模式。关于memcached的rehash过程,以后再做介绍。从redis的rehash过程设计的很巧,也很优雅。在这里值得注意的是,redis在find数据的时候,是同时查找正在迁移的ht[0]和被迁移的ht[1]。防止迁移过程数据命不中的问题。