【Redis】网络模型(day10)

在本篇文章中,主要是对五种网络模型进行一个简单的介绍,然后对Redis4.0和6.0的网络模型进行一个概述。

用户空间和内核空间

在Linux系统上,分为用户空间、内核空间和硬件设备。硬件设备主要包括CPU、内存、网卡等物体,内核应用去使用硬件设备工作时,需要操纵其对应的驱动。通过不同驱动的分类,内核空间也就形成了内存管理、文件管理、进程管理以及网络管理等内容。用户应用在工作时,是不能直接操作硬件设备及其驱动,需要去调用内核应用,从而再调用驱动,最后调用到硬件设备。原因是因为内核应用也是软件,本身就会消耗一定的资源,而用户应用也会消耗一定的资源,为了避免用户应用和内核应用之间的冲突导致内核应用出现异常、甚至崩溃,计算机把用户应用和内核应用进行分离。并且,进程的寻址空间也会划分成两部分:内核空间和用户空间。

Linxu中把命令进行了划分,Ring3表示受限的命令,他是不能直接调用系统资源;Ring0表示特权命令,他能调用一切系统资源。

内核应用可以执行特权命令,调用一切系统资源。

用户应用只能执行受限的命令,所以,他是不能够直接调用系统资源,只能通过内核应用提供的接口才能间接去调用系统资源。

用户应用在执行受限命令时,他就是在用户空间做事,所以此时就是用户态。当执行到了特权命令时,应用对应的线程就会去内核应用做事,此时就是内核态。所以,用户态和内核态之间是可以进行切换的。

以读写IO为例来说明内核态和用户态的切换:首先,明确的一点就是为了提高IO效率,用户空间和内核空间都加入了缓冲区。当服务和客户端建立连接之后,客户端请求发送之后,服务就要接收数据,此时,用户应用就会向内核应用发送命令,表示自己要接收数据,内核空间就会去硬件设备这里接收数据,当数据从硬件设备到了内核缓冲区后,用户缓冲区就会读取这些数据,这就是读数据的过程。写数据时,用户应用先把数据从用户缓冲区拷贝到内核缓冲区,然后内核缓冲区再写入硬盘或者其他硬件设备中。

IO模型

在IO中,根据读取数据的来源可以分成磁盘IO和网络IO,磁盘IO就是对磁盘的读写工作,网络IO就是通过网络进行数据的拉取和输出。

  • 网络IO:用户缓冲区等待数据进入网卡,然后从网卡中读取到内核缓冲区,再从内核缓冲区中拷贝到系统缓冲区中。
  • 磁盘IO:把数据从磁盘读取到内核缓冲区,然后从内核缓冲区拷贝到系统缓冲区。

下述内容以读取数据为例来分别介绍这些模型:

服务器和客户端建立连接之后,当客户端有请求发送过来时,用户应用就会向内核应用发送读取数据的命令,内核应用等到网卡等硬件设备准备好数据后,内核缓冲区首先读取硬件设备中准备好的数据,然后再拷贝到应用缓冲区中,这就是读取数据的一个简单流程。

在网络IO模型中,主要是1步骤和2步骤之间处理数据有差异。

 

a7ae157b7362075f5f3ce42b6d7ec3d1.png

 阻塞IO

阻塞IO(Blocking IO),顾名思义,就是读取数据时需要阻塞等待。

用户应用向内核应用发起recvfrom的读取命令之后,此时内核应用发现缓冲区中没有数据,于是就会等待,直到硬件设备中准备好数据之后,内核缓冲区首先从硬件设备中读取数据,然后再将数据拷贝到用户缓冲区中。

在这个过程中,第一个需要等待的地方就是用户应用发起命令之后,会一直阻塞等待,第二个需要等待的地方就是数据准备好之后,用户应用需要阻塞等待数据拷贝完成。

因此,阻塞IO其实就是命令发起之后,会一直等待,直到数据读取完毕。

a7ae157b7362075f5f3ce42b6d7ec3d1.png

非阻塞IO

非阻塞IO,即用户应用发起读取数据的命令之后,如果没有准备好数据,那么内核应用就会立即返回响应,表示数据没有准备好。然后,用户应用就会持续不断的发送命令,直到数据准备完成,然后就又会阻塞等待直到数据拷贝完成。

非阻塞IO在工作时,第一阶段不会阻塞等待,只有在第二个阶段读取数据时,才会进入阻塞状态。

本质上,非阻塞IO并没有啥用,甚至还有可能增加系统资源的消耗。本来在阻塞IO中,系统啥事也不干,等着就行。结果来到非阻塞IO这里,系统还得强迫一直发送请求,直到数据准备完成,这导致了CPU空转,使得CPU使用率暴增。

对于阻塞IO和非阻塞IO两种模型中,读取数据时都会阻塞等待,不过在第一阶段等待数据就绪时处理方式不同,阻塞IO是持续阻塞,直到数据准备好,而非阻塞IO则是一直发送读取数据的请求。不过两种方案本质上还是一直占用着当前线程,没有啥含金量。

假设当服务端处理客户端socket时,是单线程,一次只能处理一个socket,那无论是阻塞还是非阻塞,都是针对一个socket在干活,即使其他socket已经准备就绪,他也不会去读取其他数据。只有当这个socket的数据准备就绪并且读取完成之后,该线程才会去进行下一个socket的读取。假设当前是多线程,那每一个线程都会对应一个socket去读取,如果现在同时有十万个请求,那就得十万个线程,或者等待,无论是哪种情况,系统性能都是极差的。

于是,我们想,能不能一个线程去对应多个socket,这样,当哪个数据就绪之后,就去读哪个数据,让线程一直工作,这样就不用一对一,也不需要持续等待了,其实,这种方法就是接下来要讲的IO多路复用。

IO多路复用

概述

在Linux系统中,秉承着一个理念就是一切皆文件,常规文件就不用说了,视频、硬件设备以及网络套接字也属于是文件。并且,每一个文件都会对应一个文件描述符表,简称FD,是一个从0开始递增的无符号整数。

IO多路复用,就是先使用一个函数来监控这些socket,其实就是监控一堆FD,看看有没有就绪(可读或者可写),如果有就绪了的,那么就用户应用才会发送recvfrom命令去读取数据,此时就不需要等待了,因为肯定是有数据就绪的。

IO多路复用中,对于监听函数来说,一共是有3个:select、poll、epoll。对于select和poll来说,只会通知用户应用说有进程就绪,但是具体哪一个,不知道,需要用户应用自己来确定。而对于epoll来说,则在通知时不仅通知有数据准备就绪,还会具体说明是哪几个数据。

 

4fd582c88f73bb60e9cabd393179be9f.png

select

select函数是Linux中最早的多路复用的实现方案。

 

e1e7bec7fc7d97c9e4fcfc84f5a6b0f3.png

如上所示,是select函数的参数。

1. 创建fd_set,也就是要监听的fd集合,默认全部为0,并且是读事件集合set。

2. fd_set中是以比特位存储,最多存储1024个FD,假设要监听的fd为1,2,4,6,那就把对应的比特值置为1。

对于1024的来源,__FD_SETSIZE的大小是1024,__NFDBITS的大小是32,因此结果是32,所以该数组最多放32个元素,而数组的类型是long int,即4个字节,并且由于该数组保存是以比特位为单位的,所以最多保存32 * 4 * 8 = 1024个元素。

3. 调用select函数并传参select(7, set, null, null, 3) ,并且将集合从用户空间拷贝到内核空间。

4. 内核应用遍历一次传过来的集合,此次遍历到7,因为标记的FD最大值是6。查看是否有就绪的集合,如果有那么就返回结果。如果没有,那么就查看超时时间是啥,如果是0直接返回,如果是null表示持续等待直到有就绪的为止,如果大于0那么就是有固定等待时间,等到时间之后就会返回空,反之等待过程中被唤醒,然后返回就绪的。

5. 等待数据就绪唤醒之后,就会再次遍历,如果是1并且没就绪,那么就置为0,反之不管。这样,集合中保存的值是1的就是就绪的,并且,select函数会返回一个值,表示就绪的个数。

6. 把集合再次拷贝到应用空间中。

7. 应用空间遍及集合,找到就绪的FD,就会去发起读取数据的命令。

缺点:

1. select函数需要将整个fd_set集合从用户空间拷贝到内核空间,然后再拷贝回来。

2. select无法得知具体是哪个fd就绪,需要遍历整个集合。

3. fd_set集合的个数不能超过1024。

poll

 

0d248725081c316e888eb9c619b39ae5.png

poll函数是对select函数的改进,但是效果并不明显。

1. 创建pollfd数组,向其中添加要监听的FD信息,数组大小自定义(改进的点,不再是1024个)。

2. 调用poll函数,并且将pollfd数组拷贝到内核空间,把数组转成链表存储,没有上限。

3. 遍历链表,判断是否就绪。

4. 依旧是和select函数一样,跟超时时间有关。不过最终的结果就是把pollfd数组拷贝到用户空间,并且返回就绪的fd数量。

5. 用户应用判断是否有就绪的FD,有的话就遍历polfd数组,找到就绪的FD。

6. 找到之后,发起recvfrom命令去读取数据。

缺点:

可以看到,poll相比select唯一变的点就是可以存在无限个,其他的问题依旧没变。并且,随着而来的问题是,监听的FD越多,每次遍历消耗的时间也就越长,性能反而会下降。

epoll

epoll是对select和poll的巨大改进,他不仅解决了重复拷贝的问题,还能准确返回就绪的FD。

首先,调用epoll_create函数,直接会在内核空间中创建eventpoll结构体。结构体中是一个红黑树,一个链表,红黑树表示要监听的FD,链表则表示已经就绪的FD。创建好之后,还会返回对应的句柄epfd。

 

1e53285d731cb3061d54d9d1b349b576.png

 然后,调用epoll_ctl函数,其中的参数分别epfd,即句柄、op,表示要进行的操作,由于红黑树非常快,因此影响并不大、fd,表示要监听的FD、epoll_event,表示要监听的事件类型。假设是添加操作,那么还会设置一共回调函数,当回调函数被触发时,进行的操作是将FD添加到对应的就绪链表上。

 

b9c583eb06ce707eca5ed666d0eedf52.png

最后,调用epoll_wait函数,其中的参数分别是epfd,即句柄、epoll_event,空event数组,用于接收就绪的FD、maxevents,要接受数组的最大长度、timeout,超时时间,对于超时时间来说,和上两种都一样(当有就绪FD时,直接返回;没有就绪FD时,判断等待时间,当时间为null时,等待直到有就绪FD返回,就绪时间为0,直接返回,就绪时间大于0,等待时间结束,如果还没有就绪的,那就返回,在这期间有就绪的就立即返回)。

在epoll中,唯一一次拷贝就是有FD就绪之后,把FD数组拷贝到应用空间中。

b9c583eb06ce707eca5ed666d0eedf52.png

 时间通知机制

当FD有数据可读时,我们调用epoll_wait就可以得到就绪的FD数组,但是事件通知的模式有两种(LT和ET):

LT:当FD有数据可读时,会重复通知多次,直至数据处理完成,是epoll的默认模式。

ET:当FD有数据可读时,只会被通知一次,不管数据是否处理完成。

 

6bcd3fba49e2de9e4705ec5df73966e5.png

 

285b3b04425627d95df6292b44f6698c.png

 web服务流程

1. 服务端调用epoll_create函数,在内核空间创建一个红黑树用于接收要监听的FD,创建一个链表用来记录就绪的FD。

2. 服务端会先创建一个ServerSocket,然后调用epoll_ctl函数,将其对应的FD注册进去。

3. 当有客户端要来连接服务端时,epoll_wait函数就会调用,将就绪的也就是ServerSocket拿到,然后将其拷贝到用户空间,用户空间就会去发起读取数据的命令。

4. 由于此时只有ServerSocket,所以读取到的一定就是客户端socket,然后再次调用epoll_ctl函数,将其注册到红黑树上。

5. 链表上再有就绪的FD时,此时就无法确定是要接受的命令还是要注册的FD,所以要判断一下,如果是命令的话,那么就读取数据,做出响应,反之就读取数据,进行注册。

6. 不过,其实在第一次读取数据时就会进行判断,防止大家迷糊,所以在第五步做了声明。

 

b1258975e8a8b88117f823fbfd0c669b.png

总结 

select存在的三个问题是:

1. 监听的FD数量不能超过1024

2. 每次select都要把所有监听的FD拷贝到内核空间,有可能还要再次拷贝回来

3. 不确定就绪的FD是哪个,每次都要进行遍历

poll存在的问题是:

虽然解决了select上FD数量限制的问题,但是依然要循环遍历才能找到就绪的FD,如果FD过多,那么性能会下降。

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

1. 针对数量问题,epoll采用红黑树来存储,理论上无上限,而且其增删改效率极高,性能不会随监听的FD数量增多而下降。

2. 针对多次拷贝的问题,把FD插入到红黑树之后,每次调用epoll_wait函数不用自带参数,也就是不用拷贝要查询的FD列表,只需要在有就绪FD的情况下,把就绪的FD拷贝一份即可。

3. 针对循环遍历FD数组的情况,epoll专门搞了一个链表来放就绪的FD,当epoll_wait来时,只需要把就绪的拿走就行,无需其他操作。

Redis网络模型

Redis到底是单线程还是多线程?

从Redis全局来说,他是多线程的,从Redis核心的操作,即在命令处理上,它是单线程的。并且,是否单线程还取决于他的版本,在4.0时,引入多线程处理一些耗时较长的任务,例如异步删除命令等,在6.0时,引入多线程在处理核心网络模型问题上,进一步提高对于多核CPU的利用率。

Redis为什么这么快

1. 首先,Redis是基于内存服务的,那么比起MySQL操作硬盘来说,他就一定快了许多。

2. 其次,Redis的性能瓶颈并不是在处理命令上而是网络延迟,因此对于核心命令的处理引入多线程并不会对处理命令有多大的性能提升。相反,引入多线程操作命令之后,还要考虑线程安全问题,这势必就要引入另一个内容,也就是锁来解决线程安全问题,这会使得实现复杂度增高,性能也会大打折扣。而且多线程还会引起线程上下文的切换,带来不必要的开销。

3. 然后,Reids本身操作的都是一些简单的数据结构,所以就比较快。

3. Redis引入了IO多路复用机制,并且在处理核心网络模型上,引入了多线程。

Redis的执行流程

Redis的网络模型是基于IO多路复用机制的,因此描述大致也是基于此。

1. 启动服务之后,首先要初始化服务

b14cccc5ccfa409c98040a8b36fcf8d8.png

dc0e54506f56b415bc884650a33fb4a2.png

        a. 第一步就是调用epoll_create,在内核空间生成一个红黑树和一个链表。红黑树用来监听FD,链表则用来放就绪的FD。

        b. 第二步就是Redis服务生成一个ServerSocket,并且调用一个连接应答处理器(用于处理ServerSocket的读事件,也就是用来注册客户端FD的)。

 

ee05498f66df551c4dab1df0bb73624b.png

                1) 在连接应答处理器中,会调用epoll_ctl,将服务的FD监听起来,并且给它搞一个命令请求处理器(用于处理客户端的读事件,也就是把客户端发送的命令进行一系列的操作,最后处理该命令,处理之后再放到一个队列中,等待被写出)。

ee05498f66df551c4dab1df0bb73624b.png

         c. 第三步就是注册一个前置处理器,用处在第二大步骤。

 

2. 开始监听事件循环

 

631d84fd0402414aaeab5acd9f644528.png

5e7c459e87470f8365557103c609c140.png

        a. 第一步就是调用前置处理器(前置处理器中,会调用这个队列,即要被写出的事件,如果有内容的话,那么就交给命令回复处理器,命令回复处理器的作用就是将事件写出)。

 

9251958999d8aa39c1a7d71287a692ee.png

至此,大致流程基本结束。

通过大量的实际应用发现,对于命令读处理器和命令回复处理器,其实是把未操作的命令读入和写出,本质上跟处理命令没啥关系,所以6.0的时候,在这两步中加入了多线程,不过对于核心命令的执行来说,依旧是单线程。

 

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

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

相关文章

QT开发--QT基础

第0章 QT工具介绍 0.1 编译工具 uic,rcc,moc,qmake 都是 qt 的工具 uic 主要是 编译 .ui文件 -> ui_xxx.h //.ui文件 .h rcc 主要是 编译 资源文件.qrc文件 -> xxx.rcc …

SpringBoot3.3 优雅启停定时任务

定时任务是非常常见的功能,在一个复杂的应用程序中,如何优雅地管理这些定时任务的启动与停止尤为重要。 Spring Boot 提供了强大的任务调度支持,通过@Scheduled注解可以轻松地创建定时任务,并且可以通过配置来灵活地管理这些任务的执行环境。在本文中,我们将深入探讨如何…

如何设置 GitLab 密码过期时间?

GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 60天专业…

使用Pytorch+Numpy+Matplotlib实现手写字体分类和图像显示

文章目录 1.引用2.内置图片数据集加载3.处理为batch类型4.设置运行设备5.查看数据6.绘图查看数据图片(1)不显示图片标签(2)打印图片标签(3)图片显示标签 7.定义卷积函数8.卷积实例化、损失函数、优化器9.训练和测试损失、正确率(1)训练(2)测试(3)循环(4)损失和正确率曲线(5)输出…

高效使用AI,一文掌握提示词的编写原则

ChatGPT问世以后就引爆全网热议,它除了能够聊天,还可以根据所提出的要求进行文字翻译、文案撰写、代码撰写等工作。在 《探秘爆火的ChatGPT:大语言模型是个啥?它到底咋工作的?》 一文中,我已经详细介绍了Ch…

仿IOS桌面悬浮球(支持拖拽、自动吸附、自动改变透明度与点击、兼容PC端与移动端)

使用 pointerdown/pointermove/pointerup 实现仿IOS桌面悬浮球效果,支持拖拽、指定拖拽选对容器,指定拖拽安全区、自动吸附、自动改变透明度与点击,兼容PC端与移动端。 效果展示 https://code.juejin.cn/pen/7423757568268304421 代码实现 …

掌握RocketMQ4.X消息中间件(一)-RocketMQ基本概念与系统架构

1 MQ介绍 MQ(Message Quene) : 翻译为 消息队列,别名为 消息中间件,通过典型的 生产者和消费者模型,生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收&#xff0c…

<Project-8.1 pdf2tx-MM> Python Flask 用浏览器翻译PDF内容 2个翻译引擎 繁简中文结果 从P8更改

更新 Project Name:pdf2tx (P6) Date: 5oct.24 Function: 在浏览器中翻译PDF文件 Code:https://blog.csdn.net/davenian/article/details/142723144 升级 Project Name: pdf2tx-mm (P8) 7oct.24 加入多线程,分页OCR识别,提高性能与速度 使…

Qt+VS2019+大恒相机相机回调方式总结

一、前言 大恒驱动安装完成后,在安装目录有SDK调用文档,里面有更详细的调用介绍,此文档对近期做的Demo做一个回顾性总结。 二、调用流程概述 三、针对性内容介绍: 1. 在执行相机操作之前,需要先执行此代码&#xff1…

强大的PDF到Word转换工具

Solid Converter:强大的PDF到Word转换工具推荐 在日常工作和学习中,PDF是最常用的文件格式之一。然而,编辑PDF文档并不总是那么方便,尤其是当你需要将PDF文件转换为Word文档时。Solid Converter 是一款强大的工具,专为…

前端面试:项目细节重难点问题分享(18)

更多详情:爱米的前端小笔记(csdn~xitujuejin~zhiHu~Baidu~小红shu)同步更新,等你来看!都是利用下班时间整理的,整理不易,大家多多👍💛➕🤔哦!你们…

Java基础(下)

泛型 Java 泛型&#xff08;Generics&#xff09; 是 JDK 5 中引入的一个新特性。使用泛型参数&#xff0c;可以增强代码的可读性以及稳定性。 编译器可以对泛型参数进行检测&#xff0c;并且通过泛型参数可以指定传入的对象类型 ArrayList<Person> persons new Arra…

实战逆向RUST语言程序

实战为主&#xff0c;近日2024年羊城杯出了一道Rust编写的题目&#xff0c;这里将会以此题目为例&#xff0c;演示Rust逆向该如何去做。 题目名称&#xff1a;sedRust_happyVm 题目内容&#xff1a;unhappy rust, happy vm 关于Rust逆向&#xff0c;其实就是看汇编&#xff…

【WebGPU Unleashed】1.4 对不同的顶点设置不同颜色

一部2024新的WebGPU教程&#xff0c;作者Shi Yan。内容很好&#xff0c;翻译过来与大家共享&#xff0c;内容上会有改动&#xff0c;加上自己的理解。更多精彩内容尽在 dt.sim3d.cn &#xff0c;关注公众号【sky的数孪技术】&#xff0c;技术交流、源码下载请添加微信号&#x…

新电脑 Windows 系统初始配置

文章目录 前言1 前置配置2 安装软件2.1 通讯工具2.2 后端开发工具2.3 硬件开发工具2.4 前端开发工具2.4 其它工具 3 Windows 11 优化4 写在最后 前言 分区&#xff08;个人习惯&#xff09;&#xff1a;1TB SSD 分为 2 个分区&#xff0c;一个 256GB 分区为系统盘&#xff0c;剩…

计算机毕业设计 基于Python的豆果美食推荐系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

嵌入式数据结构中线性表的具体实现

大家好,今天主要给大家分享一下,如何使用数据结构中的线性表以及具体的实现。 第一:线性表的定义和表示方法 线性表的定义 – 线性表就是零个或多个相同数据元素的有限序列。 • 线性表的表示方法 – 线性表记为: L=(a0,∙∙∙∙∙∙∙∙ai-1aiai+1 ∙∙∙∙∙∙an-1) •…

软考《信息系统运行管理员》- 4.3 信息系统软件运维的过程

4.3 信息系统软件运维的过程 文章目录 4.3 信息系统软件运维的过程日常运维日常运维的内容日常运行例行测试维护例行测试流程的关键点例行维护流程的关键点 定期测试维护 缺陷诊断与修复信息系统软件缺陷的概念信息系统软件缺陷的分类信息系统软件缺陷诊断与修复流程缺陷诊断与…

Python编程:创意爱心表白代码集

在寻找一种特别的方式来表达你的爱意吗&#xff1f;使用Python编程&#xff0c;你可以创造出独一无二的爱心图案&#xff0c;为你的表白增添一份特别的浪漫。这里为你精选了六种不同风格的爱心表白代码&#xff0c;让你的创意和情感通过代码展现出来。 话不多说&#xff0c;咱…

pyQT生成界面,更改后不清除自定义代码的方法

基本原理就是作个子类继承生成的界面&#xff0c;在子类里写代码 工程结构 dialog_ui.py 界面子类 from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtGui import QStandardItemModel, QStandardItem, QColor, QFont from PyQt5.QtCore import Qt import Ui_dialog i…