Redis——网络模型之IO讲解

目录

前言

1.用户空间和内核空间

1.2用户空间和内核空间的切换

1.3切换过程

2.阻塞IO

3.非阻塞IO

4.IO多路复用

4.1.IO多路复用过程

4.2.IO多路复用监听方式

4.3.IO多路复用-select

4.4.IO多路复用-poll

4.5.IO多路复用-epoll

4.6.select poll epoll总结

4.7.IO多路复用-事件通知机制

4.8.IO多路复用-web服务流程

5.信号驱动IO 

6.异步IO

7.同步和异步


前言

Redis 以其卓越的性能和灵活的特性,成为众多开发者在缓存、消息队列等场景的首选。而 Redis 强大性能的背后,其网络模型与 IO 机制发挥着关键作用数据的读写速度直接影响着用户体验。大家日常逛的电商平台,流畅加载商品详情页离不开它;刷社交软件时,点赞瞬间显示也有它的功劳 。接下来,我们就深入挖掘一下,看看 Redis 是如何通过它们实现高效运转的。

 

1.用户空间和内核空间

为了避免用户应用导致冲突甚至内核崩溃,用户应用与内核是分离的:

进程的寻址空间会划分两部分:内核空间,用户空间

这就代表了一个完整的32位寻址空间

那什么是寻址空间呢?

寻址空间是指计算机系统中处理器能够访问的内存地址范围。

计算机中的内存是由许多存储单元组成的,每个存储单元都有一个唯一的编号,这个编号就是地址。处理器通过地址来访问内存中的数据和指令。寻址空间就是这些地址的集合,它决定了处理器能够访问的内存大小。

我们还要在系统权限上进行划分,因为我们cpu运行的各种各样的命令里边,有一些命令风险等级比较低,有一些比较高。所以cpu会把各种各样的命令划分成四个不同的等级,Ring0风险等级最低,Ring3风险等级最高

  • 用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问
  • 内核空间可以执行特权命令(Ring0),调用一切系统资源

 

1.2用户空间和内核空间的切换

应用程序在用户空间,内核应用在内核空间,但是我们一个进程它在执行过程中因为业务比较多,可能会执行一些普通命令和特权命令调用系统资源。因此进程会在用户空间和内核空间之间进行一个转换

linux系统为了提高io效率,会在用户空间和内核空间都加入缓冲区

  • 写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备
  • 读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区

 

1.3切换过程

IO在用户空间和内核空间切换流程

写数据到磁盘过程

  • 1.进程在做一些简单运算,字符串处理,之后要把数据写出到我们的磁盘需要调用我们的内核
  • 2.写数据先写到缓冲区,要往磁盘写必须要切换到内核
  • 3.切换到内核,内核没有我要写的数据,所以要先把用户缓冲区的数据拷贝到内核的缓冲区,然后再把缓冲区的数据写入我们的磁盘

读数据到用户空间

  • 1.开始用户空间发起read的请求,请求到达内核空间判断有没有数据,如果要读的是磁盘,要先去寻址wati,for data,寻址到之后把数据读取到缓冲区
  • 2.把数据从内核缓冲区拷贝到用户空间用户再区处理这些数据

从上图可知IO读写效率的主要原因是

  • 1.数据等待过程,(用户空间读数据发起read请求,内核空间接收到这个请求需要去寻址和把数据写到缓冲区)
  • 2.就是数据拷贝过程非常影响性能,一个空间缓冲区数据拷贝到另一个空间缓冲区

数据拷贝是由操作系统内核来完成的。

 

2.阻塞IO

不同IO模型的差别就是在1.等待数据就绪 2.读取数据过程

阻塞IO就是两个阶段都必须阻塞等待;

所以阻塞IO就是2.把数据写到内核缓冲区1.把内核缓冲区数据写到用户空间缓冲区这两个阶段都是阻塞状态。

 

3.非阻塞IO

顾名思义,非阻塞IO的recvfrom操作会立即返回结果而不是阻塞用户进程。

非阻塞IO就是第一阶段不停调用recvfrom去读取数据读取不到不会阻塞一直反复调用直到成功(反而让cpu使用率增加)

然后第二阶段内核拷贝数据到用户空间依然是阻塞状态

4.IO多路复用

无论是阻塞IO还是非阻塞IO,用户应用在一阶段都需要调用recvfrom来获取数据,差别在于无数据时的处理方案:

  • 如果调用recvfrom时,恰好没有数据,阻塞IO会使进程阻塞,非阻塞IO使CPU空转,都不能充分发挥CPU的作用。
  • 如果调用recvfrom时,恰好有数据,则用户进程可以直接进入第二阶段,读取并处理数据

比如服务端处理客户端Socket时,在单线程情况下,只能依次处理每一个socket,如果正在处理的socket恰好未就绪(数据不可读或不可写),线程就会被阻塞,所有其它客户端socket都必须等待,性能自然会很差。

解决方案就是数据就绪了,用户应用就去读取数据

用户进程如何知道内核中数据是否就绪呢?

4.1.IO多路复用过程

文件描述符(File Descriptor):简称FD,是一个从0开始递增的无待号整数,用来关联Linux中的一个文件。在Linux中,一切皆文件,例如常规文件,视频,硬件设备等,当然也包括网络套接字(socket)。


IO多路复用:是利用单个线程来同时监听多个FD,并在某个FD可读,可写时得到通知,从而避免无效的等待,充分利用CPU资源

IO多路复用过程:

  1. 用户应用首先去调用select函数,不在是调用recvfrom(recvfrom直接是尝试读取数据读的目标是具体某一个FD)
  2. select函数内部可以接收多个FD,也就是说可以把每个客户端socket对应的FD,传给select函数,然后传入到内核中,内核就可以去检查你要去监听的多个FD,有没有任何一个是就绪的,只要由任意一个或者多个就绪就会直接返回这个结果。
  3. 如果这n个FD都没有就绪那么就会稍微等一会,在等待过程中其实就会有后台进程去监听这些FD,一旦有任意一个或者多个就绪返回结果。这个等待不可避免的。
  4. 拿到readable结果了就去调用recvfrom我们可以明确知道哪些FD准备好啦,然后拷贝数据返回结果(循环调用)

其实IO多路复用1.等待数据就绪用户进程也是阻塞阶段,阶段二数据拷贝同样是阻塞的

区别在于:

  • 阻塞IO调用的是调用recvfrom去监听某一个FD有没有就绪,没有就会阻塞
  • IO多路复用调用的是select函数去监听多个FD,只要有一个FD就绪就去处理

4.2.IO多路复用监听方式

IO多路复用监听FD的方式,通知的方式又有多种实现,常见的有:

select

poll

epoll

差异:

  • select和poll只会通知用户进程有FD就绪,但不确定具体是哪个FD,需要用户进程逐个遍历FD来确认
  • epoll则会在通知用户进程FD就绪的同时,把已就绪的FD写入用户空间

 

4.3.IO多路复用-select

select是Linux中最早的IO多路复用实现方案:

select函数:

1.nfds:这是需要监视的最大文件描述符加 1。举例来说,若要监视的文件描述符为 3、4、5,那么nfds的值就是5 + 1 = 6

为了能更精细地管理和监控不同类型的 IO 事件,select将FD分成三个集合

2.readfds该集合用于监视文件描述符的可读事件。

3.writefds:此集合用于监视文件描述符的可写事件。

4.exceptfds:这个集合用于监视文件描述符的异常事件。

执行流程:

首先用户空间

  • 1.创建fd_set rfds集合
  • 2.fd_set集合底层使用fds_bits[]来监听,共可以监听1024个Fd,要监听哪个就把哪个位置变为1。比如要监听fd = 1,2,5
  • 3.执行select函数,把fds_bits[]数组拷贝到内核空间

内核空间

  • 1.首先遍历fd_set看有没就绪
  • 2.没有就绪则睡眠,后台监听
  • 3.等待数据就绪被唤醒或超时
  • 4.fd = 1数据就绪,其他没有就绪剔除,返回结果有几个就绪了,拷贝到用户空间遍历哪个就绪了

处理完之后再次把要监听数据放到集合里执行select去监听往复处理数据

缺点:

  • 1.需要将整个fd_set从用户空间拷贝到内核空间,select结束还要再次拷贝回用户空间
  • 2.select无法得知具体哪个FD就绪,需要遍历整个FD_set,
  • 3.fd_set监听的fd数量不能超过1024

 

4.4.IO多路复用-poll

poll模式对select模式做了简单改进,但性能提升不明显,部分关键代码如下:

poll参数部分和select差不多主要区别在于fds,pollfd数组,没有去划分不同事件集合全部划分到一个数组当中。区别是哪种事件主要在于结构体中 events属性不同值代表不同监听类型

revents表示实际发生的事件类型,内核会把就绪的事件类型放在这个集合里,超时时间过了还没有就绪FD,就把这个值给成0返回给用户空间,这样一来就知道这个FD有没有发生事件。

IO流程:

1.创建pollfd数组,向其中添加关注的fd信息,数组大小自定义
2.调用poll函数,将pollfd数组拷贝到内核空间,转链表存储,无上限
3.内核遍历fd,判断是否就绪
4.数据就绪或超时后,拷贝pollfd数组到用户空间,返回就绪fd数量n
5.用户进程判断n是否大于0
6.大于0则遍历pollfd数组,找到就绪的fd

与select相比:

  • select模式中的fd_set大小固定为1024,而pollfd在内核中采用链表,理论上无上限
  • 监听fd越多,每次遍历消耗时间也越久,性能反而会下降

4.5.IO多路复用-epoll

epoll模式是对select和poll的改进,它提供了三个函数:

eventpoll结构体:使用红黑树存储要监听的FD,和使用链表记录就绪的FD

2.接下来我们就要去监听FD了,会使用到第二个函数

这个函数epoll_ctl将我们一个FD添加到eventpoll里面,相当于监听FD

传入的参数包括(需要添加到哪个eventpoll,是增删改哪个操作,要监听的fd,监听事件类型

3.第三个函数就是等待FD的监听就绪。函数传入参数(eventpoll指针,空数组用于接收就绪的FD,events数组最大长度,超时时间)返回就绪的数量

那么我怎么知道哪个FD就绪了呢?空数组就派上用场了

我们把这个数组拷贝到用户空间就知道哪些FD就绪了,不用去遍历

4.6.select poll epoll总结

select模式存在的三大问题:

  • 能监听的FD最大不超过1024
  • 每次select都需要把所有要监听的FD都拷贝到内核空间
  • 每次都要遍历所有FD来判断就绪状态

poll模式的问题:

poll模式利用链表解决了select中监听FD上限问题,但依然要遍历所有FD,如果监听较多,性能会下降

epoll模式中如何解决这些问题的?

  • 基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查效率都非常高,性能不会随监听FD数量增多而下降
  • 每个FD只需要执行一次epoll_ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝到Fd到内核空间
  • 内核会将就绪的FD拷贝到用户空间的知道位置,用户进程无需遍历所有FD就知道就绪的FD是谁

 

4.7.IO多路复用-事件通知机制

但FD有数据可读时,我们调用epoll_wait就可以得到通知。但是事件通知的模式有两种:

  • LevelTriggered:简称lLT.当FD有数据可读时,会重复通知多次,直至数据处理完成。是epoll的默认模式。
  • EdgeTriggered:简称LT.当FD有数据可读时,只会被通知一次,不管数据是否处理完成。

举个栗子:

  1. 假设一个客户端socket对应的fd已经注册到了epoll实例中
  2. 客户端socket发送了2kb的数据
  3. 服务端调用epoll_wait,得到通知说fd就绪
  4. 服务端从fd读取了1kb数据
  5. 回到步骤3(再次调用epoll_wait,形成循环)

总结:

ET模式避免了LT模式可能出现惊群现象

ET模式最好结合非阻塞IO读取FD数据,相比LT会复杂一些

4.8.IO多路复用-web服务流程

serverSocket:接收客户端请求

 

5.信号驱动IO 

信号驱动IO是内核建立SIGIO的信号管理并设置回调,但内核有FD就绪时,会发出SIGIO信号通知用户,期间用户可以执行其它业务,无需阻塞等待。

首先信号驱动IO第一阶段处理流程:

  1. 用户进程一上来不是调用recvfrom,而是系统调用sigaction指定FD绑定信号处理函数,立即结束不用阻塞等待,
  2. 如果没有数据内核帮我们监听如果有数据了帮我们去唤醒并且提交一个信号给我们用户进程,
  3. 之前建立的SIGIO信号处理函数就去处理这个信号,整个过程用户不用去等待可以去干其他事情

第二阶段和其他阻塞IO一致

那么你可能会问这个IO这么好为什么不用而要去用多路复用IO?

当有大量IO操作时,信号较多,SIGIO处理函数不能及时处理可能导致信号队列溢出

而且内核空间与用户空间频繁的信号交互性能也较低。

 

6.异步IO

异步IO的整个过程都是非阻塞的,用户进程调用完异步API后尽可以去做其他事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。

  • 1.异步IO并没有调用recvfrom函数而是调用aio_read通知内核说我想读哪个FD,读到哪里去就结束了。
  • 2.内核把数据准备就绪再把数据拷贝完成通知用户进程

异步IO在两个阶段都是非阻塞的

这种模式听着非常好但是高并发场景下如果对异步 I/O 的并发度控制不当,可能会导致系统资源过度使用,从而影响性能。

过多的异步网络请求可能会导致网络拥塞,降低系统的响应速度。

 

7.同步和异步

IO操作是同步还是异步,关键看数据在内核空间与用户空间的拷贝过程(数据读写的IO操作),也就是阶段二是同步还是异步:

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

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

相关文章

Jenkins 多分支流水线: 如何创建用于 Jenkins 状态检查的 GitHub 应用

使用 Jenkins 多分支流水线时,您可以将状态检查与 GitHub 拉取请求集成。 以下是状态检查的示例 要实现这些类型的状态检查,您需要创建一个与 Jenkins 主实例集成的 GitHub 应用。 在本博客中,我们将介绍如何创建一个 GitHub 应用&#xff…

大模型如何突破“知识盲区”?一场静悄悄的技术革命正在发生

大模型如何突破“知识盲区”?一场静悄悄的技术革命正在发生 凌晨三点,程序员李然盯着屏幕上的报错信息苦笑。他正在调试的智能客服系统,又一次把"北京今日体感温度"回答成了"建议穿羽绒服"。这不是代码错误,…

【SQL Server】数据探查工具1.0研发可行性方案

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 想抢先解锁数据自由的宝子,速速戳我!评论区蹲一波 “蹲蹲”,揪人唠唠你的超实用需求! 【SQL Server】数据探查工具1.0研发可行性方案…

Qt GUI 库总结

Qt GUI 库总结 Qt GUI 库(QtGui)是 Qt 框架中负责图形用户界面(GUI)开发的核心模块。本文将一步步详解 QtGui,从基础入门到高级应用,帮助你全面掌握其功能。以下内容包括环境配置、基本功能、核心特性及进…

如何在米尔-STM32MP257开发板上部署环境监测系统

本文将介绍基于米尔电子MYD-LD25X开发板(米尔基于STM35MP257开发板)的环境监测系统方案测试。 摘自优秀创作者-lugl4313820 一、前言 环境监测是当前很多场景需要的项目,刚好我正在论坛参与的一个项目:Thingy:91X 蜂窝物联网原型…

网络互连与互联网3

1.SMTP简单邮件传输协议,用于发送电子邮件,默认情况下是明文传输,没有加密机制。 SSL是一种安全协议,对电子邮件进行加密传输。 POP3主要用于接收电子邮件 IMAP用于接收电子邮件 2.采用存储-转发方式处理信号的设备是交换机 …

DICOM通讯(ACSE->DIMSE->Worklist)

DICOM 通讯协议中的 ACSE → DIMSE → Worklist 这条通讯链路。DICOM 通讯栈本身是一个多层的协议结构,就像 OSI 模型一样,逐层封装功能。 一、DICOM 通讯协议栈总体架构 DICOM 通讯使用 TCP/IP 建立连接,其上面封装了多个协议层次&#xf…

优化自旋锁的实现

在《C11实现一个自旋锁》介绍了分别使用TAS和CAS算法实现自旋锁的方案,以及它们的优缺点。TAS算法虽然实现简单,但是因为每次自旋时都要导致一场内存总线流量风暴,对全局系统影响很大,一般都要对它进行优化,以降低对全…

Excel 中让表格内容自适应列宽和行高

Excel 中让表格内容自适应列宽和行高 目录 Excel 中让表格内容自适应列宽和行高自适应列宽自适应行高在Excel中让表格内容自适应列宽和行高,可参考以下操作: 自适应列宽 方法一:手动调整 选中需要调整列宽的列(如果是整个表格,可点击表格左上角行号和列号交叉处的三角形全…

JWT令牌:实现安全会话跟踪与登录认证的利器

摘要:本文深入探讨了JWT令牌在实现会话跟踪和登录认证方面的应用,详细介绍了JWT令牌的概念、组成、生成与校验方法,以及在实际案例中如何通过JWT令牌进行会话跟踪和登录认证的具体实现步骤,为系统的安全认证机制提供了全面且深入的…

Mybtis和Mybatis-Plus区别

MyBatis 和 MyBatis-Plus 是 Java 中常用的持久层框架,MyBatis-Plus 是在 MyBatis 基础上增强的工具包,让开发更便捷、高效。下面是两者主要的区别: ✅ 核心区别总结: 特性MyBatisMyBatis-Plus配置复杂度需要手写大量 XML 或注解…

JavaScript 性能优化实战

一、代码执行效率优化 1. 减少全局变量的使用 全局变量在 JavaScript 中会挂载在全局对象(浏览器环境下是window,Node.js 环境下是global)上,频繁访问全局变量会增加作用域链的查找时间。 // 反例:使用全局变量 var globalVar = example; function someFunction() {con…

学习笔记十六——Rust Monad从头学

🧠 零基础也能懂的 Rust Monad:逐步拆解 三大定律通俗讲解 实战技巧 📣 第一部分:Monad 是什么? Monad 是一种“包值 链操作 保持结构”的代码模式,用来处理带上下文的值,并方便连续处理。 …

PL/SQL登录慢,程序连接Oracle 提示无法连接或无监听

PL/SQL登录慢,程序连接Oracle 提示无法连接或无监听 错误提示:ORA-12541: TNS: 无监听程序 的解决办法, 现象:PL/SQL登录慢,程序连接Oracle 提示无法连接或无监听 监听已经正常开起,但还是PL/SQL登录慢或…

Windows10,11账户管理,修改密码,创建帐户...

在这里,我们使用微软操作系统的一款工具:netplwiz 它可以非常便捷的管理用户账户. 一:修改密码(无需现在密码) 01修改注册表 运行命令:regedit 在地址栏输入: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Passwor…

电脑 BIOS 操作指南(Computer BIOS Operation Guide)

电脑 BIOS 操作指南 电脑的BIOS界面(应为“BIOS”)是一个固件界面,允许用户配置电脑的硬件设置。 进入BIOS后,你可以进行多种设置,具体包括: 1.启动配置 启动顺序:设置从哪个设备启动&#x…

iOS 冷启动时间监控:启动起点有哪些选择?

⏱️ iOS 冷启动时间监控:启动起点有哪些选择? 作者:侯仕奇 来源:sqi.io 在监控 iOS 冷启动性能时,一个关键问题是:如何精确记录 App 冷启动的开始时间? 本文将对不同的“冷启动起点”监控方式…

onlyoffice关闭JWT后依然报错如何解决?

onlyoffice关闭JWT后依然报错如何解决? 一、部署方式 我是以docker方式部署的,直接通过环境变量禁用了JWT,命令如下: docker run -d \--name onlyoffice-no-jwt \--restartalways \-p 8069:80 \-e JWT_ENABLEDfalse \onlyoffic…

rk3588 驱动开发(一)字符设备开发

3.字符设备驱动开发 3.1 什么是字符设备驱动 字符设备:就是一个个字节,按照字节流进行读写操作的设备,读写是按照先后顺序的。 举例子:IIC 按键 LED SPI LCD 等 Linux 应用程序调用驱动程序流程: Linux中驱动加载成功…

设计模式 --- 外观模式

外观模式是一种结构型设计模式,为复杂子系统提供​​统一的高层接口​​,通过定义一个外观类来​​简化客户端与子系统的交互​​,降低系统耦合度。这种模式隐藏了子系统的复杂性,将客户端与子系统的实现细节隔离开来,…