redis 存储结构原理 2

news2024/11/15 20:29:57

咱们接着上一部分来进行分享,我们可以在如下地址下载 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.coloradmin.cn/o/900337.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

php_mb_strlen指定扩展

1 中文在utf-字符集下占3个字节,所以计算出来长度为9。 2 可以引入php多字节字符的扩展&#xff0c;默认是没有的&#xff0c;需要自己配置这个函数 3 找到php.ini文件&#xff0c;去掉;extension mbstring的注释&#xff0c;接着重启apache服务 可以看到准确输出的中文的长度…

javascript期末作业【三维房屋设计】

1、引入three.js库 官网下载three.js 库 放置目录并引用 引入js文件: 设置场景&#xff08;scene&#xff09; &#xff08;1&#xff09;创建场景对象 &#xff08;2&#xff09;设置透明相机 1,透明相机的优点 透明相机机制更符合于人的视角,在场景预览和游戏场景多有使用…

视频怎么转gif高清动图?分享一款视频转gif工具

许多小伙伴都不知道如何将拍摄的短视频转gif图片&#xff0c;本文将分享一款专业的视频转gif工具&#xff0c;打来浏览器即可将视频在线转gif&#xff08;https://www.gif.cn&#xff09;&#xff0c;操作简单&#xff0c;使用方便&#xff0c;下面是详细的步骤。 打开网站&am…

SpringBoot案例-员工管理-新增员工

查看页面原型&#xff0c;明确需求 页面原型 需求 阅读接口文档 接口文档链接如下&#xff1a; 【腾讯文档】SpringBoot案例所需文档 https://docs.qq.com/doc/DUkRiTWVaUmFVck9N 思路分析 阅读需求文档后可知&#xff0c;前端发送请求的同时&#xff0c;将前端请求参数以…

centos8 使用phpstudy安装tomcat部署web项目

系统配置 1、安装Tomcat 2、问题 正常安装完Tomcat应该有个配置选项&#xff0c;用来配置server.xml web.xml 还有映射webapps路径选项&#xff0c;但是我用的这个版本并没有。所以只能曲线救国。 3、解决 既然没有配置项&#xff0c;那就只能按最基本的方法配置&#xff0c…

算法之排序总结

排序算法 最近&#xff0c;一直在学习业务上的知识&#xff0c;对基础没有怎么重视&#xff0c;因此&#xff0c;这篇文章想对于排序算法进行一个大致的总结&#x1f913;&#x1f913;&#x1f913;。 首先来说一下&#xff0c;关于排序一些相关的基础知识。 排序概述 原地…

代码随想录第25天|216.组合总和III ​​​​​​​,17. 电话号码的字母组合

216.组合总和III 回溯三部曲 确定递归函数参数 targetSum&#xff08;int&#xff09;目标和&#xff0c;也就是题目中的n。k&#xff08;int&#xff09;就是题目中要求k个数的集合。sum&#xff08;int&#xff09;为已经收集的元素的总和&#xff0c;也就是path里元素的…

(学习笔记-进程管理)什么是悲观锁、乐观锁?

互斥锁与自旋锁 最底层的两种就是 [互斥锁和自旋锁]&#xff0c;有很多高级的锁都是基于它们实现的。可以认为它们是各种锁的地基&#xff0c;所以我们必须清楚它们之间的区别和应用。 加锁的目的就是保证共享资源在任意时间内&#xff0c;只有一个线程访问&#xff0c;这样就…

LabVIEW模拟化学反应器的工作

LabVIEW模拟化学反应器的工作 近年来&#xff0c;化学反应器在化学和工业过程领域有许多应用。高价值产品是通过混合产品&#xff0c;化学反应&#xff0c;蒸馏和结晶等多种工业过程转换原材料制成的。化学反应器通常用于大型加工行业&#xff0c;例如酿酒厂公司饮料产品的发酵…

C 基础拾遗

C基础拾遗 预处理器 预处理器 14.1 预定义符号 14.2 #define

5种常见的3D游戏艺术风格及工具栈

在游戏开发领域&#xff0c;3D 艺术风格已成为为玩家创造身临其境、引人入胜的体验的重要组成部分。 随着技术的进步&#xff0c;创造令人惊叹的 3D 视觉效果的可能性已经大大扩展&#xff0c;为游戏开发人员提供了广泛的选择。 在本文中&#xff0c;我们将探讨当今游戏开发中…

Seaborn数据可视化(一)

目录 1.seaborn简介 2.Seaborn绘图风格设置 21.参数说明&#xff1a; 2.2 示例&#xff1a; 1.seaborn简介 Seaborn是一个用于数据可视化的Python库&#xff0c;它是建立在Matplotlib之上的高级绘图库。Seaborn的目标是使绘图任务变得简单&#xff0c;同时产生美观且具有信…

micropython SSD1306/SSD1315驱动

目录 简介 代码 功能 显示ASCII字符 ​编辑 画任意直线 画横线 画竖线 画矩形 画椭圆 画立方体 画点阵图 翻转 反相 滚动 横向滚动 纵向滚动 奇葩滚动 简介 我重新写了一个驱动&#xff0c;增加了一些功能&#xff0c;由于我的硬件是128*64oled单色I2C&#xff0c;我只…

lvs-DR模式:

lvs-DR数据包流向分析 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 Director Server 和 Real Server 在同一个网络中&#xff0c;数据通过二层数据链路…

08.异常处理与异常Hook(软件断点Hook,硬件断点Hook)

文章目录 异常处理异常Hook&#xff1a;VEH软件断点HOOKVEH硬件断点HOOK 异常处理 1.结构化异常SEH #include <iostream>int main() {goto Exit;__try {//受保护节int a 0;int b 0;int c a / b;std::cout << "触发异常" << std::endl;}/*EXCE…

学习笔记:Opencv实现图像特征提取算法SIFT

2023.8.19 为了在暑假内实现深度学习的进阶学习&#xff0c;特意学习一下传统算法&#xff0c;分享学习心得&#xff0c;记录学习日常 SIFT的百科&#xff1a; SIFT Scale Invariant Feature Transform, 尺度不变特征转换 全网最详细SIFT算法原理实现_ssift算法_Tc.小浩的博客…

如何获得Android 14复活节彩蛋

每个新的安卓版本都有隐藏复活节彩蛋的悠久传统&#xff0c;可以追溯到以前&#xff0c;每个版本都以某种甜食命名。安卓14也不例外&#xff0c;但这一次的主题都是围绕太空构建的——还有一个复活节彩蛋。 安卓14复活节彩蛋实际上是一款很酷的小迷你游戏&#xff0c;你可以乘…

[Mac软件]MacCleaner 3 PRO 3.2.1应用程序清理和卸载

应用介绍 MacCleaner PRO是一个应用程序包&#xff0c;将帮助您清除磁盘空间并加快Mac的速度&#xff01; MacCleaner PRO - 让您的Mac始终快速、干净和有条理。 App Cleaner & Uninstaller PRO - 完全删除未使用的应用程序并管理Mac扩展。 磁盘空间分析仪PRO-分析磁盘空…

飞天使-k8s简单搭建

文章目录 k8s概念安装部署-第一版无密钥配置与hosts与关闭swap开启ipv4转发安装前启用脚本开启ip_vs安装指定版本docker 安装kubeadm kubectl kubelet,此部分为基础构建模版 k8s一主一worker节点部署k8s三个master部署,如果负载均衡keepalived 不可用&#xff0c;可以用单节点做…

STM32 CubeMX (第四步Freertos内存管理和CPU使用率)

STM32 CubeMX STM32 CubeMX &#xff08;第四步Freertos内存管理和CPU使用率&#xff09; STM32 CubeMX一、STM32 CubeMX设置时钟配置HAL时基选择TIM1&#xff08;不要选择滴答定时器&#xff1b;滴答定时器留给OS系统做时基&#xff09;使用STM32 CubeMX 库&#xff0c;配置Fr…