1.前言

此系列将尽可能详细介绍断更博客半年以来个人的一个成长,主要是对Netty的源码的一个解读记录,将从整个计算机宏观IO体系上,到Java的原生NIO例子最后到Netty的源码解读。不求完全掌握,但求知道前因后果,设计思路,来检验半年所学(之前是懒,水平不够,现在写博客查漏补缺)。介绍过程中所涉及的知识点可能有错,请各位指教,相互学习。

2.为什么关注IO

简而言之,就是为了程序执行快,当然现代程序的瓶颈IO只占了其中一部分。IO为什么成为了系统瓶颈呢,引用网上一篇博客(这里)对CPU、内存和磁盘等存储介质的运行比较,这里简要说明一下:

CPU的运行速度快的离谱,大部分简单指令只要一个时钟周期(操作系统是按时间片(几十毫秒,不固定)运行程序的,这样单核CPU就可以实现大的时间尺度上多个程序同时运行的假象),一个时间周期1/3纳秒,足以见CPU速度之快。把一个时间周期看做1秒,从主存中读取数据就花了4分钟,果断不能忍啊,所以现代计算机在主存和CPU之间会构建多级高速缓存,L1、L2、L3等。CPU尽可能从L1获取数据,只要3秒,L1尽可能从L2获取数据,只要14秒,同样L2->L3,L3->内存。这样可以在有限的成本增加,大幅度提高CPU和主存的数据交互带来的性能浪费。当然这会带来而外的问题,比如什么数据应该放在L1、L2、L3中,另外一个带来的问题就是内存可见性问题,Java中多线程volatile字段的含义(多线程中线程A、B持有同一个变量,A修改了这个变量,确实在主存中该变量被修改了,但是B修改的时候会产生线程安全问题,因为由于高速缓存的存在,B读取该变量不是从主存中读取的,而是从高速缓存中读取,此时这个值并没有更新,导致A的操作对B线程而言是不可见的。volatile字段的作用就在于强制从主存中获取该变量的值,而不是高速缓存。当然这样做只保证了可见性,即A线程操作对B线程可见,并不能保证线程安全,还需要保证操作的原子性,这里就不再多解释)。

上述扯远了,再来看内存获取磁盘数据的速度,接上面的例子,大概是1年零3个月。纳尼!!!1年零3个月获取数据,CPU就工作了1秒?这就是IO对程序运行带来的可怕的性能制约。阻塞式IO必须等待执行完IO操作,才能运行后面的操作,这对CPU性能是极大的浪费。虽然在时间片消耗完后CPU会切换到其它程序运行相关代码,但是阻塞式IO带来的性能浪费并没有得到解决。所以我们才需要关注非阻塞式IO,让程序继续执行其它逻辑,等到IO操作完成后再执行相应的操作。IO的速度慢在寻道操作,即磁头要先移动到正确的磁道上,然后磁盘旋转到正确的位置上,读取指定扇区的数据。RPM就是衡量磁盘的旋转速度,越快耗时越少。另一个方式就是将文件连续存储在磁盘上,这样也就能节省寻道时间了,但实际上这种方式带来的效果并不明显,主要关注的还是单位时间上寻道和随机IO操作次数,固态硬盘SSD在这方面做的就很好。还有一种方式就是硬盘的cache机制,其将一组零散的写入操作合并成一个,磁盘控制写入顺序,进而减少寻道次数。一系列读取操作也可以重组减少耗时。

3.现代IO操作模型

上面扯了那么多,说明了IO对CPU的性能的浪费,NIO的主要目的是减少这部分无效损耗,并没有对IO所需要的耗时有太多作用。NIO的意思是非阻塞IO,其实现方式有多种,下面准备介绍一下目前IO的主要模型。

在介绍IO模型之前,首先要明白几个概念,才会清楚为什么NIO可以减少CPU的性能浪费。现代操作系统为了保证系统的正确执行,分成了两种操作模式:用户模式和监督程序模式(内核模式)。这么做的原因是为了保护操作系统和用户程序不受其它程序的影响,实现方法是:将能引起损害的机器指令作为特权指令,用户执行特权指令的时候,硬件不会执行该指令,而是认为该指令是非法指令,会以陷阱的方式通知操作系统,由操作系统内核处理,最后转交给用户程序,IO操作就是一个特权指令。操作系统进程有其独享的内核空间,用户程序数据及相关内容放在用户空间中,用户发起一个IO请求,首先就会切换到内核模式,将数据读取到内核空间中去,之后将这部分数据拷贝到用户空间中,切换到用户模式继续执行用户程序,这就是一个基本的IO流程。这里插入一段文件网络传输零拷贝的知识:将文件通过网络传输涉及了两次IO,一次磁盘IO,一次网络IO,由于上述所讲的操作系统的安全策略,内核会先读取文件数据到内核空间,再拷贝到用户空间,传输到网络中去的时候,又将数据从用户空间拷贝到了内核空间,这造成了多次拷贝的浪费,实际上我们只需要将数据读取到内核空间,直接传输到网络中去就可以了。零拷贝的做法就是将文件描述符和网络描述符同时交给内核,指示内核将文件描述符的内容全部传输给网络通道中,这样就达到了零拷贝的效果了。

有了上诉对操作系统的执行方式的了解,再来看IO模型就会比较好理解阻塞式IO和非阻塞式IO的区别,以及非阻塞式IO的好处了。下面介绍目前5种IO模型:

3.1 阻塞IO

阻塞式IO就是用户发起IO请求->内核等待数据加载->内核加载数据完毕->拷贝内核空间数据到用户空间->提醒用户程序完成IO读取操作。

整个过程种,用户发起请求到最后一直都在等待内核处理好数据,之前也说过这段时间相对于CPU而言,执行是非常慢的,所以这段时间整个都是浪费了,一直处理等待之中。这就是阻塞式IO。

3.2 非阻塞IO

阻塞式IO的特点就是等待数据和拷贝数据两段都被阻塞了,后面就产生了上图这种非阻塞式IO了。这种思路就是不断的询问内核是否把数据准备好了,如果没好就继续询问,好了就开始拷贝数据。这种方式拷贝数据阶段也式阻塞的,但是毕竟是内存操作,不会很慢,但是关键问题在于前面的轮询,这个过程也是非常消耗CPU的,并没有达到多好的效果。

3.3 多路复用IO

IO multiplexing就是我们比较熟悉的select/epoll,也被称为事件驱动IO。其可以在单个process中处理多个网络连接。有一个专门的任务负责轮询所有的socket,只要有一个socket有数据达到,就会通知用户进程。当用户进程调用了select,整个进程就会被block,内核会监视所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回,用户调用read操作,数据就会从内核拷贝到用户进程。

该方法和阻塞式IO没有太大不同,可能更差,这里有两个系统调用,但是其优势就在于能够处理多个连接,所以说大量连接此种方式更加,少量连接可能多线程+阻塞式IO要更出色。这种方式用户进程其实也是一直被block的,不过是被select阻塞而不是socketIO。因此select()与非阻塞IO类似。

实际上这个模型也有很多问题。如果连接数真的过多,select会消耗大量时间去轮询各个句柄。epoll方式虽然能更高效,但是epoll方式受制于操作系统,跨平台困难。另外该模型将事件探测和事件响应混在了一起,如果事件响应执行过长,整个响应会延迟。通常使用事件驱动库解决该问题libevent等。

Java的NIO就是基于select实现的。

3.4 信号驱动IO

信号驱动IO会注册一个信号处理程序给内核,内核处理好了就会触发该信号通知程序执行,最终处理数据。该方式真正做到第一阶段的非阻塞,但是其不适合TCP协议。因为无法判断该信号的具体含义,该信号产生的非常频繁,UDP传输采取的较多。

3.5 异步IO

异步IO可以称得上全程无阻塞,直到数据全部准备完成才会通知用户执行后续操作。但是在netty上并没有采取该种方式,其一是在linux系统上运行速度并没有比epoll方式更快,其二是与Netty精心设计的线程模型相悖。windows系统实现了IOCP方式的异步IO,其是proactor模型。

3.6 总结

上诉5种IO模型,只有异步IO方式达到了完全的非阻塞,阻塞式IO则是完全阻塞。但是常用的还是复用IO的方式,设计的好足以媲美aio,而且aio在某些情况性能不如epoll方式,和其具体实现有关。select/epoll和aio造就了两种设计模式,前者是reactor,后者就是proactor。

顺带一提,epoll就是用于解决select在大量连接时遍历连接效率低下的问题,其是基于事件驱动,fd的限制数也远大于1024。epoll_wait只会返回准备就绪的fd,使用nmap内存映射技术避免了内存复制的开销。epoll还有个知识点就是edge-triggered和level-triggered,边缘触发和水平触发。水平触发指的是当准备就绪的fd没有被用户进程处理,下一次查询仍会返回,这是select poll的触发方式。边缘触发指的是无论准备好的fd有没有被处理,下一次都不再返回。理论上边缘触发性能更高,但是实现非常复杂,任何意外的丢失事件都会造成请求处理错误。epoll默认使用水平触发。

4.后记

本节粗略介绍一下IO的相关概念,以便更好理解NIO,下一章介绍JAVA的NIO实现,这里指的就是select poll方式,结合代码,更易理解多路信号复用IO。select poll的真正含义。

漫谈NIO(1)之计算机IO实现的更多相关文章

  1. Java基础知识强化之IO流笔记71:NIO之 NIO的(New IO流)介绍

    1. I/O 简介 I/O ( 输入/输出  ):指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口.它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的. ...

  2. 【网络IO系列】IO的五种模型,BIO、NIO、AIO、IO多路复用、 信号驱动IO

    前言 在上一篇文章中,我们了解了操作系统中内核程序和用户程序之间的区别和联系,还提到了内核空间和用户空间,当我们需要读取一条数据的时候,首先需要发请求告诉内核,我需要什么数据,等内核准备好数据之后 , ...

  3. NIO学习:异步IO实例

    工作模式: 客户端代码: package demos.nio.socketChannel; import java.io.ByteArrayOutputStream; import java.io.I ...

  4. Java网络编程和NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型

    Java网络编程与NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型 知识点 nio 下 I/O 阻塞与非阻塞实现 SocketChannel 介绍 I/O 多路复用的原理 事件选择器与 ...

  5. Java nio 笔记:系统IO、缓冲区、流IO、socket通道

    一.Java IO 和 系统 IO 不匹配 在大多数情况下,Java 应用程序并非真的受着 I/O 的束缚.操作系统并非不能快速传送数据,让 Java 有事可做:相反,是 JVM 自身在 I/O 方面 ...

  6. 大话存储 1 - 走进计算机IO世界

    组成计算机的三大件:CPU,内存和IO. 1 总线 总线就是一条或者多条物理上的导线,每个部件都接到这些导线上,同一时刻只能有一个部件在接收或者发送. 仲裁总线:所有部件按照另一条总线,也就是仲裁总线 ...

  7. 漫谈NIO(3)之Netty实现

    1.前言 上一章结合Java的NIO例子,讲解了多路IO复用的一个基本使用方法,通过实际编码加深对其理解.本章开始进入Netty的环节,前面两章都是为了Netty进行铺垫说明.此节将对比Java的NI ...

  8. 漫谈NIO(2)之Java的NIO

    1.前言 上章提到过Java的NIO采取的是多路IO复用模式,其衍生出来的模型就是Reactor模型.多路IO复用有两种方式,一种是select/poll,另一种是epoll.在windows系统上使 ...

  9. 【Java TCP/IP Socket】Java NIO Socket VS 标准IO Socket

    简介 Java  NIO从JDK1.4引入,它提供了与标准IO完全不同的工作方式. NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题.    1. ...

随机推荐

  1. 程序员面试50题(1)—查找最小的k个元素[算法]

    题目:输入n个整数,输出其中最小的k个.例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4. 分析:这道题最简单的思路莫过于把输入的n个整数排序,这样排在最前面的k个数 ...

  2. 51Nod 1376 最长递增子序列的数量 (DP+BIT)

    题意:略. 析:dp[i] 表示以第 i 个数结尾的LIS的长度和数量,状态方程很好转移,先说长度 dp[i] = max { dp[j] + 1 | a[i] > a[j] && ...

  3. Vue 需要使用jsonp解决跨域时,可以使用(vue-jsonp)

    1,执行命令 npm install vue-jsonp --save 2.src/main.js中添加: import VueJsonp from 'vue-jsonp' Vue.use(VueJs ...

  4. Object-C中 - self 和super 的含义

    //super:父类         //self:自己              //自己理解         //以MobilePhone为例,父类为NSObject         //在类方法 ...

  5. 解决Error creating bean with name 'huayuanjingguanDaoimp' defined in file [D:\apache-tomcat-7.0.52\webapps\landscapings\WEB-INF\classes\com\itheima\landscaping\dao\imp\huayuanjingguanDaoimp.class]: Invo

    问题描述: 10:23:13,585 ERROR ContextLoader:307 - Context initialization failedorg.springframework.beans. ...

  6. (线段树)Just a Hook -- hdu -- 1689

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=1698 思路: 我的想法很简单,像上一题一样从后面向前面来算,前面已经覆盖的,后面自然不能再来计算了,具体 ...

  7. c# 调用短信平台接口,给手机发送短信

    项目上要做个发手机短信的功能.网上找找了,用的微米的短信接口. 注册后,获得UID和UID key,C#代码中需要这个 调用代码很简单 ", con = "[微米]您的验证码是:6 ...

  8. From Alpha to Gamma (II)

    这篇文章被拖延得这么久是因为我没有找到合适的引言 -- XXX 这一篇接着讲Gamma.近几年基于物理的渲染(Physically Based Shading, 后文简称PBS)开始在游戏业界受到关注 ...

  9. C语言作业03-函数

    1.本章学习总结 1.1 思维导图 1.2本章学习体会,代码量学习体会 1.2.1学习体会 通过这几周的函数学习,让我明白了函数的重要性,在很多时候运用函数,会使得代码分工明确,逻辑严密,不繁琐.函数 ...

  10. Process Class (System.Diagnostics)

    import sys def hanoi(n, a, b, c): if n == 1: print('%c --> %c' % (a, c)) else: hanoi(n-1, a, c, b ...