redis 源代码之数据结构(3)--hash表实现

本文详细介绍了Redis中Hash表的数据结构、实现原理及其自动扩容机制。重点探讨了Hash函数选择及冲突解决方法,并通过具体代码展示了如何进行渐进式rehash。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ash表应用范围很广,实现一个hash表有两个重要因素。1,hash函数的选择,很多研究人员都给出了性能卓越的函数;2解决冲突,最常见的是链表的方法,还有开放定址法等方法。redis的hash表(在dict.c dict.h中)用的hash函数是Thomas Wang's 32 bit Mix Function 和MurmurHash2,整个hash实现相当精致而且它最大的特色在于可以实现自动扩容,这样可以解决负载因子过大产生的问题。整个redis hash内存布局如下 



redis  hash的结构体定义如下

  1. typedef struct dictEntry {
  2. void *key;
  3. union {
  4. void *val;
  5. uint64_t u64;
  6. int64_t s64;
  7. } v;
  8. struct dictEntry *next;
  9. } dictEntry; //此处定义了hash表中的一个节点,key/value/下一个节点指针
  10. typedef struct dictType {
  11. unsigned int (*hashFunction)(const void *key); //将key生成一个hash值 #1
  12. void *(*keyDup)(void *privdata, const void *key); //存储key值 #2
  13. void *(*valDup)(void *privdata, const void *obj); //存储value #3
  14. int (*keyCompare)(void *privdata, const void *key1, const void *key2);//比较两个key #4
  15. void (*keyDestructor)(void *privdata, void *key); //删除key的内容 #5
  16. void (*valDestructor)(void *privdata, void *obj); // 删除val #6
  17. } dictType; //操作hash的几个基本函数
  18. /* This is our hash table structure. Every dictionary has two of this as we
  19. * implement incremental rehashing, for the old to the new table. */
  20. typedef struct dictht {
  21. dictEntry **table;
  22. unsigned long size; //hash表的大小(总为2的n次幂)
  23. unsigned long sizemask; //实际为size - 1,这样就可以直接对sizemask进行取模获得桶的位置
  24. unsigned long used; //hash表中已经使用的桶数
  25. } dictht;
  26. typedef struct dict {
  27. dictType *type;
  28. void *privdata;
  29. dictht ht[2];//有两个hash表,一开始新增加的元素都会塞到ht[0]中去,当负载因子(元素数目/桶数)达到一定的阈值(dict_force_resize_ratio = 5),就会扩容
  30. int rehashidx; /* rehashing not in progress if rehashidx == -1 */
  31. int iterators; /* number of iterators currently running ,redis限制有迭代器(iterators > 0)的时候,禁止rehash*/
  32. } dict;
  33. /* If safe is set to 1 this is a safe iterator, that means, you can call
  34. * dictAdd, dictFind, and other functions against the dictionary even while
  35. * iterating. Otherwise it is a non safe iterator, and only dictNext()
  36. * should be called while iterating. */
  37. typedef struct dictIterator {
  38. dict *d;
  39. int table, index, safe;
  40. dictEntry *entry, *nextEntry;
  41. } dictIterator;

1,hash表的创建

  1. dict *dictCreate(dictType *type,
  2. void *privDataPtr)
  3. {
  4. dict *d = zmalloc(sizeof(*d));//zmalloc是redis对malloc的封装(用的jemalloc库)
  5. _dictInit(d,type,privDataPtr);//privDataPtr还不知道有什么用,_dictInit主要对dict结构体内的数据进行初始化,并调用_dictReset初始化ht[0],ht[1]
  6. return d;
  7. }
2,向hash表添加元素

创建hash表的时候,并没有申请内存空间,当增加一个key的时候,才会真正划分hash表的内存。

  1. int dictAdd(dict *d, void *key, void *val)
  2. {
  3. dictEntry *entry = dictAddRaw(d,key);
  4. if (!entry) return DICT_ERR;
  5. dictSetVal(d, entry, val);
  6. return DICT_OK;
  7. }
增加key的函数调用链:dictAdd->dictAddRaw->_dictKeyIndex(这个主要获取该key在桶中的位置,如果该key已经存在,则返回-1)->dictSetKey

若正在处于rehash中,则在ht[1]表中插入key,否则只在ht[0]中插入key。

  1. static int _dictKeyIndex(dict *d, const void *key)
  2. {
  3. //...
  4. /* 这里会进行hash桶的内存分配*/
  5. if (_dictExpandIfNeeded(d) == DICT_ERR)
  6. return -1;
  7. /* 计算该key所在的桶位置 */
  8. h = dictHashKey(d, key);
  9. for (table = 0; table <= 1; table++) {
  10. //进行key的检查,确定没有重复的key,有的话,直接返回-1
  11. }
_dictKeyIndex会调用_dictExpandIfNeeded进行扩容, _dictExpandIfNeeded内部调用dictExpand,dictExpand会声明一个dictht变量,如果ht[0]的table为NULL,就用该变量初始化ht[0]的,否则就初始化ht[1],并将rehashidx设置为0,为rehash做准备。

3,redis的hash表实现rehash

  1. /* 执行n步rehash,将ht[0] n个桶内容重新hash到ht[1]的n个桶,如果rehash完毕,则交换ht[0]和ht[1]的指针,并返回0,没rehash完毕,就返回1 */
  2. int dictRehash(dict *d, int n) {
  3. if (!dictIsRehashing(d)) return 0;
  4. while(n--) {
  5. dictEntry *de, *nextde;
  6. /* Check if we already rehashed the whole table... */
  7. if (d->ht[0].used == 0) {
  8. zfree(d->ht[0].table);
  9. d->ht[0] = d->ht[1];
  10. _dictReset(&d->ht[1]);
  11. d->rehashidx = -1;
  12. return 0;
  13. }
  14. /* Note that rehashidx can't overflow as we are sure there are more
  15. * elements because ht[0].used != 0 */
  16. assert(d->ht[0].size > (unsigned)d->rehashidx);
  17. while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;//跳过空桶
  18. de = d->ht[0].table[d->rehashidx]; //一个桶的第一个元素
  19. /* Move all the keys in this bucket from the old to the new hash HT */
  20. while(de) {
  21. unsigned int h;
  22. nextde = de->next;
  23. /* Get the index in the new hash table */
  24. h = dictHashKey(d, de->key) & d->ht[1].sizemask; //重新计算hash值,并计算出key在ht[1]桶的位置
  25. de->next = d->ht[1].table[h];
  26. d->ht[1].table[h] = de;
  27. d->ht[0].used--;
  28. d->ht[1].used++;
  29. de = nextde;
  30. }
  31. d->ht[0].table[d->rehashidx] = NULL; //清空链头
  32. d->rehashidx++;
  33. }
  34. return 1;
  35. }
真正rehash的过程并不是一次就完成的,如果一百万个key进行rehash,会导致整个服务卡在rehash 的过程上,导致局部过热,因此,作者渐进的rehash,也就是将rehahs的操作平摊到dictAddRaw(增加key), dictGenericDelete(删除key),dictFind(找到key), dictGetRandomKey(随机获得一个key)这些操作中,rehash更加平滑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值