Redis原理 - IO详解

原文首更地址,阅读效果更佳!

Redis原理 - IO详解 | CoderMast编程桅杆icon-default.png?t=N5K3https://www.codermast.com/database/redis/redis-IO.html

用户空间与内核空间

任何Linux 系统的发行版,其系统内核都是 Linux 。我们的应用都需要通过 Linux 内核与硬件交互。

 

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

  • 内存的寻址空间划分为两部分:内核空间、用户空间

32 位的操作系统,寻址地址就为 0 ~ 2322 ^ {32}232

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

当进程运行在用户空间时称为用户态,运行在内核空间时称为内核态。

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

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

 

5种IO模型

  1. 阻塞 IO(Blocking IO)
  2. 非阻塞 IO(Nonblocking IO)
  3. IO 多路复用(IO Multiplexing)
  4. 信号驱动 IO(Signal Driven IO)
  5. 异步 IO(Asynchronous IO)

#阻塞IO

顾名思义,阻塞 IO 就是在等待数据拷贝数据到用户空间两个阶段过程中都必须阻塞等待。

 

  1. 用户线程发出 IO 请求
  2. 内核会去查看数据是否准备就绪,如果没有准备就绪那么就会一直等待,而用户线程就会处于阻塞状态,用户线程处于阻塞状态
  3. 当数据准备就绪以后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除阻塞状态

可以看到,阻塞IO模型中,用户进程在两个阶段都是阻塞状态。

#非阻塞IO

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

 

  1. 等待数据阶段,如果数据没有就绪,则立刻返回 EWOULDBLOCK。这个过程用户进程是非阻塞的,但是用户进程会一直发起请求,忙轮训,直到内核处理才开始停止轮训。
  2. 数据就绪以后,再将数据从内核中拷贝至用户空间。这个阶段用户进程是阻塞的。

可以看到,非阻塞 IO 模型中,用户进程在第一个阶段是非阻塞的,在第二个阶段是阻塞的。虽然是非阻塞的,但是性能并没有得到提高,而且忙等机制会导致 CPU 空转,CPU 使用率暴增。

#IO多路复用

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

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

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

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

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

 

实现 IO 多路复用的技术有三种方式:

  • select
  • poll
  • epoll

差异:

  • select 和 poll 只会通知用户进程有FD就绪,但是不确定是那个 FD,需要用户进程逐个遍历 FD 来确认
  • epoll 会通知用户进程 FD 就绪的同时,把已就绪的 FD 写入用户空间,直接能定位到就绪的 FD

#SELECT

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

// 定义类型别名 __fd_mask,本质是 long int
typedef long int __fd_mask;

/* fd_set 记录要监听的fd集合,及其对应状态 */
typedef struct {
    // fds_bits是long类型数组,长度为 1024/32 = 32
    // 共1024个bit位,每个bit位代表一个fd,0代表未就绪,1代表就绪
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
    // ...
} fd_set;


// select函数,用于监听多个fd的集合
int select(
    int nfds,// 要监视的fd_set的最大fd + 1
    fd_set *readfds,// 要监听读事件的fd集合
    fd_set *writefds,// 要监听写事件的fd集合
    fd_set *exceptfds,  // 要监听异常事件的fd集合
    // 超时时间,nulT-永不超时;0-不阻塞等待;大于0-固定等待时间
    struct timeval *timeout
);

 

具体流程如下:

  1. 用户空间中创建 fd_set rfds
  2. 假如要监听 fd = 1,2,5
  3. 用户空间中执行 selec(5 + 1, rfds, null, null, 3)
  4. 将用户空间中创建的 fd_set rfds 数组拷贝到内核空间中
  5. 内核空间中遍历拷贝后的 fd_set rfds 数组
  6. 如果没有就绪,则将该位置的 fd 设置为0。

select模式存在的问题:

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

#POLL

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

// pollfd 中的事件类型
#define POLLIN      //可读事件
#define POLLOUT     //可写事件
#define POLLERR     //错误事件
#define POLLNVAL    //fd未打开

// pollfd结构
struct pollfd{
    int fd;             // 要监听的 fd
    *short int events;  // 要监听的事件类型:读、写、异常
    short int revents;  // 实际发生的事件类型
}

// poll函数
int poll(
    struct pollfd xfds, // pollfd数组,可以自定义大小
    nfds_t nfds,        // 数组元素个数
    int timeout         // 超时时间
);

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 越多,每次遍历消耗的时间也越久,性能反而会下降

#EPOLL

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

struct eventpoll{
    //...
    struct rb_root rbr; // 一颗红黑树,记录要监听的fd
    struct list_head rdlist;  // 一个链表,记录就绪的 FD
    //...
}

// 1.会在内核创建eventpolL结构体,返回对应的句柄epfd
int epoll create(int size);

// 2.将一个FD添加到epol的红黑树中,并设置ep_poli_calLback
// calTback触发时,就把对应的FD加入到rdlist这个就绪列表中
int epoll _ctl(
    int epfd,   // epoll实例的句柄
    int op,     // 要执行的操作,包括:ADD、MOD、DEL
    int fd,     // 要监听的 FD
    struct epoll_event *event // 要监听的事件类型: 读、写、异常等
);

// 3.检查rdlist列表是否为空,不为空则返回就绪的FD的数量
int epoll wait(
    int epfd,       // eventpoll 实例的句柄
    struct epoll_event *events, // 空event 数组,用于接收就绪的 FD
    int maxevents,  // events 数组的最大长度
    int timeout // 超时时间,-1永不超时;0不阻塞;大于0为阻塞时间
);

 

#事件通知机制

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

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

举个例子

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

结论

  • ET 模式避免了 LT 模式可能出现的惊群现象
  • ET 模式最好结合非阻塞 IO 读取 FD 数据,相比 LT 会复杂一些

#WEB服务流程

基于 epoll 模式的 web 服务的基本流程图: 

#总结

select 模式的存在的三个问题:

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

poll 模式的问题:

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

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

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

#信号驱动IO

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

 

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

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

#异步IO

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

 

在异步 IO 模型中,用户进程在两个阶段都是非阻塞的状态。

异步 IO 模型虽然很简单,但是在高并发的访问下,内核中会处理大量请求,容易导致内核崩溃。

#同步和异步

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎么给PDF添加图片水印?其实很简单,看这篇就会了!

许多人都意识到版权问题的重要性,尽管在日常生活中我们可能很少遇到,但在办公和学习中却经常涉及到此类问题。例如,我们辛辛苦苦制作的PDF文件,如何确保不被他人盗用呢?这就涉及到如何为PDF添加图片水印的问题,相当于…

经典基于外观的SLAM框架-RTABMAP(RGBD视觉输入方案)

经典基于外观的SLAM框架-RTABMAP 文章目录 经典基于外观的SLAM框架-RTABMAP1. RTABMAP整体框架2.RTABMAP的内存管理机制3. 视觉里程计4. 局部地图5. 回环检测与图优化6. 代码工程实践 1. RTABMAP整体框架 RTABMAP是采用优化算法的方式求解SLAM问题的SLAM框架,本赛题…

【python 第三方库安装换源】

换源: pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/其他国内第三方库的下载源地址: 阿里云:http://mirrors.aliyun.com/pypi/simple/ 科技大学:https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣&a…

Vue实例知识点分享

文章目录 导文下面是创建 Vue 实例的基本步骤 常用的 Vue 实例方法和属性总结 导文 Vue的实例是用来创建 Vue 应用程序的对象。通过实例化 Vue 构造函数,我们可以创建一个具有响应式数据、计算属性、方法和生命周期钩子等特性的 Vue 实例。 下面是创建 Vue 实例的基…

python技术分享

文章目录 python介绍应用领域环境搭建基础知识编程工具变量基本数据类型容器数据类型程序结构运算符函数类 技巧总结python内存管理python常用技术python的缺陷优化python的编码规范提升性能总结 python介绍 弱类型的语言 声明一个变量,直接赋值即可,简…

Android强大的原生调试工具adb的常用命令

文章目录 ADB简介常用命令列出链接的设备进入设备的shell环境设备日志安装应用程序卸载应用程序将本地文件复制到调试设备上将设备上的文件拉取到本地启动程序强制停止程序运行截图屏幕录制列出调试设备所有的应用的报名 结语 ADB简介 ADB(Android Debug Bridge&am…

【从零开始学习JAVA | 第二十一篇】常见API介绍 System

目录 前言: System: System类中静态方法: 总结: 前言: system 是一个很底层的 API,是一个工具类,提供了一些与系统相关的方法。他在我们写项目的时候提供了一些非常实用的方法,本…

量子机器学习Variational Quantum Classifier (VQC)简介

变分量子分类器(Variational Quantum Classifier,简称VQC)是一种利用量子计算技术进行分类任务的机器学习算法。它属于量子机器学习算法家族,旨在利用量子计算机的计算能力,潜在地提升经典机器学习方法的性能。 VQC的…

优化--分类树,我从2s优化到0.1s

1.前言 分类树查询功能,在各个业务系统中可以说随处可见,特别是在电商系统中。 但就是这样一个简单的分类树查询功能,我们却优化了5次。 到底是怎么回事呢? 2.背景 我们的网站使用了SpringBoot推荐的模板引擎:Thym…

【Python实战】Python采集情感音频

成年人的世界真不容易啊 总是悲伤大于欢喜 爱情因为懵懂而快乐 却走进了复杂和困惑的婚姻 前言 我最近喜欢去听情感类的节目,比如说,婚姻类,我可能老了吧。我就想着怎么把音乐下载下来了,保存到手机上,方便我们业余时…

Jnpf低代码开发平台

一、写在前面 低代码开发平台,一个号称能在几分钟的时间里开发出一套公司内部都可使用的应用系统开发工具。 很多人或许都隐隐听说过低代码,因为低代码不仅远名国外,国内的腾讯、阿里、华为、网易、百度等科技巨头也纷纷入局,足以…

URL到页面: 探索网页加载的神秘过程

当我们从浏览器的地址栏输入 URL, 按下回车, 再到最后出现需要的网页界面, 这中间究竟发生了什么, 接下来就一步步进行解析. 主要是如下过程: 输入网址DNS 解析客户端发送 HTTP 请求建立 TCP 连接服务器处理请求, 计算响应, 返回响应浏览器渲染页面关闭连接 本篇中只是概述整…

docker 操作手册

名词解释 images:封装了应用程序的镜像 tag:镜像的标记,一个镜像可以创建多个标记 container:装载镜像并运行 常用命令 查看容器 docker ps -a //查看全部镜像 启动容器 docker start mysql //启动mysql容器 停止容器 doc…

Maven(三):Maven的组成详解

文章目录 坐标和依赖坐标详解依赖配置依赖范围传递性依赖依赖调节可选依赖优化排除依赖归类依赖优化依赖 仓库本地仓库远程仓库仓库镜像常用搜索地址 生命周期与插件三套生命周期clean生命周期default生命周期site生命周期 插件 聚合与继承更加灵活的构建常见问题使用jdk来运行…

TuyaOS 开发固件OTA上传固件指南

文章目录 一、产品创建二、TuyaOS设备开发三、固件上传 通过TuyaOS接入涂鸦云的产品全部默认支持固件OTA功能,TuyaOS设备实现固件OTA需要: 自定义产品创建TuyaOS嵌入式开发固件上传固件OTA配置与发布 等步骤实现产品OTA。本文重点讲述TuyaOS开发模式下&…

基于数据驱动 U-Net 模型的大气污染物扩散快速预测,提升计算速度近6000倍

项目背景 当前,常见的大气污染预测模型大多是基于物理机理构建的,比如空气质量预测模型 Calpuff、AERMOD、CMAQ 等。然而,这些模型运算较为复杂,对于输入数据的要求非常高,运算耗时也比较长,适合用于常规固…

如何在 ZBrush 和 UE5 中创建精灵吟游诗人(P1)

小伙伴们大家好,今天 云渲染小编给大家带来的是CG艺术家Hugo Sena关于“精灵吟游诗人”项目背后的工作流程,讨论了角色身体、服装和竖琴的工作流程,并解释了如何在虚幻引擎 5 中设置灯光。篇幅较长,分为上下两篇,大家接…

为摸鱼助力:一份Vue3的生成式ElementPlus表单组件

目录 一、实现背景 二、简介 三、组织架构设计 四、实现方式 五、代码示例 六、示例代码效果预览 七、项目预览地址 & 项目源码地址 目前项目还有诸多待完善的地方,大家有好的想法、建议、意见等欢迎再次评论,或于github提交Issues 一、实现…

【吃透网络安全】2023软考网络管理员考点网络安全(三)计算机系统安全评估

涉及知识点 计算机系统安全评估准则,计算机系统安全评估历史,软考网络管理员常考知识点,软考网络管理员网络安全,网络管理员考点汇总。 后面还有更多续篇希望大家能给个赞哈,这边提供个快捷入口! 第一节…

解决vue依赖报错SockJSServer.js出现Cannot read property ‘headers‘ of null

前言 在做新的需求需要变更vue的项目代码时突然出现报错 TypeError: Cannot read property ‘headers’ of null at Server.socket.on (***/node_modules/webpack-dev-server/lib/servers/SockJSServer.js:68:32) 不清楚为什么突然出现了这个问题,之前在这个vue项目…