Redis核心结构字典
2014-09-20 12:52
183 查看
Redis 是支持多key-value数据库,并用 RedisDb 来表示一个key-value数据库. redisServer 中有一个 redisDb *db成员变量, RedisServer 在初始化时,会根据配置文件的 db 数量来创建一个
redisDb 数组. 客户端在连接后,通过 SELECT 指令来选择一个 reidsDb,如果不指定,则缺省是redisDb数组的第1个(即下标是 0 ) redisDb. 一个客户端在选择 redisDb 后,其后续操作都是在此 redisDb 上进行的. 下面是redisDb结构体
其中redisDb结构中包含指向dict结构的指针,这个指针指向了字典结构,下面我们具体分析字典结构的用途、组成和一些操作
1.实现数据库的键空间
2.用作hash类型键的一种实现
实现数据库键空间
Redis 是一个键值对数据库,数据库中的键值对就由字典保存:每个数据库都有一个与之相对
应的字典,这个字典被称之为键空间(key space)。
当用户添加一个键值对到数据库时(不论键值对是什么类型),程序就将该键值对添加到键空
间;当用户从数据库中删除一个键值对时,程序就会将这个键值对从键空间中删除;等等
用作 Hash 类型键的其中一种底层实现
Redis 的 Hash 类型键使用以下两种数据结构作为底层实现:
1. 字典;
2. 压缩列表;
下面给出dict结构的定义
其中dict结构中包含一个具有两个hash表的数组,hash表用dictht结构体表示,下面是其具体实现:
table实际是一个数组,数组的元素是指向dictEntry的指针,而每一个dictEntry保存着一个键值对,还有一个指向另一个dictEntry结构的指针
其中多个dictEnty通过其中的next指针可以构成链表,而redis中的hash表使用链接地址法处理碰撞,当多个不同的键具有相同的哈希值时,采用链接的方法将它们连接成一个链表
下图是整个dict字典结构的图示:
• 比率在 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
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 都会被执行一次:
dictRehashMilliseconds 可以在指定的毫秒数内,对字典进行 rehash
当 Redis 的服务器常规任务执行时(serverCrom),dictRehashMilliseconds 会被执行,在规定的时间内,尽可能地对数据库字典中那些需要 rehash 的字典进行 rehash ,从而加速数据库字典的 rehash进程(progress)
• 因为在 rehash 时,字典会同时使用两个哈希表,所以在这期间的所有查找、删除等操作,除了在 ht[0] 上进行,还需要在 ht[1] 上进行
• 在执行添加操作时,新的节点会直接添加到 ht[1] 而不是 ht[0] ,这样保证 ht[0] 的节点数量在整个 rehash 过程中都只减不增
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:
其中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字典结构的图示:
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 都会被执行一次:
dictRehashMilliseconds 可以在指定的毫秒数内,对字典进行 rehash
当 Redis 的服务器常规任务执行时(serverCrom),dictRehashMilliseconds 会被执行,在规定的时间内,尽可能地对数据库字典中那些需要 rehash 的字典进行 rehash ,从而加速数据库字典的 rehash进程(progress)
其他注意事项
在哈希表进行 rehash 时,字典还会采取一些特别的措施,确保 rehash 顺利、正确地进行:• 因为在 rehash 时,字典会同时使用两个哈希表,所以在这期间的所有查找、删除等操作,除了在 ht[0] 上进行,还需要在 ht[1] 上进行
• 在执行添加操作时,新的节点会直接添加到 ht[1] 而不是 ht[0] ,这样保证 ht[0] 的节点数量在整个 rehash 过程中都只减不增
相关文章推荐
- redis源码分析-dict(字典结构)
- redis 源码学习(核心数据结构剖析)
- Redis 字典结构实现分析
- Redis源码剖析(三)字典结构的设计与实现
- redis 系列5 数据结构之字典(上)
- Redis核心数据结构和实现
- redis中的字典与hash结构
- redis 系列6 数据结构之字典(下)
- Redis基础结构之hashtable(字典实现)
- Redis源码解析——字典结构
- Redis代码分析之核心数据结构
- Redis-数据结构-3-字典
- Redis之3-字典结构
- 解读Redis dict核心数据结构
- Redis源码剖析和注释(三)--- Redis 字典结构
- 【Redis】初探dict字典原理的实现(一)
- Redis的字典(dict)rehash过程源代码解析
- nmap核心结构概述
- 第十五章 Linux核心数据结构
- 数据结构——字典(JavaScript)