BIO到NIO的演变过程
时间:2022-07-23
本文章向大家介绍BIO到NIO的演变过程,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
背景
学习任何东西之前都得知道他是为什么而产生的。任何一个设计,或技术。都是为了解决某个或多个问题而产生的。即BIO到NIO到多路复用再到epollo 再到netty网络编程框架。今天我们来看看这个演进的过程。
IO的演进
下图是来自培训机构的一个大佬的图:
什么是IO?
- 我自己的理解:从本地磁盘或者网络传输过来的数据的读取和写入的过程。也就是从网卡或者磁盘的数据到应用程序内部(JVM的动态内存中)的过程。这个过程是相当耗费时间的,看看下面的数据。随机访问相差10万倍。在性价比的驱动下,优化硬件的成本远远比优化软件这方面的成本高的时候或者还有其他原因的时候那么就去优化一下我们的访问方式。
1.顺序访问:这种情况下,内存访问速度仅仅是硬盘访问速度的6~7倍(358.2M / 53.2M = 6.7)
2.随机访问:这种情况下,内存访问速度就要比硬盘访问速度快上10万倍以上 (36.7M / 316 = 113,924)
在看各个IO之前先理解一下这个过程linux是如何实现的: 在linux系统中一切皆文件。在 系统中启动一个服务的时候,我们会为其定义端口号代表这个服务,启动成功后linux就会创建一个监听,每接入一个clinet就会被我们的服务监听到,然后去内核拿这个数据。 重要的就是图中的fd(文件描述符)和内核交互的过程中就是根据linux系统中的这个文件描述符来确定是那个客户端的。
1.BIO
- BIO也就是阻塞IO的意思,这里的阻塞是指的应用程序监听到服务器有端口连接进来,在与内核进行交互的时候是阻塞的。看下面的图理解一下:
如图上红色线的部分。我们监听到有个client进来的时候,于是我们起一个线程去请求是否有数据进来,此时这个阶段是阻塞的,没有数据返回就会一直等待。当又有新的客户端进来的时候我们只能再起新的进程了。那就使用多线程的方式再起新的线程去请求。
- 毫无疑问,这是有问题的,我们都知道创建一个线程会耗费内存的。所以创建多个线程有可能会产生OOM的。
- 有人就说了我们起一个线程池,搞一个没有核心线程数的线程池。只有最大线程数,然后设置过期时间,是个解决办法。但是不合理。同时涌入上万甚至更多得用户,肯定得蹦
- 那搞一个非阻塞的线程呗。 在JDK1.4 NIO登场。
2.NIO
- 从上文中可知,NIO解决的问题之一是不让创建多个线程。那NIO的设计这给出的办法就是让和内核交互的这个过程中是非阻塞的。不管他有没有数据进来请求后就会返回。 2. 但是得一直轮训接入的客户端的文件描述符,查看是否有数据进来。看下图品一下:
- 从上面可以看出,解决了之前必须创建多个线程的问题。现在我们再看看在这个模型下会又有什么问题呢?for循环便利那岂不是很耗费CPU资源啊,当我们有N多个客户端进来给你CPU打满,那其他的client也不就是死。所以这又出现问题了。
- 于是又有大佬站出来了,提出了IO的多路复用原则。
3. NIO+多路复用
- 由于在NIO中是非阻塞的,现在主要问题是我们并不知道哪些客户端会有数据过来,我们得遍历所有客户端的来查看是否有数据,那就是我们如何才能让服务程序能知道哪些客户端有数据了,需要去读取。大佬在内核实现了selector,epoll ,reactor等多路复用的线程模型。其中的思想就是可以通过他们提供的方法可以拿出就绪有数据过来的客户端fd。
总结
大概是过了一下从BIO到NIO+IO多路复用的演进(IO模型的演进)
- BIO 遇到创建多个线程有可能导致OOM,对系统的并发能力有极大限制
- NIO,导致CPU空转的,导致CPU做无用功。
- NIO+多路复用以及已有实现的NIO+多路复用的模型
思考,selector和epoll是如何实现可以知道有客户端的数据进来了,且通知应用服务去读区数据呢?
资源:
- JavaNIO的具体实现 :https://juejin.im/entry/599f971af265da247d728531
- 本文参考视频:https://www.bilibili.com/video/BV1cT4y1g79r?from=search&seid=2733076814654841327
- linux 系统监控、诊断工具之 IO wait
- 关于 MySQL UTF8 编码下生僻字符插入失败/假死问题的分析
- Windows用户自查:微软紧急更新修复Meltdown和Spectre CPU漏洞
- rsync error: protocol incompatibility / mismatch
- 玩转 SHELL 脚本之:Shell 命令 Buffer 知多少?
- 使用实体嵌入的结构化数据进行深度学习
- CoffeeMiner:劫持WiFi网络接入设备进行“挖矿”的框架
- 数码管显示电路的Verilog HDL 实现
- 超前进位加法器
- 使用腾讯云“自定义监控”监控 GPU 使用率
- 非整数分频模块
- 偶数倍频
- Fiddler 高级用法:Fiddler Script 与 HTTP 断点调试
- Spark 伪分布式 & 全分布式 安装指南
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 在 RHEL8 /CentOS8 上建立多节点 Elastic stack 集群的方法
- linux 搭建svn服务器的方法步骤
- linux 下隐藏进程的一种方法及遇到的坑
- Ubuntu 18.04中截图工具shutter的编辑按钮不可用的解决办法
- Linux服务器利用防火墙iptables策略进行端口跳转的方法
- Linux下PHP网站服务器安全配置加固防护方法【推荐】
- CentOS8 yum/dnf 配置国内源的方法
- 浅析在 RHEL8 配置静态 IP 地址的不同方法
- ubuntu16.04自动设置行号的步骤详解
- CentOS 8安装ZABBIX4.4的指南
- Linux 3.X/4.x/5.x 忘记宝塔面板密码的解决方法
- Linux中grep和egrep命令详解
- centos8 使用yum 安装 mongodb 4.2的方法
- 在Linux环境下采用压缩包方式安装JDK 13的方法
- Linux下强制杀死进程的方法详解