Redis IO 多路复用底层的实现原理

2023-10-27

前言

  • 了解Redis底层关于IO多路复用的epoll实现原理前,先介绍关于IO模型,内存与磁盘交互方式、同步IO、异步IO,有助于对多路复用更好的理解。

用户空间与内核空间

  • User space 是用户程序的运行空间,Kernel space 是Linux内核运行的空间。
  • 虚拟内存被 操作系统划分为两块: 内核空间用户空间,内核空间是内核代码运行的地方,用户空间是用户程序代码运行的地方。当进程运行在内核空间时就处于内核态,当进程运行在用户空间时就处于用户态。
    在这里插入图片描述
  • Kernel space可以执行任意命令,调用系统的一切资源User space只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发起指令。
  • 通过系统接口,进程可以从用户空间切换到内核空间
	str= "my zhangsan" //用户空间
	x+=2;
	file.write(str); //切换到内核空间
	y=x+3;//切换会用户空间
  • 上面代码中,简单的赋值运算,在User space执行。第三行需要写入文件,就要切换到Kernel spacce,因为用户不能直接写文件,必须通过内核空间 。
  • 查看CPU时间在 User space 与Kernel Space之间的分配情况,可以使用top命令,他的第三行输出就是CPU时间分配统计。
    在这里插入图片描述
  • 这一行8项统计指标
    在这里插入图片描述
    • 第一项24.8 us (user 的缩写)就是CPU消耗在 User space 的时间百分比,第二项 0.5 sy (system的缩写)是消耗在 Kernel space的时间百分比。

PIO 和 DMA

PIO
  • 有必要简单说说慢速 I/O设备内存之间的数据传输方式
  • PIO 用磁盘举例,很早之前,磁盘和内存之间的数据传输是需要CPU控制的,意味着读取磁盘文件到内存中,数据要经过CPU存储转发,这种方式成为PIO 。显然,这种方式不合理,需要占用大量的时间读取文件,造成文件访问系统几乎停滞响应。
DMA
  • DMA, DMA (直接内存访问,Direct Memory Access)取代了PIO,他可以不经过CPU而直接进行磁盘和内存(内核空间)的数据交换 。在DMA模式下,CPU只需要向DMA控制器下达指令,让DMA控制器来处理数据的传送即可,DMA控制器通过系统总线来传输数据,传送完毕通知CPU,这样可以很大程度上降低CPU占有率,大大节省了系统资源

缓存I/O和直接I/O

  • 缓存IO : 数据从磁盘先通过 DMA copy到内核空间,在从内核空间通过cpu copy到用户空间。
    • 缓存I/O 被称为标准I/O,大多数文件系统的默认I/O 操作都是缓存I/O,在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区然后从内核空间缓冲区复制到应用程序的地址空间
缓存I/O的读写操作
  • 读操作
    • 操作系统检查内核的缓冲区有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回; 否则从磁盘读取,然后缓存在操作系统的缓存中。
  • 写操作
    • 将数据从用户空间复制到内核空间的缓存中。这时对用户程序来说写操作就已经完成,至于什么时候在写到磁盘中有操作系统决定,除非显示地调用了sync同步命令。
缓存I/O的优点
  • 在一定程度上分离了内核空间和用户空间,保护系统本身的运行安全
  • 可以减少读磁盘的次数,从而提高性能
缓存I/O的缺点
  • 在缓存I/O 机制中,DMA方式可以将数据直接从磁盘读到页缓存中,或者将数据从页缓存直接写回到磁盘上,而不能直接在应用程序地址空间和磁盘之间进行数据传输,这样,数据在传输过程中需要在应用程序地址空间(用户空间)和缓存(内核空间)之间进行多次数据拷贝操作,这些数据拷贝操作带来的CPU以及内存开销是非常大的。
  • 直接IO : 数据从磁盘通过DMA copy到用户空间
    • 直接I/O应用程序直接访问磁盘数据,而不经过内核缓冲区,也就是绕过内核缓冲区,自己管理I/O缓冲区,这样的好处减少一次从内核缓冲区到用户程序的数据拷贝。
直接I/O的优点
  • 对于一些复杂的应用,比如数据库服务器,他们为了充分提高性能,希望绕过内核缓冲区,由自己在用户态空间实现并管理I/O缓冲区,包括缓存机制和写延迟机制,以支持独特的查询机制。绕过内核缓冲区可以减少系统内存的开销,因为内核缓冲区本身就在使用系统内存
    在这里插入图片描述

IO的访问方式

磁盘IO

在这里插入图片描述

  • 应用程序调用read接口时,操作系统检查在内核的高速缓存有没有需要的数据,如果已经缓存了,那么就从缓冲返回,如果没有,则从磁盘中读取,然后缓存在操作系统的缓存中
  • 应用程序调用write接口时,将数据从用户地址空间复制到内核地址空间的缓存中,这时对用户程序来说,写操作已经完成,至于什么时候写到磁盘中,有操作系统决定,除非显示调用了sync同步命令。
    在这里插入图片描述

网络IO

  • 操作系统将数据从磁盘复制到操作系统内核的页缓存中
  • 应用将数据从内核缓存复制到应用的缓存中
  • 应用将数据写回内核的Socket缓存中
  • 操作系统将数据从Socket缓存区复制到网卡缓存,然后将其通过网络发出。
    在这里插入图片描述
  • 1、当发生read系统调用时,通过DMA(Direct Memory Access)将数据copy到内核模式
  • 2、然后由CPU控制将内核模式数据copy到用户模式下 的buffer
  • 3、read调用完成后,write调用首先将用户模式下buffer中的数据copy到内核模式下的socket buffer中
  • 4、最后通过DMA copy将内核模式下的socket buffer中的数据copy到网卡设备中传送。
  • 总结: 从上面的过程可以看出,数据白白从内核模式到用户模式走了一圈,浪费了两次copy,而这两次copy都是cpu copy,即占用CPU资源
磁盘IO和网络IO对比
  • 磁盘IO延时是由(以15000rpm的硬盘为例),机械转动延时(机械磁盘的主要性能瓶颈,平均为2ms) + 寻址延时(2~3ms)+ 块传输延时(一般4k每块,40m/s的传输速度,延时一般为0.1ms) 决定。
  • 网络IO延时: 服务器响应延时、带宽限制、网络延时、跳转路由延迟、本地接收延时决定。
  • 所以两者一般来说网络IO延迟要大于磁盘IO的延时

同步IO和异步IO

同步IO
  • 同步和异步是针对应用程序和内核的交互而言的,
  • 同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪
异步IO
  • 异步指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知
  • 内核空间会异步通知用户进程,并把数据直接给到用户空间。

阻塞IO和非阻塞IO

阻塞IO
  • 阻塞方式: 读取或者写入函数将一直等待,服务器的响应
    • 阻塞:用户空间通过系统调用(systemcall) 和内核空间发送IO操作时,该调用时阻塞的。
非阻塞IO
  • 非阻塞方式: 读取或者写入喊出会立即返回一个状态值
    • 非阻塞:用户空间通过系统调用(systemcall)和内核空间发送IO操作时,该调用时不阻塞的,直接返回的。知识返回时,可能没有数据而已。、

IO设计模式之Reactor pattern

  • 反应器设计模式(Reactor pattern)是一种处理并发服务请求,并将请求提交到一个或多个服务器程序的事件设计模式。当客户端请求抵达后,服务器处理程序使用多路分配策略,由一个非阻塞的线程来接收所有的请求,然后派发这些请求至相关的工作线程进行处理。
Reactor模式角色分配
  • 初始化事件分配器(Initialization Dispatcher) : 用于管理Event Handler,定义注册,移除EventHandler等。它还作为Reactor模式的入口调用Synchronous Event DemultiPlexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event() 方法。
  • 同步(多路)事件分离器(Synchronous Event Demultilexer): 无限循环等待新事件的到来,一旦发现有新的事件到来,就会通知初始化事件分发器去调取特定的事件处理器。他是由操作系统内核实现的一个函数,用于阻塞等待发生在句柄集合上的一个或多个事件。
  • 系统处理程序 (Handler) : 操作系统中的句柄,是对资源在操作系统层面的一种抽象,他可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handler,即一个网络连接
  • 事件处理器(Event Handler) : 定义事件处理方法,以供Initialization Dispatcher回调使用。
  • Handle 句柄: 用来标识socket连接或是打开文件;
  • Reactor反应器: 定义一个接口,实现以下功能: 1、供应程序注册和删除关注的事件句柄; 2、运行事件循环;3、当有就绪事件来时,分发事件到之前注册的回调函数上处理。
  • Concreate Event HandlerA : 实现应用程序所提供的特定事件处理逻辑。
为什么使用Reactor模式
  • 并发系统常使用Reactor模式代替常用的多线程的处理方式,节省系统的资源,提高系统的吞吐量。
  • 其实,在高并发的情况下,既可以使用多线程的处理方式,也可以使用Reactor处理方式。
    • 多线程的处理:为每个单独到来的请求,专门启动一条线程,这样的话造成系统的开销很大,并且在单核的机上,多线程并不能提高系统的性能,除非在有一些阻塞的情况发生,否则线程切换的开销会使得处理的速度变慢。
  • Reacotor模式的处理
    • 服务器启动一条单线程,用于轮询IO操作是否就绪,当有就绪的才进行相应的读写操作,这样的话就减少了服务器产生大量的线程,也不会出现线程之间的切换产生的性能消耗。(目前JAVA的NIO就采用的此种模式,这里引申出一个问题,在多核情况下NIO的扩展问题)
  • 以上两种处理方式都是基于同步的,多线程的处理是我们传统模式下对高并发的处理方式,Reactor模式的处理是现今面对高并发和高性能一种主流的处理方式。
Reactor业务流程时序图

在这里插入图片描述

高性能IO模型

  • 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:
    • 同步阻塞IO (Blocking IO): 即传统的IO模型
    • 同步非阻塞IO( Non-blocking IO): 默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。 注意这里所说的NIO并非Java的NIO库。
    • IO多路复用(IO MultiPlexing): 即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Lnuix中的epoll都是这种模型。
    • 异步IO(Asynchronous IO):即经典的Proactor设计模式,也被称为异步非阻塞IO
IO模型的举例理解
  • 阻塞IO, 给女神发一条短信, 说我来找你了, 然后就默默的一直等着女神下楼, 这个期间除了等待你不
    会做其他事情, 属于备胎做法.
  • 非阻塞IO, 给女神发短信, 如果不回, 接着再发, 一直发到女神下楼, 这个期间你除了发短信等待不会
    做其他事情, 属于专一做法.
  • IO多路复用, 是找一个宿管大妈来帮你监视下楼的女生, 这个期间你可以些其他的事情. 例如可以顺便
    看看其他妹子,玩玩王者荣耀, 上个厕所等等. IO复用又包括 select, poll, epoll 模式. 那么它们
    的区别是什么? 3.1 select大妈 每一个女生下楼, select大妈都不知道这个是不是你的女神, 她需要
    一个一个询问, 并且select大妈能力还有限, 最多一次帮你监视1024个妹子 3.2 poll大妈不限制盯着
    女生的数量, 只要是经过宿舍楼门口的女生, 都会帮你去问是不是你女神 3.3 epoll大妈不限制盯着女
    生的数量, 并且也不需要一个一个去问. 那么如何做呢? epoll大妈会为每个进宿舍楼的女生脸上贴上一
    个大字条,上面写上女生自己的名字, 只要女生下楼了, epoll大妈就知道这个是不是你女神了, 然后大
    妈再通知你.
  • 上面这些同步IO有一个共同点就是, 当女神走出宿舍门口的时候, 你已经站在宿舍门口等着女神的, 此时你属
    于同步等待状态
  • 接下来是异步IO的情况 你告诉女神我来了, 然后你就去王者荣耀了, 一直到女神下楼了, 发现找不见你了,
    女神再给你打电话通知你, 说我下楼了, 你在哪呢? 这时候你才来到宿舍门口. 此时属于逆袭做法
同步阻塞IO
  • 同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时被阻塞。
    在这里插入图片描述
  • 如上图所示,用户线程通过系统调用read发起IO读操作,有用户空间转到内核空间,内核等到数据包到达后,然后将接收到的数据拷贝到用户空间,完成read操作。
  • 即用户空间需要等待read将socket中的数据读取到buffer后,才继续处理接收到的数据。整个IO请求过程中,用户线程是被阻塞的,这导致后台在发起IO请求时,不能做任何事情,对CPU的资源利用率不高。
同步非阻塞IO
  • 同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK,这样做用户线程可以发起IO请求后立即返回。
    在这里插入图片描述
  • 如上图所示,由于socket是非阻塞的方式,因此用户发起IO请求时立即返回。 但并读取到任何数据,用户线程需要不断飞起IO请求,直到数据到达后,才真正读取到数据,继续执行。
  • 即用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后,立即返回返回,但是为了等到数据,仍需要不断的轮询、重复请求,消耗大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
IO多路复用
  • IO多路复用模型是建立在内核提供的多路分离函数的select基础之上的,使用select函数可以避免同步非阻塞IO模型中的轮询等待的问题。
    在这里插入图片描述
  • 如上图所示,用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。
  • 从流程上看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率很差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个IO请求,用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的,而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
  • 然而,使用select函数的优点并不仅限于此,虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据来到时再进行处理,则可以提高cpu的利用率。
    在这里插入图片描述
  • 如上图所示,通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环处理。用户线程注册事件处理器之后可以继续执行做其他的工作,而Reactor线程负责调用内核的select喊出检查socket状态,当有socket被激活时,则通知相应的用户线程,执行handle_event进行数据读取、处理工作。由于select函数式阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意这里所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达,用户线程一定不会被阻塞。
  • IO多路复用是最常使用的IO模型,但是其异步程度还不够彻底,因为他使用了会阻塞线程的select系统调用。因此,IO多路复用只能称为异步阻塞IO,而非真正的异步IO。
异步IO
  • "真正"的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知用户线程,有用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。
  • 异步IO模型使用了Proactor设计模式实现了这一机制。
    在这里插入图片描述

Redis IO多路复用技术以及epoll实现原理

  • redis 是一个单线程却性能非常好的内存数据库,主要用来作为缓存系统。redis 采用网络IO多路复用技术来保证在多连接的时候,系统的高吞吐量。
为什么Redis使用I/O 多路复用呢?
  • 首先,Redis 是跑在单线程中,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以IO操作一般情况下往往不能直接返回,这会导致某一文件的I/O 阻塞导致整个进程无法对其他客户提供服务,而I/O多路复用是为了解决这个问题而出现的。
  • selectpollepoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行响应的操作。
  • redis 的iO模型默认采用epoll实现的。
epoll实现机制
  • 假设有如下场景:
    • 有100万个客户端同时与同一个服务器进程保持着TCP连接,而每一时刻,通常只有几百上千个TCP连接时活跃的(事实上大部分场景都是这种情况),如何实现这样的高并发 ?
  • 在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千个并发连接。
  • 如果没有I/O 事件产生,我们的程序就会阻塞在select 处,但是依然有个问题,我们从select 哪里仅仅知道了,有I/O事件发生了,但却并不知道是哪几个流(可能有一个,多个,设置全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。
select 和 poll 的缺点
    1. 每次调用select/poll,都需要把句柄从用户态拷贝到内核态,这个开销在句柄很多时会很大
    1. 同时每次调用select/poll都需要在内核遍历传递进来的所有句柄,这个开销在句柄很多时也很大
    1. 针对select支持的文件描述符数量太小了,默认是1024
    1. select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;
    1. select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。
  • 相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。
什么是epoll
  • epoll的设计与实现与select 完全不同。 epoll是poll的一种优化,返回后不需要对所有的句柄进行遍历,在内核中维持了连接的列表。select 和 poll是将这个内核列表维持在用户态,然后传递给内核中。 与poll/select不同,epoll不再是单独的系统调用,而是有epoll_create/epoll_ctl/epoll_wait三个系统调用组成,后面将会看到这样做的好处。
  • epoll通过在Linux内核中申请了一个简易的文件系统,把原先的select/epoll调用分成了3个部分:
  • 1、调用epoll_create() 建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
  • 2、调用epoll_ctl向epoll对象中添加这100万个连接的套接字。
  • 3、调用epoll_wait收集发生事件的连接
  • 如此一来,要实现上面说的场景,只需要在进程启动时,建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。
epoll的优点
    1. epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这
      个数目和系统内存关系很大
      ,具体数目可以 cat /proc/sys/fs/file-max 察看。
    1. 效率提升, epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境
      中, epoll 的效率就会远远高于 select 和 poll 。
    1. 内存拷贝, epoll 在这点上使用了“共享内存”,这个内存拷贝也省略了。
redis epoll底层实现
  • 当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体有两个成员与epoll的使用方式密切相关。
  • eventpoll的结构体如下所示:
 struct eventpoll{
    //红黑树的根节点,这棵树中存储着所有添加到epoll中需要监控的事件
	struct rb_root rbr;
	//双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件
}
  • 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来事件。这些事件都会挂载到红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来。
  • 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的时间发生时会调用这个回调方法,这个回调方法在内核中叫ep_poll_callback,他会将发生的时间添加到rdlist双链表中。
 struct epitem {
	struct rb_node rbn; //红黑树的节点
	struct list_head ralink; //双向链表的节点
	struct epoll_fieldfd ffd; //事件句柄的消息
	struct eventpoll *ep; //指向其所属的eventpoll对象
	struct epoll_event event; //期待发生的事件类型
}
  • 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表是否有epitem元素即可,如果rdlist不为空,则把发生的时间复制到用户态,同时将事件数量返回给用户。
优势在哪里
  • 1、不用重复传递,我们调用epoll_wait时就相当于以往调用select/poll。但是这是却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。
    -2、 在内核里,一切皆文件,所以,epoll向内核注册了一个文件系统,用于存储上述被监控的socket。当你调用epoll_create时,就会在虚拟的epoll文件系统中创建file结点,当然这个file不是普通文件,他只是服务于epoll。
  • epoll在被内核初始化时,同时会开辟出epoll自己的内核高速缓冲区,用户安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除,这个内核高速cache区,就是建立连续的物理内存也,然后在之上建立slab层,简单来说,就是物理上分配好你想要的size的内存对象,每次使用都是使用空闲的已分配好的对象。
  • 3、高效的原因: 这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储epoll_ctl传来的socket外,还会建立一个list链表,用于存储就绪的事情,当epoll_wait调用时,仅仅观察这个list链表有没有怇,有数据就返回,没有数据就sleep, 等到timeout时间到后,即使链表没有数据页返回,所以,epoll_wiat非常高效。
准备就绪的list链表是如何维护的?
  • 当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄中断到了,就把他放到准备就绪的list链表里。所以当一个socket有数据到了,内核就把网卡上的数据copy到内核中后就来把socket插入到准备就绪的链表里了(这就是epoll的基础,回调,好好理解一下这句话,非常重要)。
  • 总结: 执行epoll_crate时,创建了红黑树和就绪表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立即返回准备就绪链表中的数据即可。
  • 最后,感谢读者的阅读,文章如有错误,评论或私信我,会及时加以改正,大家共同进步
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis IO 多路复用底层的实现原理 的相关文章

随机推荐

  • 编写Spring MVC控制器的14个技巧

    通常 在Spring MVC中 我们编写一个控制器类来处理来自客户端的请求 然后 控制器调用业务类来处理与业务相关的任务 然后将客户端重定向到逻辑视图名称 该名称由Spring的调度程序Servlet解析 以呈现结果或输出 这样就完成了典型
  • Python: 向下取整规则解释带有负号的除取整(//)和除取余(%)

    1 Python 中的 结果向下取整 例如 print 7 3 2 3333333 向下取整为 2 print 7 3 2 333333 向下取整为 3 print 7 3 2 333333 向下取整为 3 print 3 7 0 42 向
  • linux sed命令详解

    简介 sed 是一种在线编辑器 它一次处理一行内容 处理时 把当前处理的行存储在临时缓冲区中 称为 模式空间 pattern space 接着用sed命令处理缓冲区中的内容 处理完成后 把缓冲区的内容送往屏幕 接着处理下一行 这样不断重复
  • wireshark的usb抓包分析 1 - 抓取数据

    wireshark的版本为Version 3 2 4 v3 2 4 0 g893b5a5e1e3e 操作系统为Win10 64bit 安装过程要注意的是 有一步需要选择安装USBpcap 不装这个没法抓USB包 安装完成后需要重启电脑 1
  • MCU-串口通信协议

    MCU 串口通信协议 一 介绍 串行通信就像单车道 所有数据得一个一个通行 并行就像多车道 一次可以通行多辆车 MCU常用到的串口通信模块主要有两种 UART和USART UART 全称是Universal Asynchronous Rec
  • JetBrains软件使用经验(转载)

    版权声明 本文为博主原创文章 遵循 CC 4 0 BY NC SA 版权协议 转载请附上原文出处链接和本声明 本文链接 https blog csdn net I love you dandan article details 997124
  • [python爬虫] Selenium切换窗口句柄及调用Chrome浏览器

    因为我的博客是根据我每个阶段自己做的事情而写的 所以前言可能有点跑题 但它更有利于让我回忆这个阶段自己的所作所为 这篇文章主要介绍Selenium爬虫获取当前窗口句柄 切换窗口句柄以及调用Chrome浏览器几个知识点 其中获取当前句柄的方法
  • 三元操作符

    通常N元操作符指的是该操作符有N个操作数 如赋值操作符 它是一个二元操作符 所以它有两个操作数 左右各一个 又如减号 是一个二元操作符 但是当它作为负号 使用的时候 便是一个一元操作符 它表示负数 所以只有一个操作数 那么 三元操作符理应有
  • python--unicodedata用法

    python unicodedata用法 需要掌握的 1 将Unicode字符 chr 转换为等效的数值 以浮点形式返回 print unicodedata numeric 四 4 0 2 将unicode字符 chr 转换为其等效的数字值
  • 【C语言初学必看】猜数字游戏背后的知识

    目录 前言 1 先看主体部分 大体逻辑的部分 a 为什么用do while循环 b switch语句 c 关于scanf 中的 d s 和 printf 的关系 d menu 和game 函数为什么没有返回值 e 为什么 intput 在w
  • ABAP 351 - 动态编程

    作为面对对象的编程语言 ABAP也是支持动态编程的 ABAP351作为一门独立的课程介绍了类反射机制如何实现的过程 一 Field Symbols Field Symbols 字段符号 在ABAP编程中经常使用 实际上它具备以下几点特性 字
  • citespace教程

    参考视频教程 CiteSpace保姆级教程1 文献综述怎么写 研究生写文神器 手把手教你做科学知识图谱 以中国知网为例 哔哩哔哩 bilibili Citespace 从下载到图谱分析 详细教程 基于CNKI和WOS 陈同学 哔哩哔哩 bi
  • “数”说程序员|“后浪”涌袭下的开发者现状

    红网时刻长沙10月23日讯 见习记者 赵翼鹏 10月23日至25日 长沙 中国1024程序员节 将盛大举行 为期3天的会议将以开源为主议题进行讨论 并包括了十多场技术论坛 多位操作系统领域大咖还将史上首次在岳麓山展开对话 为什么会是1024
  • BUCK和BOOST电路详解

    1 BUCK电路 BUCK是一种降压型电路 他的特征就是输出电压低于输入电压 输入的电流是脉动的 输出的电流是连续的 BUCK电路的原理图如下图所示 当开关管Q1驱动为高电平时 开关管导通 电感L1被充磁储能 流过电感的电流线性增加 同时给
  • 《无码的青春》第七章 御姐

    之前我们各有各的故事 各有各的守护 然而今天却成了同一类人 失去了各自的女神 失去了自己的信仰 没有了牵挂 也不再需要对什么人负责 四个单身男人 在这个浮华的城市里尽情的释放着过剩的荷尔蒙 周末我们会聚在道哥的酒吧里 在浮光掠影的酒精和静静
  • python3的turtle画模仿3d星空,运动的恒星小宇宙

    本文参考原文 http bjbsair com 2020 03 25 tech info 6248 html 1 宇宙 2 代码实现条件 python3 3 第1步 第1步 导入模块 from turtle import from rand
  • 8.Xaml Border控件

    1 运行图片 2 运行源码
  • 自定义滚动条@莫成尘

    先看代码 复制使用即可 以下代码均可复制粘贴使用 我将以注释的形式解释代码左右 您将看到以下效果 原生的滚动条比较方正 不够圆滑 很大程度上不能满足我们的审美 有时候需要修改其样式 滚动条的高度将随着内容的多少自适应
  • GOTC演讲回顾

    5月27 28日 由上海浦东软件园 开放原子开源基金会 Linux基金会亚太区和开源中国联合发起的2023全球开源技术峰会 Global Open source Technology Conference GOTC 在上海圆满召开 大会聚焦
  • Redis IO 多路复用底层的实现原理

    文章目录 前言 用户空间与内核空间 PIO 和 DMA PIO DMA 缓存I O和直接I O 缓存I O的读写操作 缓存I O的优点 缓存I O的缺点 直接I O的优点 IO的访问方式 磁盘IO 网络IO 磁盘IO和网络IO对比 同步IO