百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

redis基础结构–词典(redis 基础)

mhr18 2024-10-28 16:18 19 浏览 0 评论

注释代码:版本3.5以后

https://github.com/aleafboat/redis.git

扩容函数调用关系

结构定义

1.typedef struct dict

2.{

3. dictType *type;

4. void *privdata;

5. dictht ht[2];

6. long rehashidx; /* rehashing not in progress if rehashidx == -1 */

7. int iterators; /* number of iterators currently running */

8.} dict;

9.

10.typedef struct dictht

11.{ //字典hash table

12. dictEntry **table;

13. //可以看做字典数组,俗称桶bucket

14. unsigned long size;

15. //指针数组的大小,即桶的层数

16. unsigned long sizemask;

17. unsigned long used;

18. //字典中当前的节点数目

19.} dictht;

20.

21.typedef struct dictEntry {

22. void *key;

23. void *val;

24. struct dictEntry *next;

25.} dictEntry;

扩容过程

何时需要扩容?

每次增加dictEntry时,都会调用一次_dictExpandIfNeeded()这个函数。

1.如果dict正在扩容,则返回正常DICT_OK

2.如果dict刚刚初始化,也就是两个dictht都为NULL,则调用dictExpand()函数初始化。dictExpand()同样会做一次判断两个dictht都为NULL,然后直接赋给ht[0]。

3.如果可以扩容(dict_can_resize=1),那么只要现在表中键总数大于表的长度就开始扩容。如果不能扩容(也就是dict_can_resize=0),

但是如果表中键总数与表的长度的比值大于某一个阀值(由dict_force_resize_ratio静态变量决定),那么就强制扩容。

扩容是怎么进行的? 之前所说的每个dict中都有两个哈希表结构dictht *ht[2]。

当开始扩容时,把第一个ht作为原始表,

第二个作为扩容后的表。dict中rehashidx决定了目前扩容的进度。

扩容过程什么时候执行?

当决定开始扩容后,dict中rehashidx赋为0,然后有两个函数来进行扩容。

max(4,used)

int dictRehash(dict *d, int n);

int dictRehashMilliseconds(dict *d, int ms);

第一个函数中n决定每次rehash(也就是转移dictEntry)的次数,

第二个函数决定调用rehash函数的时间。

每次执行查找,增加,删除操作都会执行一次dictRehash(1)(如果正在扩容),使得整个漫长的扩容过程隐含在每一次简单的操作中。

函数调用关系:

/* Rehash for an amount of time between ms milliseconds and ms+1 milliseconds */

int dictRehashMilliseconds(dict *d, int ms) {

long long start = timeInMilliseconds();

int rehashes = 0;

//每隔100ms执行一次

while(dictRehash(d,100)) {

rehashes += 100;

if (timeInMilliseconds()-start > ms) break;

}

return rehashes;

}

int dictRehash(dict *d, int n)

{ //移动n*10层(buckets)从d->ht[0] 到d->ht[1]

int empty_visits = n*10; /* Max number of empty buckets to visit. */

if (!dictIsRehashing(d)) return 0;

1.while(n-- && d->ht[0].used != 0)

2.{

3. dictEntry *de, *nextde;

4.

5. /* Note that rehashidx can't overflow as we are sure there are more

6. * elements because ht[0].used != 0 */

7. assert(d->ht[0].size > (unsigned long)d->rehashidx);

8. //没有数据不移动,数组哈希表

9. while(d->ht[0].table[d->rehashidx] == NULL)

10. {

11. d->rehashidx++;

12. if (--empty_visits == 0) return 1;

13. }

14. de = d->ht[0].table[d->rehashidx];

15. /* Move all the keys in this bucket from the old to the new hash HT */

16. while(de)

17. {

18. unsigned int h;

19.

20. nextde = de->next;

21. /* Get the index in the new hash table */

22. h = dictHashKey(d, de->key) & d->ht[1].sizemask;

23. //前插 例如head -- B -C--D insert A A->B-C>D, head-A

24. de->next = d->ht[1].table[h];

25. d->ht[1].table[h] = de;

26.

27. d->ht[0].used--;

28. d->ht[1].used++;

29. de = nextde;

30. }

31. d->ht[0].table[d->rehashidx] = NULL;//第rehashidx层移动完毕

32. d->rehashidx++;//继续下一层

33.}

34.

35./* Check if we already rehashed the whole table... */

36.if (d->ht[0].used == 0)

37.{

38. zfree(d->ht[0].table);

39. d->ht[0] = d->ht[1];

40. _dictReset(&d->ht[1]);

41. d->rehashidx = -1;//扩容完毕

42. return 0;

43.}

44.

45./* More to rehash... */

46.return 1;

}

总结:

扩容步骤

  • 业务操作触发扩容 计算扩容数量

  • 后台进程,间隔时间移动数据

QA

1 在扩容期间如果有新元素插入进来怎么办?

插入扩容的的哈希表

ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];

2 每次扩容多少?

初始化为数组大小为 长度为 4

如果操作引起扩容 长度为 2*size 2倍

(类似vector)

还有一个可能为原来元素个数

当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:

1)服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。

2)服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。

if (d->ht[0].used >= d->ht[0].size &&

(dict_can_resize ||

d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))

{

return dictExpand(d, d->ht[0].used*2);

//元素个数大于层次个数 或者配置强制扩容参数

}

3 如何实现慢慢扩容 ?

解决办法通过开辟一个新的线程来处理

4 假如原来的元素在A位置,扩容之后还咋A位置吗?

肯定不是呀 key%n 方式 n发生了变化

5 redis有没有想oracle那样有索引 自己定位

没有,index 是计算出来的,具体位置遍历就可以了

相关推荐

如何检查 Linux 服务器是物理服务器还是虚拟服务器?

在企业级运维、故障排查和性能调优过程中,准确了解服务器的运行环境至关重要。无论是物理机还是虚拟机,都存在各自的优势与限制。在很多场景下,尤其是当你继承一台服务器而不清楚底层硬件细节时,如何快速辨识它是...

第四节 Windows 系统 Docker 安装全指南

一、Docker在Windows上的运行原理(一)架构限制说明Docker本质上依赖Linux内核特性(如Namespaces、Cgroups等),因此在Windows系统上无法直...

C++ std:shared_ptr自定义allocator引入内存池

当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分...

Activiti 8.0.0 发布,业务流程管理与工作流系统

Activiti8.0.0现已发布。Activiti是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速、稳定的BPMN2流程引擎。Activiti可以...

MyBatis动态SQL的5种高级玩法,90%的人只用过3种

MyBatis动态SQL在日常开发中频繁使用,但大多数开发者仅掌握基础标签。本文将介绍五种高阶技巧,助你解锁更灵活的SQL控制能力。一、智能修剪(Trim标签)应用场景:动态处理字段更新,替代<...

Springboot数据访问(整合Mybatis Plus)

Springboot整合MybatisPlus1、创建数据表2、引入maven依赖mybatis-plus-boot-starter主要引入这个依赖,其他相关的依赖在这里就不写了。3、项目结构目录h...

盘点金州勇士在奥克兰13年的13大球星 满满的全是...

见证了两个月前勇士与猛龙那个史诗般的系列赛后,甲骨文球馆正式成为了历史。那个大大的红色标志被一个字母一个字母地移除,在周四,一切都成为了过去式。然而这座,别名为“Roaracle”(译注:Roar怒吼...

Mybatis入门看这一篇就够了(mybatis快速入门)

什么是MyBatisMyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为M...

Springboot数据访问(整合druid数据源)

Springboot整合druid数据源基本概念SpringBoot默认的数据源是:2.0之前:org.apache.tomcat.jdbc.pool.DataSource2.0及之后:com.z...

Linux 中的 &quot;/etc/profile.d&quot; 目录有什么作用 ?

什么是/etc/profile.d/目录?/etc/profile.d/目录是Linux系统不可或缺的一部分保留配置脚本。它与/etc/profile文件相关联,这是一个启动脚本,该脚...

企业数据库安全管理规范(企业数据库安全管理规范最新版)

1.目的为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。2.适用范围本规范中所定义的数据管理内容,特指存放在信息系统数据库...

Oracle 伪列!这些隐藏用法你都知道吗?

在Oracle数据库中,有几位特殊的“成员”——伪列,它们虽然不是表中真实存在的物理列,但却能在数据查询、处理过程中发挥出意想不到的强大作用。今天给大家分享Oracle伪列的使用技巧,无论...

Oracle 高效处理数据的隐藏神器:临时表妙用

各位数据库搬砖人,在Oracle的代码世界里闯荡,处理复杂业务时,是不是总被数据“搅得头大”?今天给大家安利一个超实用的隐藏神器——临时表!当你需要临时存储中间计算结果,又不想污染正式数据表...

Oracle 数据库查询:多表查询(oracle多表关联查询)

一、多表查询基础1.JOIN操作-INNERJOIN:返回两个表中满足连接条件的匹配行,不保留未匹配数据。SELECTa.emp_id,b.dept_nameFROMempl...

一文掌握怎么利用Shell+Python实现多数据源的异地备份程序

简介:在信息化时代,数据安全和业务连续性已成为企业和个人用户关注的焦点。无论是网站数据、数据库、日志文件,还是用户上传的文档、图片等,数据一旦丢失,损失难以估量。尤其是当数据分布在多个不同的目录、服务...

取消回复欢迎 发表评论: