BIO(Blocking IO)
最基础的IO模型,当进行IO操作时,线程会被阻塞,直到操作完成。
比如read和write,通常IO操作都是阻塞I/O的,也就是说当你调用read时,如果没有数据收到,那么线程或者进程就会被挂起,直到收到数据。无法处理并发。
阻塞 I/O 是最简单的 I/O 模型,一般表现为进程或线程等待某个条件,如果条件不满足,则一直等下去。条件满足,则进行下一步操作。
NIO(Non-blocking IO) 同步非阻塞IO
- 在这种模型中,线程可以在不阻塞的情况下对文件描述符进行读写操作。
- 当你调用read时,如果有数据收到,就返回数据,如果没有数据收到,就立刻返回一个错误(标识符),这样是不会阻塞线程了。此时用户进程需要不断轮询,如果轮询频繁,则浪费了大量的CPU资源;如果轮询频率低,则不能实时地获取数据。
不断轮询,过度浪费CPU资源。
IO多路复用
- 文件描述符:简称fd,是一个从0开始递增的无符号整数,用来关联Linux中的文件,在Linux中,一切皆文件,例如常规文件、视频,也包括网络套接字
- IO多路复用:利用单个线程可以监视并同时等待多个文件描述符,而不是使用多个线程(每个文件描述符一个线程,每次new一个线程),当这些文件描述符(套接字描述符)其中一个进入读就绪或者写就绪,select、poll、epoll就可以返回,从而避免无效的等待,充分利用CPU资源
select
可以看到select监听的文件描述符包括三个readfds、writefds、exceptfds,将用户传入的数组拷贝到内核空间,select会被阻塞,直到描述符就绪,返回。
selcet核心执行流程
- select是一个阻塞函数,当没有数据时,会一直阻塞在select。
- 当有数据时,会将对应的rset设置为1
- select函数返回,不在阻塞。遍历文件描述符判断那个fd置位,读取数据,然后处理。
缺点:
- bitmap 最大1024 一个进程最多处理1024个客户端。
- rset位不可重复用,每次socket有数据就会相应的位被置位。
- 文件描述符数组拷贝到了内核态,select调用需要传入fd数组,需要拷贝一份到内核,高并发场景下消耗的资源是惊人的。
- select没有通知用户态哪一个socket有数据,select仅返回可读文件的个数,具体哪个可读还需要用户自己O(N)遍历。
小结:select方式,既做到了一个线程处理多个客户端连接(文件描述符),又减少了系统调用的开销(多个文件描述符只有一次 select 的系统调用 + N次就绪状态的文件描述符的 read 系统调用
poll
在select类似,在select上做了一部分改进,并没有解决根本问题。
优点
- 使用数组来解决select的bitmap 1024限制。
- 有事件发生时,将对应的revents置位位为1,遍历的时候将对应的位置设置为0,可以实现重用。
缺点
本质上还是select方法
- poll 将fds数组拷贝到内核态,仍然有开销。
- poll并没有通知用户态那个socket有数据,还是需要O(N)遍历
epoll
- 没有监控数量限制,使用红黑树存储一份文件描述符集合,
- 每个文件描述符只需要执行一次epoll_ctl添加到红黑树,无需用户每次都重新传入;—— 解决了 select 中 fd_set 重复拷贝到内核的问题
- 通过异步 IO 事件找到就绪的文件描述符,而不是通过轮询的方式;
- 内核会将就绪的FD拷贝到用户空间指定位置,使用队列存储就绪的文件描述符,且会按需返回就绪的文件描述符,无须再次遍历;
Redis的IO多路复用
-
在多路复用IO模型中,会有一个内核线程不断去管理和轮询多个socket状态,只有当真正读写事件发送时,才会去调用实际IO读写操作,不需要建立新的进程和线程,也不需要维护这些进程和线程,大大减少了资源占用。
-
多路复用是使用select、poll、epoll可以同时监察多个流的IO事件的能力,在空间时候,会把当前线程阻塞,当有一个或多个流有IO事件,就从阻塞态唤醒,于是程序就会轮询一遍所有的流(epoll只会轮询真正发出事件的流),并且按顺序处理就绪的流,可以避免大量无用操作。这样就可以让单个线程高效处理多个连接请求。
redis中IO阻塞的问题
Rdis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以I/O操作在一般情况下往往不能直接返回,这会导致某一文件的I/O阻塞导致整个进程无法对其它客户提供服务,而I/O多路复用就是为了解决这个问题而出现
Redis使用epoll实现IO多路复用
Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
Redis服务采用Reactor的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)
Reactor模式,是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程
Reactor模式
Reactor模式也叫Dispatcher模式。即I/O多了复用统一监听事件,收到事件后分发(Dispatch给某进程),是编写高性能网络服务器的必备技术。Reactor模式中有2个关键组成:
- Reactor,Reactor在一个单独的线程中运行,负贵监听和分发事件,分发给适当的处理程序来对IO事件做出反应。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人
- Handlers,处理程序执行I/O事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际办理人。Reactor通过调度适当的处理程序来响应I/O事件,处理程序执行非阻塞操作