Redis的存储原理和数据模型

一、Redis是单线程还是多线程呢?

        我们通过跑redis的代码,查看运行的程序可以得知,Redis本身其实是个多线程,其中包括redis-server,bio_close_file,bio_aof_fsync,bio_lazy_free,io_thd_*,jemalloc_bg_thd等过程,其中的io_thd_*就是多线程的意思,包含多个接收io的线程。

        但是我们常说的Redis是单线程是什么意思呢?其实是说的是Redis在处理我们发送的命令是单线程的。也就意味着有前后顺序。

二、命令处理为什么是单线程?

        首先我们需要了解一下单线程的局限性:如果在单线程中碰到了一些耗时操作,比如cpu的大量计算和阻塞等待的io处理,那么整个线程就会被阻塞等待,大大降低效率,这样对Redis而言就会影响性能。

        那么针对这些问题,Redis有没有相关的处理方式,比如io密集型,cpu密集型。

1、io密集型

        磁盘io:对于 fork 进程,在子进程中持久化,我们通过异步刷盘来处理。

        网络io:对于服务多个客户,造成io密集型的话,我们采用reactor网络模型来处理。而对于数据请求或返回数据量比较大的话,我们需要开启io多线程来处理。

2、cpu密集型

在Redis中我们采用分治的方式数据结构切换渐进式数据迁移

分治的方式:将一个大的问题分成多个小问题进行处理。对于一个操作时间长的问题,我们将一段一段的进行处理。

数据结构的切换:在Redis中含有五种类型的结构,在每一种的结构中还有更小的结构,我们根据不同的情况使用这一不同的小结构,使效率最快。

渐进式数据迁移:类似于分治的第二种。

        那么为什么不采用多线程处理呢?由于我们含有五种数据类型,而且每种类型由多个数据结构实现,这样使我们加锁变得复杂,并且加锁粒度不好控制。那么使用单线程就可以避免多线程间频繁的上下文切换,减少线程切换额外带来的开销,从而提高处理速度。下面会讲解。

三、对象编码

        下面的图片中,共有五种数据类型:string,list,hash,set,zset。其中每一个类型都含有不同的数据结构,Redis会根据不同的情况选出不同的数据结构的。

        跳表:就是多层级的链表,一层一层的搜索,将时间复杂度降低到和二分查找一个速度。理想跳表下,可以模拟出二叉树的结构,和二叉树一个搜索速度(空间换时间)。但是这种情况需要重构,重构的时间太长。因此实现Redis的跳表:从节约内存出发,可以让这个结构更加扁平,把二叉堆变成四叉堆。

四、单线程为什么这么快?

1、采用了哪些机制

内存数据库:Redis数据库是内存数据库,是将数据直接存储到内存中的,这样的读取速度比存储在磁盘中的速度提高了近10倍。

数据组织方式:Redis是一个KV类型的,Redis把这一对直接放到hashtable里面。下面会着重讲解。

数据结构高效:多种数据结构,可以来回切换,使效率和占用内存保持平衡。

2、hashtable 

        在数据组织方式中使用了hashtable,我们所有的数据都是存放在这个里面。由于Redis存储是KV存储,我们根据K这个值来进行选定位置。对于使用了hash表,我们每次的set和get之前都要对这个Key值进行hash,对于一样的Key值,我们hash出来肯定是一样的,所以我们就可以做到O(1)的时间复杂度。

        但是当我们开辟出来的空间使用完毕,那么我们就会出现hash冲突,比如一共六个位置,这六个位置全部有数据了,那么我们再添加一个数据,此时这个数据肯定要发生hash冲突,当一个坑位中出现n个结点的时候,那么我们的查找速度就从O(1)降到O(n)。对于这种情况,我们需要进行扩容。

        负载因子 = used / size ; used是数组存储元素的个数,size是数组的长度。负载因子越小,冲突越小,负载因子越大,冲突越大。而redis的负载因子是1。

2.1、扩容

        当我们每个位置都已经满了还要插入数据,也就是负载因子>1 时,就需要进行扩容,并且是翻倍扩容。如果正在 fork (在 rdb、aof 复写以及 rdb-aof 混用情况下)时,会阻止扩容;但是此时若负载 因子 > 5 ,索引效率大大降低, 则马上扩容;

        扩容后我们的hash函数发生变化。hash(key) % size;那么我们hash后存储的位置可能发生变化。

2.2、缩容

        当我们的负载因子 < 0.1 则会发生缩容;缩容的规则是恰好包含used的2的n次方。举个例子:当存储的元素为9,那么包含该元素的为2的4次,也就是16。

2.3、渐进式rehash

        当我们扩缩容的时候,我们发现映射规则发生改变,因此需要重新进行hash,所以叫做rehash。

        当我们阅读Redis源码的时候,我们可以发现DB数据库中的hashtable是有两个哈希表的:ht[2](数组);默认情况下,Redis将数据存储在ht[0]中,那么为什么需要两个hashtable呢?

        我们在扩缩容之前是存放在ht[0]中的,当我们需要进行rehash时,我们就将数据存放在ht[1]中,当全部hash之后,我们就将ht[1]赋值给ht[0],将ht[1]置空。

        那为什么叫做渐进式rehash呢?因为当hashtable中的元素过多的时候,不能一次性rehash到ht[1]中去,这样就会一直占用redis,无法及时处理其他命令,所以需要渐进式rehash。

渐进的方法:1、分治思想。2、加入定时器

1、分治:我们每次rehash一个槽位,把这个操作放入到增删改查的后面去,一步一步的将全部数据转移到另一个哈希表中去。但是这种方法在数据很多的情况下有点慢。

2、定时器:我们在Redis不太忙的时候,弄一个定时器,每隔一段时间,执行一次rehash,每次最大执行一毫秒,每次步长为100个数组槽位。

处于渐进式rehash的时候,不会发生扩缩容。

3、数据结构高效

        我们在上面提到了很多的数据类型,比如string类型,在它的下面还有三种:int,raw,embstr。这三种用于分别存储不同类型的字符串。在这里有个面试题可以瞅一眼:为什么Redis中字符串选择64个字节作为分界线?为什么string类型中要以44为分界线?

        首先内存分配器都是按照大小为2的几次方(2,4,8,16,32,64....)进行分配的,同时cpu cache line(cpu缓存行)最小访问单位为64个字节,所以选择64个字节作为分界线。对于在string字符串中小于44字节选择embstr编码格式,大于44字节选择raw编码格式。其中embstr顾名思义就是嵌入式字符串,嵌入到redisObject中,而raw就是在redisObject中维持一个指向堆上的资源。

        我们通过查看存储string类型的源码可以发现是redisObject占据了16个字节,由于是64字节,所以需要sdshdr8(sdshdr8是Redis中用于表示简单动态字符串(SDS)的一个结构体类型)来存储,这里占用三个字节,这些全都是字符串的头部信息。因为string类型是一个二进制安全的字符串,但是为了兼容c的字符串库函数,字符串末尾要以'\0'作为分隔符,所以需要减去这一个长度。所以64-16-3-1 = 44。

4、做出优化

采用分治思想,把rehash进行分摊或者放入定时器中。然后将耗时阻塞的操作扔给其他线程处理。再然后对于不同的对象类型采用不同的数据结构实现。

五、redis的多线程工作原理

        对于大量的阻塞io和cpu计算,我们采用多线程工作的方法进行处理。下面的图就是redis的处理流程。

        当大量客户端连接上后,发送多个命令到服务端,我们的reactor服务器将这些任务分发到不同的线程中去。其中一次任务的处理流程是:read->decode->compute->encode->send。读取数据,解析,处理,加密,发送数据。具体的处理函数可以自行阅读源码。

         接下来让我们看看多线程是怎么运行的。下面这张图中的数组代表客户端发送来的任务。下面有四个线程,其中一个是主线程。我们还记得Redis处理任务是单线程,每个任务的处理都要走上面那幅图的流程。

        我们将任务分发给每个线程,让他们负责读数据,解析,加密,发送数据。而处理数据全部交给主线程进行处理。也就是说主线程只负责处理核心数据,而其他线程负责处理其他业务。

讲解完毕啦!https://github.com/0voice

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

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

相关文章

猫头虎分享:Python库 SQLAlchemy 的简介、安装、用法详解入门教程

&#x1f42f; 猫头虎分享&#xff1a;Python库 SQLAlchemy 的简介、安装、用法详解入门教程 大家好&#xff0c;我是猫头虎&#xff01;今天有粉丝问猫哥&#xff1a;“在项目开发中如何高效地进行数据库操作&#xff1f;是否有一个灵活又强大的ORM库推荐&#xff1f;”正好&…

[Linux] 进程优先级 进程的调度与切换 环境变量详解

进程优先级 && 进程的调度与切换 && 环境变量 1.进程优先级1.1查看进程1.2 PRI VS NI1.3用指令调整优先级 2.进程的调度与切换2.1 进程切换2.2 linux实现进程调度的算法 3.环境变量前言引入&#xff08;main参数--命令行参数&#xff09;3.1 环境变量3.2 PATH环…

题目:单调栈

1、关于栈的概述 栈是一种数据结构&#xff0c;遵循“后进先出”&#xff08;LIFO, Last In, First Out&#xff09;的原则。这意味着最后被插入栈中的元素会最先被移除。可以把它想象成一个垒盘子的情况&#xff0c;新的盘子总是放在最上面&#xff0c;而最上面的盘子会最先被…

4------维修手机工具 解锁 刷机 保资料修复 修改参数等多工具合集 工具预览与操作解析

此款工具可能很多维修技术都使用过。早期知名手机维修加密狗。目前已经修改为可以任何人使用。此工具集合了多个版本以及加密狗工具。所谓的这些手机维修仪器工具。只是把很多工具直接整合到他里面。然后按需运行。其实查看解压后的文件会在其中找到有些小工具集合。类似基带修…

英文翻译无忧:2024年四大翻译工具推荐!

在全球化时代&#xff0c;英语已成为国际交流的重要语言。对于许多英语非母语的伙伴来说&#xff0c;一款好用的英文翻译工具至关重要。今天&#xff0c;小编为大家盘点几款实用的英文翻译工具&#xff01; 福昕在线翻译 直达链接&#xff1a;fanyi.pdf365.cn/ 福昕在线翻译…

基于51单片机的220V交流数字电流表proteus仿真

地址&#xff1a;https://pan.baidu.com/s/1QmpPLvDTuW7QG7P-JCLPPg 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectron…

vulkano (rust) 画一个三角形 (vulkan 渲染窗口初始化 (Linux) 下篇)

上文说到, vulkan 相比 OpenGL (ES), 更加贴近底层硬件, 许多东西需要应用软件手动管理, 所以 vulkan 的初始化过程比较麻烦, 或者说学习曲线比较陡峭. 但是, 这种麻烦是一次性的, 一旦学会了, 就能开始享受 vulkan 的诸多好处啦 ~ 本文以绘制一个三角形为例, 介绍 vulkan 的初…

DBA运维小技巧之存储篇-Oracle服务器根目录满了怎么处理(2)迁移至新存储空间

1 前情提要 话说上次DBA小倩通过删除home lv&#xff0c;把空间扩给了/分区&#xff0c;问题暂时得到了解决。 没过几天&#xff0c;领导找到小倩下达任务&#xff0c;客户说数据库在本地磁盘空间太小了又快要满了&#xff0c;由于之前用的服务器本地磁盘&#xff0c;性能也比…

信息安全工程师(5)域名与域名解析

一、域名 1. 定义与功能 域名&#xff08;Domain Name&#xff09;是互联网上用于标识网站或服务器地址的名称&#xff0c;由一串由点分隔的字符组成&#xff0c;如“example.com”。域名的主要功能是提供一种便于记忆和输入的地址形式&#xff0c;以代替难以记忆的IP地址。域名…

【软件测试】肇新合同管理系统 需求说明书

1 引言 1.1 编写目的 本文档将列举实现合同管理系统所需要的全部功能&#xff0c;并对每个功能给出简单的描述。 本文档的预期读者包括&#xff1a;最终用户&#xff0c;项目负责人&#xff0c;评审人员&#xff0c;产品人员&#xff0c;软件设计开发人员&#xff0c;测试人员…

linux网络编程——UDP编程

写在前边 本文是B站up主韦东山的4_8-3.UDP编程示例_哔哩哔哩_bilibili视频的笔记&#xff0c;其中有些部分博主也没有理解&#xff0c;希望各位辩证的看。 UDP协议简介 UDP 是一个简单的面向数据报的运输层协议&#xff0c;在网络中用于处理数据包&#xff0c;是一种无连接的…

OpenAI o1:AI推理的未来,如何平衡性能与成本?

OpenAI o1&#xff1a;AI推理的未来&#xff0c;如何平衡性能与成本&#xff1f; &#x1f680;人工智能的未来&#xff0c;已经悄然走向一个新的拐点&#xff01;9月14日&#xff0c;OpenAI正式推出了两款新型模型——o1-preview与o1-mini。虽然这并非是GPT-4的简单升级版&am…

Python 从入门到实战19(函数参数)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了函数的基本介绍。今天我们继续学习一下函数参…

java坏境搭建

目录 安装 步骤1 步骤2 步骤3 步骤4 环境变量 1、在桌面“计算机”或“此电脑”图标上右键&#xff0c;选择“属性”&#xff0c;进入控制面板的计算机系统页面后&#xff0c;点击“高级系统设置”&#xff0c;不同操作系统可能界面不同&#xff1a; 2、点击“环境变量”…

铝型材及其常用紧固件、连接件介绍

铝型材介绍&#xff08;包括紧固件和连接件以及走线&#xff09; 铝型材 铝型材一般是6063铝合金挤压成型&#xff0c;分为欧标和国标两个标准。&#xff08;左边国标&#xff0c;右边欧标&#xff0c;欧标槽宽一点&#xff09; 由于槽型不一样&#xff0c;相关的螺栓和螺母也…

VS Code终端命令执行后老是出现 __vsc_prompt_cmd_original: command not found

VS Code终端命令执行后老是出现 __vsc_prompt_cmd_original: command not found。 如下图&#xff08;vscode终端中&#xff09;&#xff1a; 解决方案&#xff1a; 1、vim ~/.bashrc 2、在~/.bashrc里面加入命令&#xff1a;unset PROMPT_COMMAND 3、source ~/.bashrc

GB28181在融合指挥调度系统应用方案探究和技术实现

GB28181规范在融合指挥调度系统主要围绕实现视频监控系统的互联互通、音视频数据的实时传输与控制、以及应急指挥调度的高效性展开。 一、GB28181规范概述 GB/T 28181是中国国家标准《安全防范视频监控联网系统信息传输、交换、控制技术要求》的编号&#xff0c;该标准规定了…

深度学习——微积分求导,反向传播

目录 一、导数二、自动微分&#xff0c;反向传播2.1 对向量求导的例子2.1 求和函数的梯度 一、导数 举一个关于导数的实例&#xff0c;定义一个函数 u f ( x ) 3 x 2 − 4 x uf(x)3x^2-4x uf(x)3x2−4x pip install matplotlibimport numpy as np from matplotlib_inline im…

拥有一个你说了算的人生—觉知

觉知&#xff0c;是最大的容器 觉知的力量 觉知&#xff0c;必然意味着对自身的了解&#xff0c;并且还会伴随着深刻的体验 觉知是光&#xff0c;而没有被觉知之物&#xff0c;就藏在黑暗中。一旦有觉知之光照进来&#xff0c;黑暗不仅无所遁形&#xff0c;而且黑暗中的动力还…

【滑动窗口】一题讲透滑动窗口!

&#x1f680;个人主页&#xff1a;一颗小谷粒 &#x1f680;所属专栏&#xff1a;力扣刷题 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1.1 题目要求 1.2 算法图解分析 1.3 代码实现 1.4 时间复杂度分析 1.5 算法思想总结 1.1 题目要…