高并发服务器IO模型

  • 高并发服务器 IO 模型
  • 一、 什么是 IO?
  • 二、 IO 的 5 种模型
  • 三、Linux 并发网络编程模型
  • 四、select、epoll、kqueue、IOCP
  • 更多c/c++ Linux服务器高阶知识、电子书籍、视频等等请后台私信【架构】获取

    知识点有C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。

    高并发服务器IO模型

    高并发服务器IO模型

    一、什么是 IO?

    IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整 IO 分为两阶段:用户进程空间内核空间、内核空间设备空间(磁盘、网络等)。IO 有内存 IO、网络 IO 和磁盘 IO 三种,通常我们说的 IO 指的是后两者。

    二、 IO 的 5 种模型

    5 种模型可以分为同步 IO 和异步 IO 两大类,1~4 为同步 IO 模型,5 位异步 IO 模型

    阻塞 IO 模型

    阻塞 IO 模型 , 进程发起 IO 系统调用后,进程被阻塞,转到内核空间处理,整个 IO 处理完毕后返回进程。操作成功则进程获取到数据。资源不可用时,IO 请求一直阻塞,直到反馈结果(有数据或超时)。

    典型应用:阻塞 socket、Java BIO;

    特点:

    进程阻塞挂起不消耗 CPU 资源,及时响应每个操作;

    实现难度低、开发应用较容易;

    适用并发量小的网络应用开发;

    不适用并发量大的应用:因为一个请求 IO 会阻塞进程所有操作。

    非阻塞 IO 模型

    非阻塞 IO 模型,进程发起 IO 系统调用后,如果内核缓冲区没有数据,需要到 IO 设备中读取,进程返回一个错误而不会被阻塞;进程发起 IO 系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。

    对于上面的阻塞 IO 模型来说,内核数据没准备好需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。

    典型应用:socket 是非阻塞的方式(设置为 NONBLOCK)

    特点:

    进程轮询(重复)调用,消耗 CPU 的资源;

    实现难度低、开发应用相对阻塞 IO 模式较难;

    适用并发量较小、且不需要及时响应的网络应用开发;

    IO 复用模型

    IO 复用模型,多个的进程的 IO 可以注册到一个复用器(select)上,然后用一个进程调用该 select, select 会监听所有注册进来的 IO;如果 select 没有监听的 IO 在内核缓冲区都没有可读数据,select 调用进程会被阻塞;而当任一 IO 在内核缓冲区中有可数据时,select 调用就会返回;而后 select 调用进程可以自己或通知另外的进程(注册进程)来再次发起读取 IO,读取内核中准备好的数据。

    典型应用:select、poll、epoll 三种方案,JAVA NIO 特点:

    专一进程解决多个进程 IO 的阻塞问题,性能好;Reactor 模式;

    实现、开发应用难度较大;

    适用高并发服务应用开发:一个进程(线程)响应多个请求;

    Linux 中 IO 复用的实现方式主要有 select、poll 和 epoll:

    Select:注册 IO、阻塞扫描,监听的 IO 最大连接数不能多于 FD_SIZE;

    Poll:原理和 Select 相似,没有数量限制,但 IO 数量大扫描线性性能下降;

    Epoll :事件驱动不阻塞,mmap 实现内核与用户空间的消息传递,数量很大,Linux2.6 后内核支持;(Windows 有 IOCP、FreeBSD 下有 Kqueue)

    信号驱动的 IO 模型

    信号驱动的 IO 模型,当进程发起一个 IO 操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用 IO 读取数据。

    特点:回调机制,实现、开发应用难度大;

    异步 IO 模型

    异步 IO 模型,当进程发起一个 IO 操作,进程返回(不阻塞),但也不能返回果结;内核把整个 IO 处理完后,会通知进程结果。如果 IO 操作成功则进程直接获取到数据。

    典型应用:JAVA7 AIO、高性能服务器应用

    不阻塞,数据一步到位;Proactor 模式;

    需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性;

    实现、开发应用难度大;

    非常适合高性能高并发应用;

    五种 IO 模型比较

    高并发服务器IO模型

    IOCompare.png

    三、Linux 并发网络编程模型

    Apache 模型

    Apache 模型,简称 PPC(Process Per Connection):为每个连接分配一个进程。主机分配给每个连接的时间和空间上代价较大,并且随着连接的增多,大量进程间切换开销也增长了。很难应对大量的客户并发连接。

    TPC 模型

    TPC 模型(Thread Per Connection ):每个连接一个线程。和 PCC 类似。

    select 模型

    select 模型:I/O 多路复用技术。

    • 每个连接对应一个描述。select 模型受限于 FD_SETSIZE 即进程最大打开的描述符数 linux2.6.35 为 1024,实际上 linux 每个进程所能打开描数字的个数仅受限于内存大小,然而在设计 select 的系统调用时,却是参考 FD_SETSIZE 的值。可通过重新编译内核更改此值,但不能根治此问题,对于百万级的用户连接请求 即便增加相应 进程数, 仍显得杯水车薪呀。
    • select 每次都会扫描一个文件描述符的集合,这个集合的大小是作为 select 第一个参数传入的值。但是每个进程所能打开文件描述符若是增加了 ,扫描的效率也将减小。
    • 内核到用户空间,采用内存复制传递文件描述上发生的信息。

    poll 模型

    poll 模型:I/O 多路复用技术。poll 模型将不会受限于 FD_SETSIZE,因为内核所扫描的文件 描述符集合的大小是由用户指定的,即 poll 的第二个参数。但仍有扫描效率和内存拷贝问题。

    pselect 模型

    pselect 模型:I/O 多路复用技术。同 select。

    epoll 模型

    • 无文件描述字大小限制仅与内存大小相关
    • epoll 返回时已经明确的知道哪个 socket fd 发生了什么事件,不用像 select 那样再一个个比对。
    • 内核到用户空间采用共享内存方式,传递消息。

    四、select、epoll、kqueue、IOCP

    select()和 poll()

    在 linux 没有实现 epoll 事件驱动机制之前,我们一般选择用 select 或者 poll 等 IO 多路复用的方法来实现并发服务程序。在大数据、高并发、集群等一些名词唱得火热之年代,select 和 poll 的用武之地越来越有限,风头已经被 epoll 占尽。

    select 的缺点:

    单个进程能够监视的文件描述符的数量存在最大限制,通常是 1024,当然可以更改数量,但由于 select 采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;(在 linux 内核头文件中,有这样的定义:#define __FD_SETSIZE 1024)

    内核 / 用户空间内存拷贝问题,select 需要复制大量的句柄数据结构,产生巨大的开销;

    select 返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;

    select 的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行 IO 操作,那么之后每次 select 调用还是会将这些文件描述符通知进程。

    相比 select 模型,poll 使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。

    拿 select 模型为例,假设我们的服务器需要支持 100 万的并发连接,则在__FD_SETSIZE 为 1024 的情况下,则我们至少需要开辟 1k 个进程才能实现 100 万的并发连接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。因此,基于 select 模型的服务器程序,要达到 10 万级别的并发访问,是一个很难完成的任务。

    epoll

    epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 poll,是 Linux 下多路复用 IO 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核 IO 事件异步唤醒而加入 Ready 队列的描述符集合就行了。epoll 除了提供 select/poll 那种 IO 事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存 IO 状态,减少 epoll_wait/epoll_pwait 的调用,提高应用程序效率。

    epoll 有两种工作方式

    ET:Edge Triggered,边缘触发。仅当状态发生变化时才会通知,epoll_wait 返回。换句话,就是对于一个事件,只通知一次。且只支持非阻塞的 socket。

    LT:Level Triggered,电平触发(默认工作方式)。类似 select/poll,只要还有没有处理的事件就会一直通知,以 LT 方式调用 epoll 接口的时候,它就相当于一个速度比较快的 poll.支持阻塞和不阻塞的 socket。

    kqueue

    kqueue 与 epoll 非常相似,最初是 2000 年 Jonathan Lemon 在 FreeBSD 系统上开发的一个高性能的事件通知接口。注册一批 socket 描述符到 kqueue 以后,当其中的描述符状态发生变化时,kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了。

    IOCP

    IOCP 全称 I/O Completion Port,中文译为 I/O 完成端口。IOCP 是一个异步 I/O 的 API,它可以高效地将 I/O 事件通知给应用程序。一个套接字[socket]与一个完成端口关联了起来,然后就可继续进行正常的 Winsock 操作了。然而,当一个事件发生的时候,此完成端口就将被操作系统加入一个队列中。然后应用程序可以对核心层进行查询以得到此完成端口。

    诚然,Windows 的 IOCP 非常出色,目前很少有支持 asynchronous I/O 的系统,但是由于其系统本身的局限性,大型服务器还是在 UNIX 下。而且正如上面所述,kqueue/epoll 与 IOCP 相比,就是多了一层从内核 copy 数据到应用层的阻塞,从而不能算作 asynchronous I/O 类。但是,这层小小的阻塞无足轻重,kqueue 与 epoll 已经做得很优秀了。

    select、epoll、kqueue、IOCP 对比

    只有 IOCP 是 asynchronous I/O,其他机制或多或少都会有一点阻塞。

    select 低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

    epoll, kqueue、select 是 Reacor 模式,IOCP 是 Proactor 模式。

    java nio 包是 select 模型。。



    分享到:


    相關文章: