【Redis】网络模型(day10)

news2024/12/23 1:49:32

在本篇文章中,主要是对五种网络模型进行一个简单的介绍,然后对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.coloradmin.cn/o/2212892.html

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

相关文章

垃圾回收器和垃圾回收机制(简单介绍,用于回忆总结)

文章目录 垃圾回收机制1. 分代收集2. 标记复制3. 标记清除4. 标记压缩(整理) 垃圾回收器1. Serial / Serial Old2. Parallel Scavenge3. ParNew收集器4. CMS收集器5. G1收集器 参考链接 垃圾回收机制 1. 分代收集 分代收集(Generational Co…

吉时利KEITHLEY 2657A源表keithley2651A数字源表

Keithley 2657A 源表是一款高电压、高功率、低电流源测量单元 (SMU) 仪器,可提供前所未有的功率、精度、速度、灵活性和易用性,以提高研发、生产测试和可靠性环境中的生产力。 Keithley 2657A SourceMeter 仪器专门设计用于表征和测试高压电子器件和功率…

【隐私计算篇】一种批量匿踪查询友好算法PIRANA的原理分析

1. 背景分析 前段时间开展了批量匿踪查询算法迭代优化的工作,取得了一些进展。不得不说,甲方爸爸永远会提出非常有挑战性的目标,push你去想各种解决方案。在实际的算法研发落地上,我们会结合算法本身的机制改进以及工程优化这两方…

力扣hot100--二叉树

目录 二叉树 1. 94. 二叉树的中序遍历 2. 98. 验证二叉搜索树 3. 101. 对称二叉树 二叉树 1. 94. 二叉树的中序遍历 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 示例 1: 输入:root [1,null,2,3] 输出:[1,3,2]示…

[ComfyUI]最好用的图像提示词反推工具发布 2.0 版本啦!更好用了!

图像提示词反推工具我也介绍了好一些了,但是架不住技术一直在迭代啊!过一段时间就出一个新的,或者是升级版,所以我们的分享也不能停! 前段时间 joy_caption 蛮火的,不过后来也陆陆续续出了一些比较好用的反…

JMeter性能测试时,如何做CSV参数化

在现代软件开发中,性能测试是保证应用程序在高负载条件下稳定运行的重要环节。为了实现真实场景的测试,参数化技术应运而生。其中,CSV参数化是一种高效且灵活的方法,可以让测试人员通过外部数据文件驱动测试脚本,从而模…

U-Boot阶段系统全量更新固件包制作杂记

背景:有一个在 U-Boot 阶段做系统全量自更新的需求,要制作系统的全量固件包(U-Boot.img、kerlen.img、rootfs.img)。大体分为三个主要部分:U-Boot-shell 脚本编写、打包各镜像为一个固件包、固件包的加密和解密 一、U…

电采暖集控系统陕西高陵体育馆应用项目案例

电采暖集控系统是一种集监测、控制和管理于一体的智能管理系统,旨在提高采暖效率、降低能耗和运营成本,同时提升用户的舒适度。该系统利用先进的计算机控制技术和系统集成技术,实现对电热采暖设备的集中管理和远程操控。 陕西高陵体育馆 是…

四川方维嘉术科技有限公司简介

四川方维嘉术科技有限公司 公司简介 四川方维嘉术科技有限公司成立于2023年,注册资本100万元整,位于中国西南地区的中心位置,是一家专注于供应医疗设备、高值耗材并提供医疗方面解决方案的企业。 【主要代理产品】 湖南瑞康通 &#xff1…

Alberta Wells数据集:首个包含超过213,000个油气井的大规模高质量基准数据集,它们是温室气体和其他污染物的重要来源,助力环境监测与气候变化。

2024-10-11,由Mila – Quebec AI Institute和McGill University等机构创建了首个大规模油井检测数据集,这个数据集的意义在于提供了一个工具,能够通过卫星图像识别和定位全球数以百万计的废弃油气井,这对于减少温室气体排放和保护…

数据结构与算法:堆与优先队列的深入剖析

数据结构与算法:堆与优先队列的深入剖析 堆是一种特殊的树形数据结构,广泛应用于优先队列的实现以及各种高效的算法中,如排序和图算法。通过深入了解堆的结构、不同堆的实现方式,以及堆在实际系统中的应用,我们可以掌…

使用js和canvas实现简单的网页打砖块小游戏

玩法介绍 点击开始游戏后,使用键盘上的←→控制移动,小球会不停移动,板子触碰小球时会反弹,碰撞到砖块时会摧毁砖块,如果没有用板子接住小球就游戏失败 代码实现 代码比较简单,直接阅读注释即可&#x…

工作日志:elementplus上传图片问题

问题&#xff1a;打开弹窗&#xff0c;上传一张照片后&#xff0c;关闭再打开&#xff0c;之前上传的图片仍在列表里展示。 然后添加了几行代码&#xff0c;报错。 <el-upload list-type"picture-card":limit"1":on-success"handleAvatarSuccess&…

Spring Boot 之三大配置文件.properties、.yml、.yaml 及其优先级解析

Spring Boot 的强大之处在于其高度可配置性&#xff0c;允许开发者根据不同环境和需求定制应用程序的行为。而这一切的核心便是配置文件。Spring Boot 支持多种配置文件格式&#xff0c;其中最常用的三种是 .properties、.yml 和 .yaml。 1. .properties&#xff1a;传统方式 …

基于NXP LS1023+FPGA的嵌入式解决方案

基于 NXP公司的LS1043A高性能64位ARM四核处理器。 LS1043A处理器是NXP公司面向嵌入式网络推出的一款四核64位ARM处理器&#xff0c; 支持无风扇设计的灵活I/O封装&#xff0c; 提供超过10 Gbps的性能&#xff0c;是专为小规格网络和工业应用而设计的解决方案。全新23x23封装方式…

jquery实现点击菜单实现高德地图定位点与数据展示联动效果

&#x1f34a;jquery实现点击菜单实现高德地图定位点与数据展示联动效果 版本介绍&#xff1a; jQuery v3.7.1高德地图JS API 2.0 代码仓库 ⭐ Gitee&#xff1a;实现点击菜单实现高德地图定位点与数据展示联动效果 1.启动说明 &#x1f4d4; 推荐VS Code编辑器插件Live Ser…

java项目之信息化在线教学平台的设计与实现(源码+文档)

项目简介 信息化在线教学平台的设计与实现实现了以下功能&#xff1a; 信息化在线教学平台的设计与实现的主要使用者管理员功能有个人中心&#xff0c;学生信息管理&#xff0c;教师信息管理&#xff0c;教学信息管理&#xff0c;学生成绩管理&#xff0c;留言板管理&#xf…

29.数据结构与算法-查找-查找的基本概念

查找的基本概念 查找表 主关键字与次关键字 查找是否成功 查找的目的&#xff08;查询&#xff0c;检索&#xff0c;插入&#xff0c;删除&#xff09; 查找表的分类&#xff08;静态查找表&#xff0c;动态查找表&#xff09; 如何评价查找算法&#xff08;平均查找长度ASL&a…

前端编程艺术(5)---Vue3(从零基础到项目开发)

目录 1.Vue.js 2.快速上手 2.数据响应式 1.reactive函数 2.ref函数 3.Vue工程化 1.安装node.js 2.脚手架创建项目 3.项目setup 4.vue指令 1.内容渲染指令 2.属性绑定指令 3.事件绑定指令 4.条件渲染指令 5.列表渲染指令 6.双向绑定指令 7.指令修饰符 8.样式绑…

[JAVAEE] 创建线程的方法 + Thread类中的常用方法 + 线程状态

目录 一. 创建线程的方法 1.1 继承 Thread 类. 1.2 实现 Runnable 接口. 1.3 lambda表达式创建线程 二. Thread类中的常用方法 2.1 start方法 2.2 run方法 2.3 sleep静态方法 2.4 isDaemoon() and setDaemon() 2.5 isAlive() 2.6 Thread.currentThread() 2.7 inter…