文章目录

  • Pre
  • I/O 的编程模型
  • 数据的传输和转化成本
  • 数据结构运用
    • 缓冲区
    • I/O 多路复用模型
  • 总结
  • QA
    • BIO、NIO 和 AIO 有什么区别?
    • I/O 多路复用用协程和用线程的区别?


Pre

我们在处理网络问题时,经常是处理 I/O 问题——输入和输出。看上去很复杂,但说白了就是如何把网卡收到的数据给到指定的程序,然后程序如何将数据拷贝到网卡。

在处理 I/O 的时候,要结合具体的场景来思考程序怎么写。从程序的 API 设计上,我们经常会看到 3 类设计:BIO、NIO 和 AIO 。

从本质上说,讨论 BIO、NIO、AIO 的区别,其实就是在讨论 I/O 的模型,我们可以从下面 3 个方面来思考 。

  • 编程模型:合理设计 API,让程序写得更舒服。

  • 数据的传输和转化成本:比如减少数据拷贝次数,合理压缩数据等。

  • 高效的数据结构:利用好缓冲区、红黑树等


I/O 的编程模型

我们先从编程模型上讨论下 BIO、NIO 和 AIO 的区别。

BIO(Blocking I/O,阻塞 I/O),API 的设计会阻塞程序调用。比如:

byte a = readKey()

假设readKey方法从键盘读取一个按键,如果是非阻塞 I/O 的设计,readKey不会阻塞当前的线程。你可能会问:那如果用户没有按键怎么办?在阻塞 I/O 的设计中,如果用户没有按键线程会阻塞等待用户按键,在非阻塞 I/O 的设计中,线程不会阻塞,没有按键会返回一个空值,比如 null。

最后我们说说 AIO(Asynchronous I/O, 异步 I/O),API 的设计会多创造一条时间线。比如

func callBackFunction(byte keyCode) {// 处理按键}readKey( callBackFunction )

在异步 I/O 中,readKey方法会直接返回,但是没有结果。结果需要一个回调函数callBackFunction去接收。从这个角度看,其实有两条时间线。第一条是程序的主干时间线,readKey的执行到readKey下文的程序都在这条主干时间线中。而callBackFunction的执行会在用户按键时触发,也就是时间不确定,因此callBackFunction中的程序是另一条时间线也是基于这种原因产生的,我们称作异步,异步描述的就是这种时间线上无法同步的现象,你不知道callbackFunction何时会执行。

但是我们通常说某某语言提供了异步 I/O,不仅仅是说提供上面程序这种写法,上面的写法会产生一个叫作回调地狱的问题,本质是异步程序的时间线错乱,导致维护成本较高。

request("/order/123", (data1) -> {//..request("/product/456", (data2) -> {// ..request("/sku/789", (data3) -> {//...})})})

比如上面这段程序(称作回调地狱)维护成本较高,因此通常提供异步 API 编程模型时,我们会提供一种将异步转化为同步程序的语法。比如下面这段伪代码:

Future future1 = request("/order/123")Future future2 = request("/product/456")Future future3 = request("/sku/789")// ...// ...order = future1.get()product = future2.get()sku = future3.get()

request 函数是一次网络调用,请求订单 ID=123 的订单数据。本身 request 函数不会阻塞,会马上执行完成,而网络调用是一次异步请求,调用不会在request("/order/123")下一行结束,而是会在未来的某个时间结束。因此,我们用一个 Future 对象封装这个异步操作。future.get()是一个阻塞操作,会阻塞直到网络调用返回。

在request和future.get之间,我们还可以进行很多别的操作,比如发送更多的请求。 像 Future 这样能够将异步操作再同步回主时间线的操作,我们称作异步转同步,也叫作异步编程。


数据的传输和转化成本

上面我们从编程的模型上对 I/O 进行了思考,接下来我们从内部实现分析下 BIO、NIO 和 AIO。无论是哪种 I/O 模型,都要将数据从网卡拷贝到用户程序(接收),或者将数据从用户程序传输到网卡(发送)。

另一方面,有的数据需要编码解码,比如 JSON 格式的数据。还有的数据需要压缩和解压。数据从网卡到内核再到用户程序是 2 次传输。注意,将数据从内存中的一个区域拷贝到另一个区域,这是一个 CPU 密集型操作。数据的拷贝归根结底要一个字节一个字节去做。

从网卡到内核空间的这步操作,可以用 DMA(Direct Memory Access)技术控制。DMA 是一种小型设备,用 DMA 拷贝数据可以不使用 CPU,从而节省计算资源。遗憾的是,通常我们写程序的时候,不能直接控制 DMA,因此 DMA 仅仅用于设备传输数据到内存中。

不过,从内核到用户空间这次拷贝,可以用内存映射技术,将内核空间的数据映射到用户空间。


数据结构运用

在处理网络 I/O 问题的时候,还有一个重点问题要注意,就是数据结构的运用。

缓冲区

缓冲区是一种在处理 I/O 问题中常用的数据结构,

  • 一方面缓冲区起到缓冲作用,在瞬时 I/O 量较大的时候,利用排队机制进行处理。

  • 另一方面,缓冲区起到一个批处理的作用,比如 1000 次 I/O 请求进入缓冲区,可以合并成 50 次 I/O 请求,那么整体性能就会上一个档次。

举个例子,比如你有 1000 个订单要写入 MySQL,如果这个时候你可以将这 1000 次请求合并成 50 次,那么磁盘写入次数将大大减少。同理,假设有 10000 次网络请求,如果可以合并发送,会减少 TCP 协议握手时间,可以最大程度地复用连接;另一方面,如果这些请求都较小,还可以粘包复用 TCP 段。在处理 Web 网站的时候,经常会碰到将多个 HTTP 请求合并成一个发送,从而减少整体网络开销的情况。

除了上述两方面原因,缓冲区还可以减少实际对内存的诉求。数据在网卡到内核,内核到用户空间的过程中,建议都要使用缓冲区。当收到的某个请求较大的时候,抽象成流,然后使用缓冲区可以减少对内存的使用压力。这是因为使用了缓冲区和流,就不需要真的准备和请求数据大小一致的内存空间了。可以将缓冲区大小规模的数据分成多次处理完,实际的内存开销是缓冲区的大小


I/O 多路复用模型

在运用数据结构的时候,还要思考 I/O 的多路复用用什么模型。

假设你在处理一个高并发的网站,每秒有大量的请求打到你的服务器上,你用多少个线程去处理 I/O 呢?对于没有需要压缩解压的场景,处理 I/O 的主要开销还是数据的拷贝。那么一个 CPU 核心每秒可以完成多少次数据拷贝呢?

拷贝,其实就是将内存中的数据从一个地址拷贝到另一个地址。再加上有 DMA,内存映射等技术,拷贝是非常快的。不考虑 DMA 和内存映射,一个 3GHz 主频的 CPU 每秒可以拷贝的数据也是百兆级别的。当然,速度还受限于内存本身的速度。因此总的来说,I/O 并不需要很大的计算资源。通常我们在处理高并发的时候,也不需要大量的线程去进行 I/O 处理。

对于多数应用来说,处理 I/O 的成本小于处理业务的成本。处理高并发的业务,可能需要大量的计算资源。每笔业务也可能会需要更多的 I/O,比如远程的 RPC 调用等。

因此我们在处理高并发的时候,一种常见的 I/O 多路复用模式就是由少量的线程处理大量的网络接收、发送工作。然后再由更多的线程,通常是一个线程池处理具体的业务工作

在这样一个模式下,有一个核心问题需要解决,就是当操作系统内核监测到一次 I/O 操作发生,它如何具体地通知到哪个线程调用哪段程序呢?

这时,一种高效的模型会要求我们将线程、线程监听的事件类型,以及响应的程序注册到内核。具体来说,比如某个客户端发送消息到服务器的时候,我们需要尽快知道哪个线程关心这条消息(处理这个数据)。例如 epoll 就是这样的模型,内部是红黑树。我们可以具体地看到文件描述符构成了一棵红黑树,而红黑树的节点上挂着文件描述符对应的线程、线程监听事件类型以及相应程序


讲了这么多,和 BIO、AIO、NIO 有什么关系?这里有两个联系。

首先是无论哪种编程模型都需要使用缓冲区,也就是说 BIO、AIO、NIO 都需要缓冲区,因此关系很大。在我们使用任何编程模型的时候,如果内部没有使用缓冲区,那么一定要在外部增加缓冲区。另一个联系是类似 epoll 这种注册+消息推送的方式,可以帮助我们节省大量定位具体线程以及事件类型的时间。这是一个通用技巧,并不是独有某种 I/O 模型才可以使用

不过从能力上分析,使用类似 epoll 这种模型,确实没有必要让处理 I/O 的线程阻塞,因为操作系统会将需要响应的事件源源不断地推送给处理的线程,因此可以考虑不让处理线程阻塞(比如用 NIO)


总结

从 3 个方面讨论了 I/O 模型。

  • 第一个是编程模型,阻塞、非阻塞、异步 3 者 API 的设计会有比较大的差异。通常情况下我们说的异步编程是异步转同步。异步转同步最大的价值,就是提升代码的可读性。可读,就意味着维护成本的下降以及扩展性的提升。

  • 第二个在设计系统的 I/O 时,另一件需要考虑的就是数据传输以及转化的成本。传输主要是拷贝,比如可以使用内存映射来减少数据的传输。但是这里要注意一点,内存映射使用的内存是内核空间的缓冲区,因此千万不要忘记回收。因为这一部分内存往往不在我们所使用的语言提供的内存回收机制的管控范围之内。

  • 最后是关于数据结构的运用,针对不同的场景使用不同的缓冲区,以及选择不同的消息通知机制,也是处理高并发的一个核心问题。

从上面几个角度去看 I/O 的模型,你会发现,编程模型是编程模型、数据的传输是数据的传输、消息的通知是消息的通知,它们是不同的模块,完全可以解耦,也可以根据自己不同的业务特性进行选择。虽然在一个完整的系统设计中,往往提出的是一套完整的解决方案 ,但实际上我们还是应该将它们分开去思考,这样可以产生更好的设计思路。


QA

BIO、NIO 和 AIO 有什么区别?

总的来说,这三者是三个 I/O 的编程模型。BIO 接口设计会直接导致当前线程阻塞。NIO 的设计不会触发当前线程的阻塞。AIO 为 I/O 提供了异步能力,也就是将 I/O 的响应程序放到一个独立的时间线上去执行。但是通常 AIO 的提供者还会提供异步编程模型,就是实现一种对异步计算封装的数据结构,并且提供将异步计算同步回主线的能力。

通常情况下,这 3 种 API 都会伴随 I/O 多路复用。如果底层用红黑树管理注册的文件描述符和事件,可以在很小的开销内由内核将 I/O 消息发送给指定的线程。另外,还可以用 DMA,内存映射等方式优化 I/O


I/O 多路复用用协程和用线程的区别?

线程是执行程序的最小单位。I/O 多路复用时,会用单个线程处理大量的 I/O。还有一种执行程序的模型,叫协作程,协程是轻量级的线程。操作系统将执行资源分配给了线程,然后再调度线程运行。如果要实现协程,就要利用分配给线程的执行资源,在这之上再创建更小的执行单位。协程不归操作系统调度,协程共享线程的执行资源。

而 I/O 多路复用的意义,是减少线程间的切换成本。因此从设计上,只要是用单个线程处理大量 I/O 工作,线程和协程是一样的,并无区别。如果是单线程处理大量 I/O,使用协程也是依托协程对应线程执行能力。

计网 - 网络 I/O 模型:BIO、NIO 和 AIO 有什么区别?相关推荐

  1. java 中 IO 的流的种类及BIO、NIO、AIO 有什么区别?

    文章目录 java 中 IO 流分为几种? BIO.NIO.AIO 有什么区别? java 中 IO 流分为几种? (1)按流划分,可以分为输入流和输出流: (2)按单位划分,可以分为字节流和字符流: ...

  2. BIO、NIO、AIO 有什么区别?

    BIO.NIO.AIO 有什么区别? BIO:线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成. NIO:线程发起IO请求,立即返回:内核在做好IO操作的准备之后 ...

  3. 内核aio_Java面试题BIO、NIO、AIO有什么区别?

    点击上方"千锋Java学院",选择"置顶公众号" 每天一道面试模拟真题及解析 课前导读 ●回复"每日一练"获取以前的题目,持续更新! ●我希 ...

  4. nio和bio的原理_Java的BIO,NIO和AIO的区别于演进

    前言 Java里面的IO模型种类较多,主要包括BIO,NIO和AIO,每个IO模型都有不一样的地方,那么这些IO模型是如何演变呢,底层的原理又是怎样的呢? 本文我们就来聊聊. BIO BIO全称是Bl ...

  5. BIO,NIO和AIO的区别

    BIO也就是同步阻塞的I/O,由JAVA程序自己处理I/O流操作;一个线程启用I/O请求后,必须等I/O处理完毕才能进行下一步; NIO是同步非阻塞的I/O,还是由JAVA处理应用操作,不过单独开启一 ...

  6. Netty入门之BIO,NIO和AIO编程

    Netty简介 Netty 的介绍 Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github 上的独立项目. Netty 是一个异步的.基于事件驱动的网络应用框架,用以快速开发 ...

  7. 常见Java面试题 BIO、NIO、AIO 有什么区别?

    BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低. NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通 ...

  8. 计网 | 服务器到底是什么?浏览器和Web服务器的区别

    服务器:我是谁 可能有很多网友不清楚,服务器和普通电脑到底有什么区别?对于普通用户而言,确实对服务器感觉很神秘,不知道服务器到底是个什么东东. 首先可以很明确的告诉你,服务器也是电脑,服务器是为电脑提 ...

  9. Java网络编程------IO模型的同步/异步/阻塞/非阻塞(1)

    IO模型的同步/异步/阻塞/非阻塞 一.同步/异步/阻塞/非阻塞 1.同步和异步 2.阻塞和非阻塞 3.同步.异步和阻塞.非阻塞组合 二.IO 1.I/O 2.阻塞IO和非阻塞IO 3.同步IO和同步 ...

最新文章

  1. [转]【 视频 】PAR、DAR和SAR都是啥
  2. C# Windows基础拾遗01—线条绘制篇
  3. Java之相对路径找不到文件问题解决方法
  4. 提升系统 10 倍性能的 10 个建议!
  5. webpack简单配置
  6. Intent Flag介绍 intent.addFlags()
  7. 深度学习 正则化 正则化率_何时以及如何在深度学习中使用正则化
  8. Django+Vue前后端分离项目的部署
  9. Windows操作系统,启动Tomcat之后DOS窗口乱码处理
  10. 走进我的交易室03_第一步成功的外部障碍
  11. 洛谷 P1028 数的计算【递推】
  12. 经验系列之java拦截器获取POST入参导致@RequestBody参数丢失问题解决
  13. aptana手动配置python环境_Python学习1:使用Aptana构建Python开发环境
  14. Python爬取、可视化分析B站大司马视频40W+弹幕
  15. series 合并pandas_在python中pandas的series合并方法
  16. 下载支持包时出现问题。要解决此问题,请参阅相应的 MATLAB Answer
  17. 1.Android稳定性测试
  18. c++期末考试样例(vm)
  19. C++--顺时针打印矩阵
  20. 短视频如何才能涨粉?几个小窍门来帮忙,快速涨粉不是梦

热门文章

  1. cuda grid 和block理解(二)
  2. 修改PostgreSQL数据库默认用户postgres的密码
  3. numpy笔记整理 multivariate_normal(多元正态分布采样)
  4. torch_geometric笔记:数据集 ENZYMES Minibatches
  5. NLP 笔记:Skip-gram
  6. 文巾解题 26. 删除有序数组中的重复项
  7. MATLAB应用实战系列(五十二)-Excel数据的读取
  8. R语言实战应用精讲50篇(三)-多重线性回归系列之模型评估与诊断应用案例
  9. 用Tableau画改进版幂函数柱状图
  10. 影子卫士和影子系统哪个好用_门店管理营销系统哪个好用,营销系统排名