前言
字典即 hash 表
是很多语言都有的数据结构,例如 Java
中的 HashMap
。字典在日常开发中具有广泛的应用,并且是 Redis
服务的基石,在 Redis 数据库的实现、发布订阅、键值过期等场景有很重要的应用。本文从源码层面介绍 Redis 的结构与操作,深入理解其内部原理。
字典结构
type
保存当前字典的 hash 函数、key 比较函数等信息。ht_table
存储数据,是一个二维数组,用于渐进式哈希。数组的每个元素dictEntry
是一个二维指针,可以看作是一个一维数组,数组每个元素是一个dictEntry
类型的指针。rehashidx
用于指示字典是否处于渐进式哈希状态。
dictType
对于 dictType
关心两个函数
hashFunction
计算redis
对象的哈希值。keyCompare
是字典key
的比较函数。
字典操作
初始化
数据库字的典初始化是在 Redis
服务启动的时候。字典初始化很简单,分配内存,给相关属性赋值。
添加逻辑
Redis
字典的添加逻辑主要分成四个部分,查找位置、扩容重哈希,添加数据。主要逻辑在查找位置和扩容重哈希。
插入元素
首先计算插入 key
的哈希值,然后遍历哈希表,查找插入的位置,详细逻辑见注释。查找的逻辑也是类似(除了需要判断是否需要扩容),见 dictEntry *dictFind(dict *d, const void *key)
。
函数调用结果有两种情况
- 第一种是根据
key
查找到已经存在的元素。函数返回值是NULL
,传入参数设置成existing
。 - 第二种是没有查找到元素,函数返回
idx
所在的元素,最后依据局部性原理使用头插法添加数据。注意这里返回的是 hash 表上 bucket 桶位置的地址。
其次是根据查找的位置,将新的 key
添加进去,方法见 dictInsertAtPosition
。主要代码在下面。这块逻辑比较简单。
扩容重哈希
Redis
字典的扩容分为 hash 数组的扩容和元素重 hash。数组的扩容的代码在下面
为了保证字典扩容不影响业务处理,字典的重 hash 不是一步完成。通常在字典的其他操作中分多次顺带完成元素的移动。常常可以看到下面的代码。
函数 dictRehash(d,1)
是重 hash 的主要逻辑。字典属性 rehashidx
记录本次要移动的元素索引,此索引上的所有元素都会被移动。如果所有元素都移动完成,那么会将重新分配的hash表作为正在使用的hash表。
总结
- redis 字典数据对象应用广泛。
- redis 字典设计尤其独到的一面,比如为了不阻塞业务端请求使用渐进式hash。