Redis原理 - IO详解

news2024/11/19 13:22:33

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

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操作),也就是阶段二是同步还是异步:

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

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

相关文章

02py游戏开发基础

版本 pygame 2.4.0 (SDL 2.26.4, Python 3.8.2) Hello from the pygame community. https://www.pygame.org/contribute.html Python开发基础 Pygame常用模块 background_image_filename "bg.jpg"#设置图像文件名称 mouse_image_filename "ship.bmp"# 将…

JVM优化00

JVM优化 0.目标 了解下我们为什么要学习JVM优化掌握jvm的运行参数以及参数的设置掌握jvm的内存模型(堆内存)掌握jmap命令的使用以及通过MAT工具进行分析掌握定位分析内存溢出的方法掌握jstack命令的使用掌握VisualJVM工具的使用 1.为什么学习JVM优化 …

LeetCode - #82 删除排序链表中的重复元素 II

文章目录 前言1. 描述2. 示例3. 答案关于我们 前言 我们社区陆续会将顾毅(Netflix 增长黑客,《iOS 面试之道》作者,ACE 职业健身教练。)的 Swift 算法题题解整理为文字版以方便大家学习与阅读。 LeetCode 算法到目前我们已经更新…

异常的介绍与处理

目录 第七章 异常 1.异常 2.处理方法 2.1.try-catch 2.2.多重catch块 2.3.finally 2.4.throw 与 throws 2.5.程序分析 3.自定义异常 内容仅供学习交流,如有问题请留言或私信!!!!! 有空您就点点赞…

【计算机视觉】计算机视觉的简单入门代码介绍(含源代码)

文章目录 一、介绍二、项目代码2.1 导入三方包2.2 读取和展示图片2.3 在图像上绘画2.4 混合图像2.5 图像变换2.6 图像处理2.7 特征检测 一、介绍 计算机视觉是一门研究计算机如何理解和解释图像和视频的学科。 它的目标是让计算机能够模拟人类视觉系统,让它们能够识…

Vivado 下 LED 灯闪烁实验

目录 Vivado 下 LED 灯闪烁实验 1、简介 2、实验环境 3、实验任务 4、硬件设计 5、程序设计 5.1、LED 闪烁模块代码 5.2、Vivado 仿真验证 5.2.1、编写 TB 仿真代码 6、下载验证 6.1、添加约束文件 .xdc 6.2、下载验证 注意:一定要先把下载器的一端连接…

FDM3D打印系列——2、一些基础概念

大家好,我是阿赵。 在买3D打印机之前,一般都会很迷茫,不知道3D打印机是怎样工作的,也不知道有哪些地方需要注意。上一篇文章通过打印一个模型,完整的体验了一次FDM打印3D模型的过程。这里解释一些在3D打印里面的比较基…

PMP考试自学可以吗?

PMP考试不建议自学,听劝,不该省的别省。 PMP现在没有自学了,今年3月的考试报了培训班的同学都说难,培训班的资源老师的专业,怎么也比自己单打独斗强吧,真的报培训班省事很多。 PS:网上说包过的…

X86架构与Arm架构区别

X86架构和ARM架构是主流的两种CPU架构,X86架构的CPU是PC服务器行业的老大,ARM架构的CPU则是移动端的老大。X86架构和arm架构实际上就是CISC与RISC之间的区别,很多用户不理解它们两个之间到底有哪些区别,实际就是它们的领域不太相同…

Liunx安装window中文字体解决中文变方框问题

问题现象描述 没安装中文字体,有可能导致你的程序在windows上运行的好好的,部署到linux上运行就可能出现汉字变成小方块的问题,场景举例:svg文件转png图片,原svg中的中文会变成方框 按如下方法安装中文字体后&#xf…

南卡OE骨传导开放式蓝牙耳机评测!舒适与音质并存!

平时买耳机的时候,你最先会关注什么方向呢?是舒适、美观,还是音质、防水? 对于我来说,首先是功能。作为一个经常健身、跑步的人,最讨厌的就是平时运动流汗进入耳朵之后那种粘腻感觉。时间长了还容易让耳道…

凹下去的白色按钮

先看效果&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>凹下去的按钮</title><style>import url("https://fonts.googleapis.com/css2?famil…

【SIGMOD 2023】深度学习弹性数据流水线系统GoldMiner,大幅提升任务和集群效率

第一板块&#xff1a;开篇 近日&#xff0c;阿里云机器学习平台PAI和北京大学杨智老师团队合作的论文《GoldMiner: Elastic Scaling of Training Data Pre-Processing Pipelines for Deep Learning》被数据库领域顶会SIGMOD 2023接收。 GoldMiner观察到深度学习任务中的数据预…

redis登录常见报错

第一次接触redis登录的时候遇见几个报错 一、使用以下两个命令报错&#xff1a; ./redis-cli -h 127.0.0.1 -p 6380 ./redis-cli -p 6380 报错&#xff1a;Could not connect to Redis at 127.0.0.1:6380: Connection refused 应该和redis.conf中配置的bind字段的IP有关…

一文全揽C/C++中所有指针相关知识点(从原理到示例,学不完根本学不完!!!!)

本篇会对C/C中【常见指针相关知识】一直进行总结迭代&#xff0c;记得收藏吃灰不迷路&#xff0c;一起学习分享喔 请大家批评指正&#xff0c;一起学习呀~ 一、指针基本知识1.1 指针的定义1.2 &#xff08;*&#xff09; 和&#xff08; &&#xff09; 运算符1.3 如何声明…

SM国密算法(四) -- SM3算法

一、简介 SM3密码杂凑算法是中国国家密码管理局2010年公布的中国商用密码杂凑算法标准。适用于商用密码应用中的数字签名和验证。 SM3是在[SHA-256]基础上改进实现的一种算法&#xff0c;其安全性和SHA-256相当。SM3和MD5的迭代过程类似&#xff0c;也采用Merkle-Damgard结构。…

OpenCV(图像处理)-基于python-滤波器(低通、高通滤波器的使用方法)

1.概念介绍2. 图像卷积filter2D() 3. 低通滤波器3.1 方盒滤波和均值滤波boxFilter()blur() 3.2 高斯滤波&#xff08;高斯噪音&#xff09;3.3 中值滤波&#xff08;胡椒噪音&#xff09;3.4 双边滤波 4. 高通滤波器4.1Sobel&#xff08;索贝尔&#xff09;&#xff08;高斯&am…

STL之set和map

目录 一. 原型二. 模板参数适配三. 迭代器四. 插入函数的修改四. 代码 一. 原型 简单实现的红黑树 template<class K, class V> struct RBTreeNode {RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> …

FPGA_学习_11_IP核_RAM_乒乓操作

本篇博客学习另一个IP核&#xff0c;RAM。 用RAM实现什么功能呢&#xff1f; 实现乒乓操作。 乒乓操作是什么呢&#xff1f; 参考&#xff1a; FPGA中的乒乓操作思想_fpga中乒乓操作的原因_小林家的龙小年的博客-CSDN博客 何为乒乓操作_fanyuandrj的博客-CSDN博客 以下是本人理…

Clion开发STM32之日志模块(参考RT-Thread)

前言 日志对于开发和排错方面有着很重要的影响。通过查看RT-Thread的源码&#xff0c;将日志的打印输出划分到具体的文件和通过宏定义对具体的日志等级进行划分&#xff0c;这样就比较方便。结合此源码的形式将其分离出来&#xff0c;作为自己项目的日志框架进行使用分为日志驱…