Redisbook学习笔记(1)字典(3)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介:

渐进式rehash

在上一节,我们了解了字典的rehash 过程,需要特别指出的是,rehash 程序并不是在激活之

后就马上执行直到完成的,而是分多次、渐进式地完成的。

假设这样一个场景:在一个有很多键值对的字典里,某个用户在添加新键值对时触发了rehash

过程,如果这个rehash 过程必须将所有键值对迁移完毕之后才将结果返回给用户,这样的处理

方式将是非常不友好的。

另一方面,要求服务器必须阻塞直到rehash 完成,这对于Redis 服务器本身也是不能接受的。

为了解决这个问题,Redis 使用了渐进式(incremental)的rehash 方式:通过将rehash 分散

到多个步骤中进行,从而避免了集中式的计算。

渐进式rehash 主要由_dictRehashStep 和dictRehashMilliseconds 两个函数进行:
. _dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动rehash ;
. dictRehashMilliseconds 则由Redis 服务器常规任务程序(server cron job)执行,用
于对数据库字典进行主动rehash ;
_dictRehashStep
每次执行_dictRehashStep ,ht[0]->table 哈希表第一个不为空的索引上的所有节点就会全
部迁移到ht[1]->table 。

在rehash 开始进行之后(d->rehashidx 不为-1),每次执行一次添加、查找、删除操作,
_dictRehashStep 都会被执行一次:

wKiom1Lfx8-xIvSUAAFG-ophSV4572.jpg

因为字典会保持哈希表大小和节点数的比率在一个很小的范围内,所以每个索引上的节点数量
不会很多(从目前版本的rehash 条件来看,平均只有一个,最多通常也不会超过五个),所以
在执行操作的同时,对单个索引上的节点进行迁移,几乎不会对响应时间造成影响。
dictRehashMilliseconds
dictRehashMilliseconds 可以在指定的毫秒数内,对字典进行rehash 。
当Redis 的服务器常规任务执行时,dictRehashMilliseconds 会被执行,在规定的时间内,
尽可能地对数据库字典中那些需要rehash 的字典进行rehash ,从而加速数据库字典的rehash
进程(progress)。
其他措施
在哈希表进行rehash 时,字典还会采取一些特别的措施,确保rehash 顺利、正确地进行:
 因为在rehash 时,字典会同时使用两个哈希表,所以在这期间的所有查找、删除等操作,
除了在ht[0] 上进行,还需要在ht[1] 上进行。
 在执行添加操作时,新的节点会直接添加到ht[1] 而不是ht[0] ,这样保证ht[0] 的节
点数量在整个rehash 过程中都只减不增。

字典的收缩
上面关于rehash 的章节描述了通过rehash 对字典进行扩展(expand)的情况,如果哈希表的
可用节点数比已用节点数大很多的话,那么也可以通过对哈希表进行rehash 来收缩(shrink)
字典。
收缩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 的大小:
. 如果rehash 是扩展操作,那么ht[1]->table 比ht[0]->table 要大;
. 如果rehash 是收缩操作,那么ht[1]->table 比ht[0]->table 要小;
字典的收缩规则由redis.c/htNeedsResize 函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 检查字典的使用率是否低于系统允许的最小比率
**
是的话返回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% 时,程
序就可以对这个字典进行收缩操作了。
字典收缩和字典扩展的一个区别是:
. 字典的扩展操作是自动触发的(不管是自动扩展还是强制扩展);
. 而字典的收缩操作则是由程序手动执行。
因此,使用字典的程序可以决定何时对字典进行收缩:
. 当字典用于实现哈希键的时候,每次从字典中删除一个键值对,程序就会执行一次
htNeedsResize 函数,如果字典达到了收缩的标准,程序将立即对字典进行收缩;
. 当字典用于实现数据库键空间(key space) 的时候, 收缩的时机由
redis.c/tryResizeHashTables 函数决定.

字典其他操作
除了添加操作和伸展/收缩操作之外,字典还定义了其他一些操作,比如常见的查找、删除和更
新。
因为链地址法哈希表实现的相关信息可以从任何一本数据结构或算法书上找到,这里不再对字
典的其他操作进行介绍,不过前面对创建字典、添加键值对、收缩和扩展rehash 的讨论已经涵
盖了字典模块的核心内容。

字典的迭代
字典带有自己的迭代器实现——对字典进行迭代实际上就是对字典所使用的哈希表进行迭代:
. 迭代器首先迭代字典的第一个哈希表,然后,如果rehash 正在进行的话,就继续对第二
个哈希表进行迭代。
. 当迭代哈希表时,找到第一个不为空的索引,然后迭代这个索引上的所有节点。
. 当这个索引迭代完了,继续查找下一个不为空的索引,如此循环,一直到整个哈希表都迭
代完为止。
整个迭代过程可以用伪代码表示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def iter_dict(dict):
// 迭代0 号哈希表
iter_table(ht[0]->table)
// 如果正在执行rehash ,那么也迭代1 号哈希表
if  dict.is_rehashing(): iter_table(ht[1]->table)
def iter_table(table):
// 遍历哈希表上的所有索引
for  index in table:
// 跳过空索引
if  table[index].empty():
continue
// 遍历索引上的所有节点
for  node in table[index]:
// 处理节点
do_something_with(node)

字典的迭代器有两种:
 安全迭代器:在迭代进行过程中,可以对字典进行修改。
 不安全迭代器:在迭代进行过程中,不对字典进行修改。
以下是迭代器的数据结构定义:

1
2
3
4
5
6
7
8
9
10
11
/*
* 字典迭代器
*/
typedef  struct  dictIterator {
dict *d;  // 正在迭代的字典
int  table,  // 正在迭代的哈希表的号码(0 或者1)
index,  // 正在迭代的哈希表数组的索引
safe;  // 是否安全?
dictEntry *entry,  // 当前哈希节点
*nextEntry;  // 当前哈希节点的后继节点
} dictIterator;

小结
 字典由键值对构成的抽象数据结构。
 Redis 中的数据库和哈希键都基于字典来实现。
 Redis 字典的底层实现为哈希表,每个字典使用两个哈希表,一般情况下只使用0 号哈希
表,只有在rehash 进行时,才会同时使用0 号和1 号哈希表。
 哈希表使用链地址法来解决键冲突的问题。
 Rehash 可以用于扩展或收缩哈希表。
 对哈希表的rehash 是分多次、渐进式地进行的。






















本文转自shayang8851CTO博客,原文链接:http://blog.51cto.com/janephp/1353930,如需转载请自行联系原作者

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6天前
|
存储 NoSQL Java
Redis入门到通关之数据结构解析-Dict
Redis入门到通关之数据结构解析-Dict
13 2
|
11月前
|
索引 Python
Python编程 字典的常用操作
Python编程 字典的常用操作
65 0
|
Go
go语言基础数据结构学习 ---- 字典(map)
go语言基础数据结构学习 ---- 字典(map)
86 0
|
存储 开发者 Python
字典的基本使用|学习笔记
快速学习字典的基本使用
68 0
|
存储 开发者 Python
字典的练习2|学习笔记
快速学习字典的练习2
65 0
|
索引 Python
Python语法之字典
前面的文章中已经介绍了循环语句for与while,以及中断语句break与continue。今天一起给小伙伴们介绍一下Python中的“字典”,这里的字典和我们平时所用的字典不一样,这个是Python中的一个重点语法。
57 0
|
Swift 索引
Swift实用小册04:数组、集合和字典的使用
Swift实用小册04:数组、集合和字典的使用
204 0
Swift实用小册04:数组、集合和字典的使用
|
开发者 Python
字典的使用_2| 学习笔记
快速学习字典的使用_2
|
开发者 Python
字典的使用1| 学习笔记
快速学习字典的使用1