redis 存储结构原理 2

咱们接着上一部分来进行分享,我们可以在如下地址下载 redis 的源码

https://redis.io/download

此处我下载的是 redis-6.2.5 版本的,xdm 可以直接下载上图中的 **redis-6.2.6 **版本,

redis 中 hash 表的数据结构

redis hash 表的数据结构定义在:

redis-6.2.5\src\dict.h

哈希表的结构,每一个字典都有两个实现从旧表到新表的增量重哈希

typedef struct dictht {dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used;
} dictht;

table:

table 是一个二级指针,对应这一个数组,数组中的每个元素都是指向了一个 dictEntry 结构体指针的,dictEntry 具体的数据结构是保存一个键值对

具体的 dictEntry 数据结构是这样的:

size:

size 属性是记录了整个 hash 表的大小,也可以理解为上述 table 数组的大小

sizemask:

sizemask 属性,和具体的 hash 值来一起决定键要放在 table 数组的哪个位置

sizemask 的值,总是会比 size 小 1 ,我们可以来演示一下

使用取余的方式,实际上是很低效的,咱们的计算机是不会做乘除法的,同样都是用加减法来进行处理的,为了高效处理,我们可以使用二进制的方式

使用二进制的方式,就会用到该字段 sizemask ,主要是用来 和 具体的 hash 值做按位与操作

如图就很明确了, size = 4,sizemask = 3,hash 值为 7, 最后 hash 值 & sizemask = 0011, 也就是 3,因此就会插入到上图的具体位置

used:

used 属性表示 hash 表里面已经有键值对的数量

对于上述的案例,可以用一个简图来表示一下 hash 表结构 dictht

dictEntry 结构每个属性的含义

typedef struct dictEntry {void *key;union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next;
} dictEntry;

上面我们看到数组中的节点信息,是 dictEntry 结构,属性分别是这些意思:

  • key

    具体的 redis 键

  • union v

    • val

      指向不同类型的数据,此处是 void * ,使用该类型,是为了节省内存

    • u64

      用于 redis 集群中的哨兵模式和选举模式

    • s64

      记录过期时间的

  • next

    指向下一个节点的指针

dict 结构

src\dict.h 文件中,咱们接着往下看,能够看到一个非常关键的结构,就是 dict ,redis 中都是使用这个结构来进行组织的

typedef struct dict {dictType *type;void *privdata;dictht ht[2];long rehashidx; /* rehashing not in progress if rehashidx == -1 */int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;
  • type

字段对应的操作函数,具体有哪些操作函数,我们可以看到typedef struct dictType 给出的信息

  • privdata

字典依赖的数据,例如 redis 具体的操作等等

  • ht[2]

hash 表的键值对,放在此处,一个旧的,一个新的

ht[0] :是扩容前的数组

ht[1]:是扩容后的数组

这个是当数据量大的时候,用于渐进式 rehash 的

  • rehashidx

来指定具体 rehash 的位置,对应到 ht[0] 的索引上,rehashidx == -1 ,就是没有进行再 hash , rehashidx != -1 时,说明正在进行再 hash

还记得我们之前说到 redis 有 16 个 db 吗?

我们在 redis 源码中 src\server.h 也能够看到 redisdb 的数据结构

我们可以看到 dict 这个字典,是 redis 中使用是相当频繁和关键的

上面有说到 ht[2] 会用在渐进式 rehash 上,那么为什么要用渐进式 rehash 以及他是如何做的?

扩容的时候,会触发 rehash

当数据量很大的时候,会涉及到扩容,若一次性从 ht[0] 拷贝到 ht[1] 是比较慢的,会阻塞其他操作,那么就没有办法处理其他请求了,因为 redis 是单线程处理任务的

ht[0] 数据拷贝到 ht[1] 的方式一

是这样进行 rehash 的

扩容的时候,rehash 是这样做的:

  • 先会对上述说到的 ht[1] 开辟内存空间,会将 ht[0].size * 2 给到 ht[1]
  • 然后再将 ht[0] 的数据,从 ht[0][0] ... ht[0][size-1] 将数据拷贝到 ht[1] 里面

如何做到渐进式呢?

使用分而治之的思想,无论 redis 目前是否在做持久化的时候,当我们每次操作 redis 增删改查,就会进行边枚举边筛查的方式,逐步的将 ht[0][0] ... ht[0][size-1] rehash 到 ht[1] 中

可以追一下代码流程 , 我们从 src\server.c 注册 setCommand 命令开始追起,代码设计关键流程如下

当追到 dictAddRaw 函数的时候,我们可以清晰的看出来,当 redis 加入数据的时候,使用的是头插法

  • 先对新的节点开辟相应的内存
  • 将新建节点的 next 对象指向链表的头
  • 然后将链表的头指向新建的节点地址,即完成了一次 头插

此处我们可以看到,实际上是做了一次 rehash

追到 dictRehash 函数的时候,可以看到此处的再 hash 函数 dictRehash,我们可以看到 rehash 的做法是:

  • 在 ht[0] 数组中,取得 rehashidx 对应的桶,或者脚数组对应的索引位置
  • 通过上述找到的索引位置,取 ht[0].table[d->rehashidx] 对应的链表
  • 然后将链表中的数据依次进行 rehash

此处 dictRehash 的 n 的参数,表示再 hash 的次数,再 hash 1 次,表示对于数组的这个桶对应的链表上的所有数据,进行一轮 hash

可以看到代码中

 /* Get the index in the new hash table */h = dictHashKey(d, de->key) & d->ht[1].sizemask;

此处正是 dictHashKey 计算出一个整数,然后和我们 dictht 中的 sizemask 进行一次按位与操作 , 旨在得到一个新的 hash 表索引位置

redis 调用 _dictRehashStep 的位置

通过查看代码中调用 _dictRehashStep 函数的位置并不多,我们一次查看调用关系,我们会知道确实是当我们每次操作 redis 增删改查的时候,会发生渐进式的 rehash , 这些是在我们进行扩容之后,如何将 ht[0] 的数据拷贝到 ht[1] 的实现方式

实际 redis 中涉及到如上几个函数 都会调用 _dictRehashStep:

  • dictAddRaw
  • dictGenericDelete
  • dictFind
  • dictGetRandomKey
  • dictGetSomeKeys

ht[0] 数据拷贝到 ht[1] 的方式二

定时器调用 dictRehash的逻辑

当 redis 中没有持久化操作的时候,redis 中的定时操作就会就会走定时的逻辑,逻辑是这样的

我们可以在 redis 源码中搜索使用 dictRehash 函数的位置

使用的位置也并不多,我们很容易就能找到按照毫秒级别来定时操作的位置

dictRehashMilliseconds

此处的逻辑是,while 循环是以 100 次 rehash 为一轮,时间限制是 1ms,只要时间不超过 1ms,能做的 rehash 次数至少是 100 次(每一轮 100 次),若超过 1 ms,则会立刻结束本次定时操作

此处我们可以看到,dictRehash(d,100) 传递的参数是 100,表示 rehash 100 次,还记得之前的渐进式 rehash 是 传入的 1 次 吗,可以往上看看文章内容

今天就到这里,学习所得,若有偏差,还请斧正

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

可以进入地址进行体验和学习:https://xxetb.xet.tech/s/3lucCI

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/97961.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RFID技术助力汽车零配件装配产线,提升效率与准确性

随着科技的不断发展&#xff0c;越来越多的自动化设备被应用到汽车零配件装配产线中。其中&#xff0c;射频识别&#xff08;Radio Frequency Identification&#xff0c;简称RFID&#xff09;技术凭借其独特的优势&#xff0c;已经成为了这一领域的重要技术之一。本文将介绍RF…

redis乐观锁+启用事务解决超卖

乐观锁用于监视库存&#xff08;watch&#xff09;&#xff0c;然后接下来就启用事务。 启用事务&#xff0c;将减库存、下单这两个步骤&#xff0c;放到一个事务当中即可解决秒杀问题、防止超卖。 但是&#xff01;&#xff01;&#xff01;乐观锁&#xff0c;会带来" …

C++运算符重载

C运算符重载 C运算符重载&#xff1a;使对象的运算表现得和编译器内置类型一样。 C实现复数类实现运算符重载 C类对象操作符重载函数函数时&#xff0c;会优先调用类的成员方法&#xff0c;没有找到再去全局去寻找对应的方法。 在调用某些操作符重载函数时&#xff0c;如果…

创建密码库/创建用户帐户/更新 Ansible 库的密钥/ 配置cron作业

目录 创建密码库 创建用户帐户 更新 Ansible 库的密钥 配置cron作业 创建密码库 按照下方所述&#xff0c;创建一个 Ansible 库来存储用户密码&#xff1a; 库名称为 /home/curtis/ansible/locker.yml 库中含有两个变量&#xff0c;名称如下&#xff1a; pw_developer&#…

YOLOv5、YOLOv8改进:S2注意力机制

目录 1.简介 2.YOLOv5改进 2.1增加以下S2-MLPv2.yaml文件 2.2common.py配置 2.3yolo.py配置 1.简介 S2-MLPv2注意力机制 最近&#xff0c;出现了基于 MLP 的视觉主干。与 CNN 和视觉Transformer相比&#xff0c;基于 MLP 的视觉架构具有较少的归纳偏差&#xff0c;在图像识…

中国剩余定理及扩展

目录 中国剩余定理解释 中国剩余定理扩展——求解模数不互质情况下的线性方程组&#xff1a; 代码实现&#xff1a; 互质&#xff1a; 非互质&#xff1a; 中国剩余定理解释 在《孙子算经》中有这样一个问题&#xff1a;“今有物不知其数&#xff0c;三三数之剩二&#x…

go es实例

go es实例 1、下载第三方库 go get github.com/olivere/elastic下载过程中出现如下报错&#xff1a; 解决方案&#xff1a; 2、示例 import package mainimport ("context""encoding/json""fmt""reflect""time""…

【前端】快速掌握HTML+CSS核心知识点

文章目录 1.HTML核心基础知识1.1.编写第一个HTML网页1.2.超链接a标签和路径1.3.图像img标签的用法1.4.表格table标签用法1.5.列表ul、ol、dl标签用法1.6.表单form标签用法1.7.区块标签和行内标签用法 2.CSS核心基础知识2.1.CSS标签选择器viewport布局2.2.CSS样式的几种写法2.3.…

【Linux取经路】解析环境变量,提升系统控制力

文章目录 一、进程优先级1.1 什么是优先级&#xff1f;1.2 为什么会有优先级&#xff1f;1.3 小结 二、Linux系统中的优先级2.1 查看进程优先级2.2 PRI and NI2.3 修改进程优先级2.4 进程优先级的实现原理2.5 一些名词解释 三、环境变量3.1 基本概念3.2 PATH&#xff1a;Linux系…

k8s 常见面试题

前段时间在这个视频中分享了 https://github.com/bregman-arie/devops-exercises 这个知识仓库。 这次继续分享里面的内容&#xff0c;本次主要以 k8s 相关的问题为主。 k8s 是什么&#xff0c;为什么企业选择使用它 k8s 是一个开源应用&#xff0c;给用户提供了管理、部署、扩…

Learning to Super-resolve Dynamic Scenes for Neuromorphic Spike Camera论文笔记

摘要 脉冲相机使用了“integrate and fire”机制来生成连续的脉冲流&#xff0c;以极高的时间分辨率来记录动态光照强度。但是极高的时间分辨率导致了受限的空间分辨率&#xff0c;致使重建出的图像无法很好保留原始场景的细节。为了解决这个问题&#xff0c;这篇文章提出了Sp…

idea2023 springboot2.7.5+mybatisplus3.5.2+jsp 初学单表增删改查

创建项目 修改pom.xml 为2.7.5 引入mybatisplus 2.1 修改pom.xml <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!--mysq…

【STM32 学习】电源解析(VCC、VDD、VREF+、VBAT)

VCC电源电压GND电源供电负电压&#xff08;通常接地&#xff09;VDD模块工作正电压VSS模块工作负电压VREFADC参考正电压VREF-ADC参考负电压VBAT电池或其他电源供电VDDA模拟供电正电压VSSA模拟供电负电压 一、VCC&#xff08;供电电压&#xff09; VCC是指芯片的电源电压&#…

MNIST手写数字数据集+7000张图片下载

MNIST手写数字图像数据集是一个经典的用于图像分类任务的数据集&#xff0c;其中包含了大量的手写数字图像样本 数据集点击下载&#xff1a; MNIST手写数字数据集7000张图片.rar

函数栈帧理解

本文是从汇编角度来展示的函数调用&#xff0c;而且是在vs2013下根据调试展开的探究&#xff0c;其它平台在一些指令上会有点不同&#xff0c;指令不多&#xff0c;简单记忆一下即可&#xff0c;在我前些年的学习中&#xff0c;学的这几句汇编指令对我调试找错误起了不小的作用…

【令牌桶算法与漏桶算法】

&#x1f4a7; 令牌桶算法与漏桶算法 \color{#FF1493}{令牌桶算法与漏桶算法} 令牌桶算法与漏桶算法&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专…

【前端|JS实战第1篇】使用JS来实现属于自己的贪吃蛇游戏!

前言 贪吃蛇游戏是经典的小游戏&#xff0c;也是学习前端JS的一个很好的练习项目。在本教程中&#xff0c;我们将使用 JavaScript 来逐步构建一个贪吃蛇游戏。我们会从创建游戏区域开始&#xff0c;逐步添加蛇的移动、食物的生成以及游戏逻辑等功能。 &#x1f680; 作者简介&a…

韦东山-电子量产工具项目:业务系统

代码结构 所有代码都已通过测试跑通&#xff0c;其中代码结构如下&#xff1a; 一、include文件夹 1.1 common.h #ifndef _COMMON_H #define _COMMON_Htypedef struct Region {int iLeftUpX; //区域左上方的坐标int iLeftUpY; //区域左下方的坐标int iWidth; //区域宽…

java八股文面试[java基础]——String StringBuilder StringBuffer

String类型定义&#xff1a; final String 不可以继承 final char [] 不可以修改 String不可变的好处&#xff1a; hash值只需要算一次&#xff0c;当String作为map的key时&#xff0c; 不需要考虑hash改变 天然的线程安全 知识来源&#xff1a; 【基础】String、StringB…

Python web实战之细说 Django 的单元测试

关键词&#xff1a; Python Web 开发、Django、单元测试、测试驱动开发、TDD、测试框架、持续集成、自动化测试 大家好&#xff0c;今天&#xff0c;我将带领大家进入 Python Web 开发的新世界&#xff0c;深入探讨 Django 的单元测试。通过本文的实战案例和详细讲解&#xff…