Redis网络模型

进程寻址空间分为两部分:内核空间、用户空间

  • 内核空间:可以执行特权命令(Ring1),调用一切系统资源
  • 用户空间:只能执行受限的命令(Ring3),不能直接调用系统资源,必须通过内核提供的接口来访问

一、五种IO模型

《Unix网络编程》中归纳了五种IO模型:
阻塞IO(Blocking IO)、非阻塞IO(NonBlocking IO)、IO多路服用(IO Multiplexing)、信号驱动IO(Signal Driven IO)、异步IO(Asynchonous IO)

读取数据过程:

下面是读取数据的基本过程:

  • 同步/异步的区别:关键在于第二阶段是同步还是异步

1、阻塞IO

等待数据阶段、读取数据阶段,都需要阻塞等待

2、非阻塞IO

recvfrom操作会立即返回结果,而不是阻塞线程

  • 性能上没有得到提升,且忙等待机制会导致CPU空转,CPU的使用率暴增

3、IO多路复用

利用单个线程来同时监听多个FD,并在某个FD可读/可写时得到通知,从而避免无效的等待,充分利用CPU资源

  • 文件描述符(File Descriptor):简称FD,是一个从0开始递增的无符号整数,用来关联linux中的一个文件

4、信号驱动IO

与内核建立Signal-driven I/O信号关联,并设置回调,当FD就绪,会发出Signal-driven I/O信号通知用户,期间用户可以执行其他业务,无需阻塞。

  • 存在问题:有大量IO操作时,信号较多。Signal-driven I/O信号处理函数不能及时处理可能导致信号队列溢出,而且内核空间与用户空间的频繁信号交互性能也较低

5、异步IO

整个过程都是非阻塞的,用户进程调用异步API后,就可以做其他事情,内核等待数据就绪并拷贝到用户空间后,才会递交信号,通知用户进程

二、SELECT、POLL、EPOLL

select、poll、epoll是IO多路复用中的三种常见实现
差异:selectpoll只会通知用户进程有FD就绪,但不确定具体是哪个FD就绪,需要用户进程逐个遍历FD来确认。epoll会通知用户进程FD就绪的同时,把已就绪的FD写入用户进程

1、SELECT

缺点:

  • 1、需要将整个fd_set从用户空间拷贝到内核空间,setlect结束还要再次拷贝回用户空间
  • 2、select无法得知具体是哪一个fd就绪,需要遍历整个fd_set
  • 3、fd_set监听的fd数量不能操过1024
数据结构
//定义类型别名 __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的集合
 * @param nfds 要监听的fd_set的最大fd + 1
 * @param readfds 要监听的读事件的fd集合
 * @param writefds 要监听的写事件的fd集合
 * @param exceptfds 要监听的异常时间的fd集合
 * @param timeout 超时时间,null-永不超时,0-不阻塞等待,大于0-固定等待时间
 **/
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
IO流程

2、POLL

相比与select,poll监听fd的大小理论上无上限。但是监听的fd越多,每次遍历消耗时间也越久,性能反而下降

数据结构
//pollfd 中的事件类型
#define POLLIN //可读事件
#define POLLOUT //可写事件
#define POLLERR //错误事件
#define POLLNVAL //fd未打开

//pollfd结构
struct pollfd {
    int fd; //要监听的fd
    short int events; // 要监听的事件类型:读、写、异常
    short int revents; //实际发生的事件类型
}

/**
 * poll函数
 *  @param fds // pollfd数组,可以自定义大小
 *  @param nfds // 数组元素个数
 *  @param timeout // 超时时间
 **/
int poll(struct pollfd *fds, 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

3、EPOLL

对select和poll进行了改进,提供三个函数(epoll_create、epoll_ctl、epoll_wait)

数据结构
struct eventpoll{
    //...
    struct rb_root rbr; // 一颗红黑树,记录要监听的FD
    struct list_head rdlist; // 一个链表, 记录就绪的FD
    //...
}

/**
 * 1、在内核创建eventpoll结构体,返回对应的句柄epfd
 * @param size
 **/
int epoll_create(int size);

/**
 * 2、将一个FD添加到epoll的红黑树中,并设置ep_poll_callback
 * callback触发时,就把对应的FD加入到relist这个就绪列表中
 * @param epfd epoll实例的句柄
 * @param op 要执行的操作,包括:ADD,MOD,DEL
 * @param fd 要监听的FD
 * @param event 要监听的事件类型: 读、写、异常等
 **/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

/**
 * 3、检查relist列表是否为空,不为空则返回就绪的FD的数量
 * @param epfd epoll实例的句柄
 * @param events 空event数组,用于接收就绪的FD
 * @param maxevents events数组的最大长度
 * @param timeout 超时时间:-1不超时,0 不阻塞,大于0为阻塞时间
 **/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
IO流程

相比于select和poll。 epoll减少了拷贝次数,减少了拷贝的数量,仅将就绪的FD返回,减少了遍历

事件通知机制

当FD有数据可读时,调用epoll_wait就可以得到通知。有如下两种模式:

  • LevelTriggered:简称LT,默认模式。会重复通知多次,直至数据处理完成
  • EdgeTriggered:简称ET。只会通知一次,不管数据是否处理完成。避免了LT模式可能出现的惊群现象,最好是结合非阻塞IO读取FD数据,相比LT会复杂一些
WEB服务流程

三、Redis中的网络模型

redis通过IO多路复用来提高网络性能,并支持各种不同的多路复用实现,并将这些实现进行封装,提供了统一的高性能事件库:AE库
Redis6.0版本中引入了多线程,目的是为了提高IO读写效率。因此在解析客户端命令写入响应结果时采用了多线程。核心的命令执行、IO多路复用模块依然是由主线程执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值