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
相关文章:

Redis的存储原理和数据模型
一、Redis是单线程还是多线程呢? 我们通过跑redis的代码,查看运行的程序可以得知,Redis本身其实是个多线程,其中包括redis-server,bio_close_file,bio_aof_fsync,bio_lazy_free,io_t…...

Linux 文件与目录操作命令详解
文章目录 前言创建文件1. touch2. vim 文件内容显示3. cat4. more5. less6. head7. tail 文件(目录)复制、删除和移动8. cp9. rm10. mv 压缩文件与解压缩11. gzip12. zip 和 unzip 创建目录13. mkdir 删除目录14. rmdir 改变工作目录15. cd16. pwd 显示目…...

MySQL篇(窗口函数/公用表达式(CTE))
目录 讲解一:窗口函数 一、简介 二、常见操作 1. sumgroup by常规的聚合函数操作 2. sum窗口函数的聚合操作 三、基本语法 1. Function(arg1,..., argn) 1.1. 聚合函数 sum函数:求和 min函数 :最小值 1.2. 排序函数 1.3. 跨行函数…...
408算法题leetcode--第七天
283. 移动零 283. 移动零思路:代码中注释阐述时间:O(n);空间:O(1) class Solution { public:void moveZeroes(vector<int>& nums) {// 简单思路:用一个辅助数组,将非0元素复制到里面// 双指针&…...
政务安全体系构建中的挑战
在数字化政务安全体系的构建过程中,面临着几个关键的挑战: ▋挑战一:安全防护滞后现代网络攻击技术不断演进,攻击手段日益多样化,如高级持续性威胁(APT)和勒索软件等新型攻击方式频繁出现。这些…...

基于EchoMimic加速版,可编辑标志点控制实现逼真音频驱动的肖像动画
EchoMimic 是蚂蚁集团终端技术部门开发的一项技术,旨在通过音频驱动生成逼真的肖像动画。对于那些初次接触这项技术的用户,本教程将带你逐步了解如何设置开发环境、获取项目代码、安装依赖,并最终成功运行示例生成自己的肖像动画。 文章目录 项目代码安装依赖业务拓展参数调…...

【STM32 HAL库】IIC通信与CubeMX配置
【STM32 HAL库】IIC通信与CubeMX配置 前言理论IIC总线时序图IIC写数据IIC读数据 轮询模式CubeMX配置应用示例AHT20初始化初始化函数读取说明读取函数 中断模式CubeMX配置状态机图fsm.caht20.c DMA模式CubeMX配置代码 前言 本文为笔者学习 IIC 通信的总结,基于keysk…...

iPhone 上丢失了重要的联系人?如何恢复已删除的 iPhone 联系人
丢失 iPhone 上的联系人可能会带来灾难。无论是一份很棒的新工作机会、潜在的恋爱对象,还是您一直想打电话的老朋友,如果您打开“联系人”应用时看到空白,这绝不是好事。不过,一切并非全无,仍然可以通过备份或专业软件…...

【有啥问啥】弱监督学习新突破:格灵深瞳多标签聚类辨别(Multi-Label Clustering and Discrimination, MLCD)方法
弱监督学习新突破:格灵深瞳多标签聚类辨别(Multi-Label Clustering and Discrimination, MLCD)方法 引言 在视觉大模型领域,如何有效利用海量无标签图像数据是一个亟待解决的问题。传统的深度学习模型依赖大量人工标注数据&…...
[强化你的LangChain工具创建技能:从基础到进阶]
强化你的LangChain工具创建技能:从基础到进阶 在现代AI开发中,为语言模型和智能代理提供工具是提升其功能的关键一步。本指南将带你深入了解如何在LangChain中创建工具,从简单的函数到复杂的可配置工具。 引言 在构建智能代理时࿰…...

4.提升客户服务体验:ChatGPT在客服中的应用(4/10)
本文大纲旨在指导撰写一篇全面探讨ChatGPT如何通过优化客户服务流程、提供实际应用案例和用户反馈,以提升客户服务体验的深入博客文章。 引言 在当今竞争激烈的商业环境中,客户服务已成为企业成功的关键因素。优质的客户服务不仅能够增强客户满意度和忠…...
Gradio导入AIGC大模型创建web端智能体聊天机器人,python(2)
Gradio导入AIGC大模型创建web端智能体聊天机器人,python(2) 选用这个大模型: https://huggingface.co/HuggingFaceTB/SmolLM-1.7B-Instructhttps://huggingface.co/HuggingFaceTB/SmolLM-1.7B-Instruct原因是该模型相对比较小&am…...
PEM 格式
文章目录 1.简介2.格式和内容3.常见用途4.标准化5.示例参考文献 1.简介 .pem 文件扩展名代表“Privacy Enhanced Mail”,但它被用于比电子邮件更广泛的上下文中,主要关联于加密、SSL/TLS 和证书管理。PEM 格式是一种用于存储和发送加密信息的标准&#…...
Android前台服务如何在后台启动activity?
本来最近在开发一个app保活另外一个app的功能,方案介绍如下: 应用A 启动一个前台服务保活自己应用A 用grpc连接应用B(服务端)是否存活如果发现B不存活,则在服务中拉起B 这次没有做好调研,直接开始了开发工作,等grpc都…...
c#visionpro开发 方法统计
toolblock开发 vpp第二种简单加载方式 public Cognex.VisionPro.ToolBlock.CogToolBlock ToolBlock1;//初始化后实例化一个方法 //窗口运行程序内部 ToolBlock1 (CogToolBlock)CogSerializer.LoadObjectFromFile(“tjjc.vpp”); MessageBox.Show(“算法加载成功”);//复制一个…...

dedecms——四种webshell姿势
姿势一:通过文件管理器上传WebShell 步骤一:访问目标靶场其思路为 dedecms 后台可以直接上传任意文件,可以通过文件管理器上传php文件获取webshell 步骤二:登陆到后台点击【核心】--》 【文件式管理器】--》 【文件上传】将准备好…...
GO GIN 推荐的库
在使用 Go 和 Gin 框架进行 Web 开发时,有许多第三方库可以增强功能和提高开发效率。以下是一些常用的、与 Gin 搭配使用的库: 1. 数据处理与验证 go-playground/validator 用于结构体字段的验证,Gin 默认已经集成了它。它提供了丰富的验证…...

YOLOv9改进策略【卷积层】| GnConv:一种通过门控卷积和递归设计来实现高效、可扩展、平移等变的高阶空间交互操作
一、本文介绍 本文记录的是利用GnConv优化YOLOv9的目标检测方法研究。YOLOv9在进行目标检测时,需要对不同层次的特征进行融合。GnConv可以考虑更高阶的空间交互,能够更好地捕捉特征之间的复杂关系,从而增强特征融合的效果,提高模…...

如何在Linux下升级R版本和RStudio
一、升级R版本 在Linux上,R的安装通常通过包管理器完成。不同的Linux发行版(如Ubuntu、Debian、Fedora等)可能略有不同。下面以Ubuntu为例,介绍如何升级R版本。如果你使用其他发行版,步骤可能类似。 二.更新步骤 2.…...

npm安装时候报错certificate has expired
打开了一个很久没用的电脑,npm和node都装好了,安装包的时候一直报错 request to https://registry.npm.taobao.org/create-react-app failed, reason: certificate has expired而且先报错rollbackFailedOptional 然而npm没什么问题,是ssl过…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...

CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?
在现代前端开发中,Utility-First (功能优先) CSS 框架已经成为主流。其中,Tailwind CSS 无疑是市场的领导者和标杆。然而,一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…...
【大厂机试题解法笔记】矩阵匹配
题目 从一个 N * M(N ≤ M)的矩阵中选出 N 个数,任意两个数字不能在同一行或同一列,求选出来的 N 个数中第 K 大的数字的最小值是多少。 输入描述 输入矩阵要求:1 ≤ K ≤ N ≤ M ≤ 150 输入格式 N M K N*M矩阵 输…...
JavaScript性能优化实战大纲
性能优化的核心目标 降低页面加载时间,减少内存占用,提高代码执行效率,确保流畅的用户体验。 代码层面的优化 减少全局变量使用,避免内存泄漏 // 不好的实践 var globalVar I am global;// 好的实践 (function() {var localV…...
Python_day48随机函数与广播机制
在继续讲解模块消融前,先补充几个之前没提的基础概念 尤其需要搞懂张量的维度、以及计算后的维度,这对于你未来理解复杂的网络至关重要 一、 随机张量的生成 在深度学习中经常需要随机生成一些张量,比如权重的初始化,或者计算输入…...
上位机知识篇---Flask框架实现Web服务
本文将简单介绍Web 服务与前端显示部分,它们基于Flask 框架和HTML/CSS/JavaScript实现,主要负责将实时视频流和检测结果通过网页展示,并提供交互式状态监控。以下是详细技术解析: 一、Flask Web 服务架构 1. 核心路由设计 app.…...