Redis-网络模型

2023-12-18 17:49:34

参考资料 :极客时间 Redis(亚风)

前置知识

系统隔离
为了避免?户应?导致冲突甚?内核崩溃,?户应?与内核是分离的:
进程的寻址空间会划分为两部分:内核空间、?户空间
? ?户空间只能执?受限的命令(Ring3),?且不能直接调?系统资源,必
须通过内核提供的接?来访问
? 内核空间可以执?特权命令(Ring0),调??切系统资源

在这里插入图片描述
缓冲区
Linux系统为了提?IO效率,会在?户空间和内核空间都加?缓冲区:
? 写数据时,要把?户缓冲数据拷?到内核缓冲区,然后写?设备
? 读数据时,要从设备读取数据到内核缓冲区,然后拷?到?户缓冲区
在这里插入图片描述
这里的数据交互采用不同的模型就会有不同的性能,因而也诞生了一些网络IO模型。

IO模型

阻塞IO

在这里插入图片描述

非阻塞IO

在这里插入图片描述

?论是阻塞IO还是?阻塞IO,?户应?在?阶段都需要调?recvfrom来获取数据,差别在于?数据时的理?案:
? 如果调?recvfrom时,恰好没有数据,阻塞IO会使进程阻塞,?阻塞IO使CPU空转,都不能充分发挥CPU的作?。
? 如果调?recvfrom时,恰好有数据,则?户进程可以直接进?第?阶段,读取并处理数据。

IO多路复用
文件表述符的概念

?件描述符(File Descriptor):简称FD,是?个从0开始递增的?符号整数,?来关联Linux中的?个?件。在Linux中,?切皆?件,例如常规?件、视频、硬件设备等,当然也包括?络套接字 (Socket)。
IO多路复?:是利?单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从?避免?效的等待,充分利?CPU资源。

区间听FD有三种实现方式

1 select
Sekect的结构

typedef struct {
	// ?度为 1024/32 = 32
	// 共1024个bit位,每个bit位代表?个fd,0代表未就绪,1代表就绪
	int 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集合
//超时时间,null-永不超时;0-不阻塞等待;?于0-固定等待时间
struct timeval *timeout);

在这里插入图片描述
select模式存在的问题:
? 需要将整个fd_set从?户空间拷?到内核空间,select结束还要再次拷?回?户空间
? select?法得知具体是哪个fd就绪,需要遍历整个fd_set
? fd_set监听的fd数量不能超过1024(因为32个int,每个int 4 * 8 32 位)总共1024位。
2 poll

#define POLLIN //可读事件
#define POLLOUT //可写事件
#define POLLERR //错误事件
// pollfd结构
struct pollfd {
    int fd; //要监听的fd
    short int events;/*要监听的事件类型:读、写、异常*/
    short int revents;/* 实际发?的事件类型*/
}// poll函数
int poll(
struct pollfd *fds,// pollfd数组,可以?定义??
nfds_t nfds, //数组元素个数
int timeout) // 超时时间

1 ) 创建pollfd数组,向其中添加关注的fd信息
2 )调?poll函数,将pollfd数组拷?到内核空间,转链表存储(内核要求转换为链表)
3 )内核遍历fd,判断是否就绪
4 )数据就绪或超时后,拷?pollfd数组到?户空间,返回就绪fd数量n
5 )判断n是否?于0,?于0则遍历pollfd数组,找到就绪的fd

与select对?:

整体过程和Select大同小异。结构优化了一些,但是还是需要遍历fd_set.

select模式中的fd_set??固定为1024,?pollfd?上限,监听FD越多,每次遍历消耗时间也越久,性能反?会下降。

3 epoll

struct eventpoll {

    struct rb_root rbr;//红?树,记录要监听的FD

    struct list_head rdlist;// 链表,记录就绪的FD

}//1.会在内核创建eventpoll结构体,返回对应的句柄epfd

int epoll_create(int size)//2.将?个FD添加到epoll的红?树中,并设置ep_poll_callback

// callback触发时,就把对应的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,

struct epoll_event *events, //event数组,?于接收就绪的FD 属于用户空间

int maxevents, // events数组的最??度

int timeout //超时时间,-1不超时;0不阻塞;?于0为阻塞时间);
    )

在这里插入图片描述
具体流程:
在这里插入图片描述

三种模式的对比:
select模式存在的三个问题:

? 能监听的FD最?不超过1024

? 每次select都需要把所有要监听的FD都拷?到内核

? 每次都要遍历所有FD来判断就绪状态

poll模式的问题:

? poll利?链表解决了select中监听FD上限的问题,但依然要遍历所有FD,如

果监听较多,性能会下降

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

? 基于epoll实例中的红?树保存要监听的FD,增删改查效率?常?

? 每个FD只需要执??次epollctl添加到红?树,?需重复拷?FD到内核空间

? 内核会将就绪的FD直接拷?到?户空间的指定位置,?户进程?需遍历所有FD就能知道就绪的FD是谁当FD有数据可读时,我们调?epollwait就可以得到通知。但是事件通知的模式

有两种:

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

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

ET模式避免了LT模式可能出现的惊群现象(就比如有多个进程监听了FD,这个FD一旦通知就会唤醒所有的进程,但是实际只需要一个进程来处理。)

ET模式最好结合?阻塞IO读取FD数据,相?LT会复杂。

信号驱动IO

信号驱动IO是与内核建?SIGIO的信号关联并设置回调,当內核有FD就绪时,会发出SIGIO信号通知?户,期间?户应?可以执?其它业务,?需阻塞等待。
在这里插入图片描述

存在的问题:当有?量lO操作时,信号较多,SIGlO处理函数不能及时处理可能导致信号队列溢出,?且内核空间与?户空间的频繁信号交互性能也较低。

AIO(异步IO)

AIO的整个过程都是?阻塞的,?户进程调?完异步API后就可以去做其它事情,内核等待数据就绪并拷?到?户空间后才会递交信号,通知?户进程。
在这里插入图片描述
IO操作是同步还是异步,关键看数据在内核空间与?户空间的拷?过程(数据读写的IO操作)
在这里插入图片描述

总结在Redis里面采用了IO多路复用,具体是epoll这种模式来实现。后面的信号IO 和 AIO虽然比较理想,但是目前使用起来还有一些问题。
在这里插入图片描述

文章来源:https://blog.csdn.net/qq_43259860/article/details/135049092
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。