关于I/O模型的文章比较多,参考多篇后理解上仍然不太满意,终需自己整理一次,也是编写高吞吐量高性能网络接口模块的基础。这里所说的主要针对网络I/O,近几年面对越来越大的用户请求量,如何优化这些步骤直接影响接口用户体验。

一、前言

I/O模型有几个名词的解释 (比较容易混淆):

阻塞与非阻塞:区别在于调用函数时,是否立即返回还是让线程等待。阻塞模型需要等待操作完成,而非阻塞模型则是立即返回(未准备好则返回一个错误码)。

同步与非同步:区别在于网络数据从内核拷贝到用户空间时是否需要用户线程参与等待。

UNIX 网络I/O模型分类有几种 (参考 UNIX Network Programming Volume.1.3rd.Ed):

1.  Blocking I/O
2.  Nonblocking I/O
3.  I/O multiplexing (select and poll)
4.  Signal driven I/O (SIGIO)
5.  Asynchronous I/O (the POSIX aio_functions)

它们有两个独特的阶段差别 :

1. 等待数据准备好.  (阻塞/非阻塞的差异)

2. 数据从内核复制到用户空间.  (同步/非同步的差异)

理解上面2个阶段,对后面的解释就很容易明白。

二、I/O 模型详解

2.1 Blocking I/O

下图以UDP服务端调用 recvfrom为例描述线程等待过程 (TCP稍微复杂):

主处理线程一直阻塞到有用户数据,并且数据从内核拷贝到用户空间,也就是说主处理线程同一时间只能处理一个用户请求:

伪代码类似如下:

  1. while (1)
  2. {
  3. Socket clientSock = serverSock.accept();
  4. processRequest(clientSock);
  5. }
  6.  
  7. void processRequest(Socket clientSock)
    {
    read(...);
    write(...);
    }

在复杂的网络环境中,经常出现一个“慢速”客户端。也就是说这个客户端数据到达很慢,在TCP网络编程中,如果以此方式等待足够的数据(根据协议定义),则会严重影响到其他客户端的处理等待时间。

由此进行改善的模型就是使用线程池,伪代码如下:

  1. while (1)
  2. {
  3. Socket clientSock = serverSock.accept();
  4. threadPool.execute(new Task(processRequest(clientSock));
  5. }
  6.  
  7. void processRequest(Socket clientSock)
  8. {
  9. read(...);
  10. write(...);
  11. }

线程池模式使得主线程能处理更多的客户端,N个客户端使用M个线程(N:M),主线程不会被一个慢客户端阻塞,但是处理能力仍然是比较有限的。

2.2 Nonblocking I/O

将socket设置为非阻塞模式,告诉内核如果操作不能立即完成则返回一个错误码而不是等待,描述图如下:

如下图,主线程一直尝试调用网络函数,直到数据准备好,该模型的缺陷就是"忙等待",CPU空转浪费系统资源。此模式极少使用,在此仅用于介绍

2.3 I/O multiplexing (多路复用)

Linux 中提供select/poll系统调用实现,线程阻塞在此方法上,监测多个socket fd(file descriptor)是否就绪,一旦有事件发生则顺序扫描具体是哪个就绪,但这样做会比较费时,模型如下:

在Linux kernel 2.6+提供了epoll实现,使用驱动方式替代顺序扫描,哪个fd有事件发生就返回哪一个(避免了select/poll的只要有一个发生就扫描全部找出是哪个,新的内核也可能对这模式实现做了一些优化)。

在ORACLE JDK有如下源码对内核做判断:

  1. package sun.nio.ch;
  2. import ...
  3. public class DefaultSelectorProvider {
  4. public static SelectorProvider create() {
  5. ......
  6.  
  7. if ("Linux".equals(str1)) {
  8. String str2 = (String)AccessController.doPrivileged(new GetPropertyAction("os.version"));
  9.  
  10. String[] arrayOfString = str2.split("\\.", 0);
  11. if (arrayOfString.length >= 2) {
  12. try {
  13. int i = Integer.parseInt(arrayOfString[0]);
  14. int j = Integer.parseInt(arrayOfString[1]);
  15. if ((i > 2) || ((i == 2) && (j >= 6))) {
  16. return new EPollSelectorProvider();
  17. }
  18. } catch (NumberFormatException localNumberFormatException) {}
  19. }
  20. }
  21. return new PollSelectorProvider();
  22. }
  23. }

关于epoll的实现有专门文章详解,在此不做细说,另外一个epoll使用mmap内存映射避免内存复制损耗。

2.4 Asynchronous I/O (异步)

异步I/O告诉内核开始某个操作,在内核完成后(包括数据从内核拷贝到用户空间)通知我们,如下图:

在ORACLE JDK 1.7+提供异步I/O的实现AsynchronousChannel,相对于原Selector实现I/O复用模式简单很多。

在这几种模型中,只有异步I/O是用户线程不参与数据从内核拷贝到用户空间这个过程。

2.5 几种I/O模型的对比

如下图,它们在 [等待数据] 和 [从内核复制数据到用户空间] 的差异:

从上图看出,只有Asynchronous I/O不参与内核数据复制。

三、实例举例说明

3.1 针对BIO拒绝服务攻击

BIO中Boss线程处理请求后交给连接池Worker处理,但线程池有限,能轻易导致服务异常。比如针对Tomcat默认配置进行HTTP慢速攻击:

声明一个Content-Length为300的POST包,开启300个线程请求,每个请求中每秒发送1 byte。默认情况下Tomcat的200个线程将爆满,在持续攻击的这300秒内,都无法正常处理其他正常用户请求,造成服务异常。

改善方法:将连接器使用NIO处理,修改默认配置protocol为Http11NioProtocol,并且增大合适的线程数,对此类攻击有一定的缓解作用。

3.2  对外接口服务应用

如果使用Java开发对外接口服务,在对响应时延和吞吐量有一定要求的话,通常可以考虑集成Netty (NIO),如果遵循Servlet标准可考虑集成Jetty使用NIO连接器处理。在协议方面,内部服务可选择高效的私有协议和高压缩比的组件(如protobuf / kryo等),对外服务可选择HTTP+JSON。

WEB服务可考虑动静分离,html/css/js/image等文件使用Nginx (sendfile提供更高效的数据传输方式)处理,业务数据才透到后端服务中,后端服务加入缓存等方式减少响应时延。

3.3 关于JDK中一些名词解析

在JDK提供的NIO API与这里介绍的NIO(Nonblocking I/O)模型是不同的概念,JDK在1.7之前提供的Selector基于select/poll、epoll(Linux kernel > 2.6)实现I/O复用技术的非阻塞IO,JDK1.7以后提供的NIO2.0 API (如AsynchronousServerSocketChannel) 才是真正的异步I/O。

参考资料:

1.  http://www.madwizard.org/programming/tutorials/netcpp/5

2.  http://www.importnew.com/22019.html

3. 《UNIX Network Programming Volume.1.3rd.Edition》

4.《Netty权威指南》

浅谈Unix I/O模型的更多相关文章

  1. 浅谈 Unix I/O 模型

    原文出处:http://miaoo.in/talk-about-unix-io-model.html 在实际应用中,数据操作通常分为输入和输出,那么以输入为例,在操作系统中,一个数据的输入通常分为以下 ...

  2. 浅谈 unix, linux, ios, android 区别和联系

    浅谈 unix, linux, ios, android 区别和联系 网上的答案并不是很好,便从网上整理的相对专业的问答,本人很菜,大佬勿喷 UNIX 和 Linux   UNIX 操作系统(尤尼斯) ...

  3. 浅谈Java的内存模型以及交互

    本文的内存模型只写虚拟机内存模型,物理机的不予描述. Java内存模型 在Java中,虚拟机将运行时区域分成6中,如下图:              程序计数器:用来记录当前线程执行到哪一步操作.在多 ...

  4. 转:浅谈UNIX下Apache的MPM及httpd.conf配置文件中相关参数配置

    为什么要并发处理 以Apache为代表的web服务器中,如果不支持并发,则在一个客户端连接的时候,如果该客户端的任务没有处理完,其他连接的客户端将会一直处于等待状态,这事不可想象的,好像没有为什么要不 ...

  5. 浅谈 css3 box盒子模型以及box-flex的使用

    display:box;box-flex是css3新添加的盒子模型属性,它的出现可以解决我们通过N多结构.css实现的布局方式.经典的一个布局应用就是布局的垂直等高.水平均分.按比例划分.   一.使 ...

  6. 浅谈异步IO各模型优缺点

    本文只讨论OverLapped I/O的三种异步模型及完成端口,像select.SWASelect不作讨论,讨论顺序从劣到优,方便于循序渐进地对比,更容易区分各模型之间的差别. 1. OverLapp ...

  7. 浅谈OSI七层模型及ICP/IP四层模型

    1.OSI七层模型的概念 在网络历史的早期,国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)共同出版了开放系统互联的七层参考模型. 一台计算机操作系统中的网络过程包括从应用请求(在协议栈 ...

  8. 【转】从Mac/OS和iOS开放源码浅谈UNIX家谱

    阅读数:1245 苹果公司在各类开源项目中长期贡献着自己的力量,但其UNIX系统技术一直都属于闭源阵营(这一点可以从NUX OS阵营和家谱图中得到答案).然而,以封闭闻名的苹果公司,2017年国庆期间 ...

  9. 【转】浅谈Node.js单线程模型

    Node.js采用 事件驱动 和 异步I/O 的方式,实现了一个单线程.高并发的运行时环境,而单线程就意味着同一时间只能做一件事,那么Node.js如何利用单线程来实现高并发和异步I/O?本文将围绕这 ...

随机推荐

  1. get和post与服务端的交互方式

    在网上看了不少关于get和post的文章,看到博主这个,现在手录下来. 原博客地址:http://www.cnblogs.com/warrior4236/p/5675756.html 一:B/S结构, ...

  2. webapp填坑记录[更新中]

    网上也有许多的 webapp 填坑记录了,这几个月,我在公司正好也做了2个,碰到了一些问题,所以我在这里记录一下我所碰到的问题: meta 头部声明在开发的时候,刚刚创建 HTML 文件,再使用浏览器 ...

  3. NJCTF 极少部分wp

    前沿:刚刚结束了NJCTF,做出来的题目很少. 1,----非常简单的misc , check QQ ,直接在QQ群可以看到发布的flag. 2,konck----hint:韩琛截获了一张纸条,突然记 ...

  4. Java 读取 .properties 配置文件的几种方式

    Java 开发中,需要将一些易变的配置参数放置再 XML 配置文件或者 properties 配置文件中.然而 XML 配置文件需要通过 DOM 或 SAX 方式解析,而读取 properties 配 ...

  5. Maven引入jar的总结

    Overview:显示maven项目的一些基本信息 Dependencies:添加jar包的页面 Plugins:添加maven插件的页面.比如tomcat-maven-plugin等 Reporti ...

  6. C# Code First 实例学习

    Code First是Entity Framework提供的一种新的编程模型.通过Code First我们可以在还没有建立数据库的情况下就开始编码,然后通过代码来生成数据库.下面具体讲解一下,在操作的 ...

  7. 2719:陶陶摘苹果-poj

    2719:陶陶摘苹果 总时间限制:  1000ms 内存限制:  65536kB 描述 陶陶家的院子里有一棵苹果树,每到秋天树上就会结出10个苹果.苹果成熟的时候,陶陶就会跑去摘苹果.陶陶有个30厘米 ...

  8. SQL测试题

    一.网上收集了一些测试题,对于掌握SQL查询语句. /* Navicat MySQL Data Transfer Source Server : lizebo's MYSQL Source Serve ...

  9. Java中net.sf.json包关于JSON与对象互转的问题

    在Web开发过程中离不开数据的交互,这就需要规定交互数据的相关格式,以便数据在客户端与服务器之间进行传递.数据的格式通常有2种:1.xml:2.JSON.通常来说都是使用JSON来传递数据.本文正是介 ...

  10. Servlet中web.xml 以及 <url-pattern>总结

    web.xml中添加Servlet配置信息 使用Eclipse创建Servlet,会自动的在WEB-INF下的web.xml中声明,但是有的时候需要我们手动的写入配置信息,以下就是Servlet在we ...