IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)
有热心的网友加我微信,时不时问我一些技术的或者学习技术的问题。有时候我回微信的时候都是半夜了。但是我很乐意解答他们的问题。因为这些年轻人都是很有上进心的,所以在我心里他们就是很优秀的,我愿意多和努力的人交朋友。我原来拿老公高中时复读过一年来开过玩笑。他却很平和而骄傲的回复说:“我是为了等你。” 眼里有一种赚翻了的表情。虽然我很感激我婆婆给了个好老公,但是生气的一点是婆婆从小说我老公脑子笨。我总跟老公说:“就是因为妈从小这么说你,你才从原本应该是天之骄子沦为一个苦逼程序员的。”但是毕业十年,他一直很努力,也很顾家。所以在我眼里他很优秀。有网友问我是怎样成长为技术大牛的。我回复说:“额~,你可不可以不问这么笼统的问题,都不知道怎么回答你。”过了几天他又问我:“你有没有看过的东西当时理解了然后又忘了。”我当时只是告诉他忘了就多看几遍,反复的看。但是他问的这个问题我一直都没有忘,我一直在想自己是怎么做的。因为通过第一个问题到第二个问题,可以看到他是很努力的在思考我说的话,对于认真的人,咱们也得认真。
本文有两条主线:一条是学习方法,怎样学过就能记住。另一条是实际项目中的IO处理问题,包括BIO,NIO,AIO,netty。旨在用学习具体知识的具体流程体现学习方法的形成过程。
对于技术的东西,我是这么过目不忘的(上篇文章已经提过,但是LZ觉得有必要强化一下,另外还加了一点):
☆ 积极的做项目
☆ 做过的东西反复琢磨
☆ 看完一本书后要再次梳理项目
☆ 手别懒,看到例子没一眼看明白就要动手实践
☆ 记录项目中潜在的问题每隔一段时间回来想一想
☆ 工匠精神:代码当成艺术品反复优化
☆ 沉住气把项目做深
☆ 好书要每隔一段时间就再看一遍
我在好几篇博客中都提到一个离线数据的小项目。因为这个项目我只有我一个人开发维护,所以我可以随时将可以优化并且放到线上,很有成就感。我做的是视频和音频部分,音频和音频的专辑是另一个小伙子做的,他当初把我的代码拷贝过去,因为数据量比我这边小很多,一直也没出什么问题,直到有一天他拿着代码来问我一个问题,我很无奈的说:“你可不可以拷贝一个新一点儿的版本,我都改的完全不是这个样子了。”
离线数据里有一个万一实时消息发送有问题,手动补发消息的接口,我直接用socket同步阻塞的方式(BIO)接收浏览器作为客户端,一直也没出什么问题,但是做过的东西要反复琢磨,所以我又分别使用了NIO,AIO,netty进行了尝试性改造。出于信息安全的考虑,本文不附带源码,简单例子可参考<实战Java高并发程序设计>第五章和<netty in action>。
由于这个手动服务一次也就是一两个人调用,比较改造后的性能,顶多看看浏览器里的请求响应时间,误差很大。对于这个最简单的RPC请求没什么可说的,最终都实现了,但是使用BIO,NIO,AIO由于返回响应我直接write到socket或者buffer里,没用什么工具,结果显示有浏览器兼容问题。netty由于内置了http协议的实现,不存在此问题。这里只比较原理的区别。
对于一个服务器端业务的开发者来说,如果这四者选,肯定要选netty。netty的jar包1MB多点,要嵌入手机啥的就算了,这是通信用的,放在客户端基本也没啥应用场景。
☆ netty使用简单,预置多种编解码器,支持多种主流协议。就像刚才说的,直接使用java api,我需要自己将read到的GET xxxx HTTP1.1 XXXXX这些数据自己解析出感兴趣的数据,返回客户端还要自己封装一个浏览器可读的响应。
☆ netty可定制,通过channelhander对通信框架进行灵活扩展。
☆ netty性能好,社区活跃,版本迭代周期短。
首先简单提一下概念。
BIO:同步阻塞式IO,服务器端与客户端三次握手简历连接后一个链路建立一个线程进行面向流的通信。这曾是jdk1.4前的唯一选择。在任何一端出现网络性能问题时都影响另一端,无法满足高并发高性能的需求。
NIO:同步非阻塞IO,以块的方式处理数据。采用多路复用Reactor模式。JDK1.4时引入。
AIO:异步非阻塞IO,基于unix事件驱动,不需要多路复用器对注册通道进行轮询,采用Proactor设计模式。JDK1.7时引入。
Netty是实现了NIO的一个流行框架,JBoss的。Apache的同类产品叫Mina。阿里云分布式文件系统TFS里用的就是Mina。我目前的项目中使用了netty作为底层实现的有阿里云的dubbo和ElasticSearch。我之所有要研究这个东西也是因为我要实现自己的搜索引擎先要调研已存在的产品。我之前项目中用过Solr,个人比较倾向于Solr。但是大家做了很多性能比较,ElasticSearch的并发能力确实要比Solr,究其原因,就是因为Solr底层还是用的Servlet容器,而ElasticSearch底层用的是Netty。
有人问:单看实现原理,显然AIO要比NIO高级,为什么Netty底层用NIO? Netty也曾经做过一些AIO的尝试性版本,但是性能效果不理想。AIO理念很好,但是有赖于底层操作系统的支持,操作系统目前的实现并没听上去那么有吸引力,是一匹刚孕育出来的黑马。其实AIO的性能上不去,也很好感性的理解。NIO相当于餐做好了自己去取,AIO相当于送餐上门。要吃饭的人是百万,千万级的,送餐员也就几百人。所以一般要吃到饭,是自己去取快呢,还是等着送的更快?目前的外卖流程不是很完善,所以时间上没想的那么靠谱,但是有优化空间,这就是AIO。
关于NIO的内部实现,大家可以参考我写的IO和socket编程。里面没有提到操作系统方面对于多路复用技术的三种常用机制:select,poll和epoll。三个的作用都是指示内核等待多个事件中的任何一个发生的时候或一定时间滞后被唤醒。区别是select函数文件描述符的数量有限制,poll函数没限制,epoll函数用一个描述符来管理多个描述符。
关于文件描述符,也就是句柄,想起一件事:好几年前用Solr做高并发压测的时候,我用了一台内存所剩很小的服务器做测试,出现了NIO超出句柄数错误。而查看了系统的最大句柄数设置,设置的很大,不可能用完。换了一台好点的服务器就没了这个问题。那是因为内存不够的时候,每次都要打开磁盘文件的数据进行搜索,而内存大点儿都走内存中的缓存了。内存数据也有句柄,但是访问磁盘速度慢,资源长时间不释放,新的请求又过来了,才是句柄超出的原因。
关于epoll,不得不提臭名昭著的epoll bug。它会导致Selector空轮询,最终导致CPU 100%。
关于多路复用,多唠叨两句。多路指的是多个网络连接,复用是复用同一个线程。目前解决各种并发问题的最大利器就是缓存。我用过Memecached和Redis。Memcached采用的是多线程,非阻塞IO复用的网络模型。Redis采用的是单线程的IO复用模型。既然单线程会严重影响整体吞吐量,因为CPU计算过程中,整个IO调用都是被阻塞的。那么为什么实际上大家普遍表示Redis的性能要优于Memcached?多线程模型可以发挥多核作用,但是引入了缓存一致性和锁的问题,带来了性能损耗。单线程可以将速度优势发挥到最大。想用多核,可以多开几个进程的嘛。话说nginx也是这样多进程单线程的模型。Netty可以定制,可以单线程IO复用,多线程IO复用,主从多线程IO复用。
Netty逻辑架构(从底层向上介绍)
1> Reactor通信调度层,由一系列辅助类组成,包括Reactor线程NioEventLoop以及其弗雷,NioSocketChannel以及弗雷,ByteBuffer及衍生Buffer,Unsafe及内部子类。
2> 职责链ChannelPipeLine,它负责调度事件在职责链中的传播,支持动态的编排职责链,职责链可以选择性的拦截自己关心的事件,对其它IO操作和事件忽略。
3> 业务逻辑编排层。
Netty实现上优势点:
☆ 零拷贝,又叫内存零拷贝,指CPU不需要为数据在内存之间的拷贝消耗资源。通常指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间而直接在内核空间中传输到网络的方式。
Netty的ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果大家自己动手写过NIO和AIO的程序,就会知道我们接触的网络传输数据是直接和ByteBuffer打交道的。在JAVA的API中,一个ByteBuffer读完数据后要flip一下,将当前操作位置设置为0.<Netty In Action>里详细介绍了Netty的ByteBuf怎样将这个缓冲区分成一段一段的,还可以压缩,将读的数据滑向一侧。而堆外内存的零拷贝,如果有JVM基础也很好理解。Java内存模型里提到每个线程都有自己的高速工作内存空间,而不是直接访问主内存。想不用工作内存,直接主内存可见,就要用volatile关键字修饰。所以堆内内存,走JVM就存在这个拷贝开销。
Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,而不用将几个小Buffer通过内存拷贝合并成一个大的Buffer.
Netty的文件传出采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。其实这个功能不是Netty特有的,Linux的sendfile函数和Java NIO的FileChannel的transferTo方法都实现了零拷贝,而Netty也通过FileRegion包装了NIO的transferTo方法。
☆ 内存池,刚才提到堆外内存可以实现零拷贝,那为啥java要设置自己的工作内存呢?快啊。堆外直接内存的分配和回收是一个非常 耗时的操作,有C或者C++基础的应该都能理解。刚才提到的ByteBuf就是采用内存池的,用来对缓存区复用。
基本一个比较底层的框架都会有自己特有的内存池技术。比如Memcached使用预分配的内存池方式,使用slab和带下不同的Chunk来管理内存。这样避免了上面提到的内存分配和回收的开销问题。
☆ 无锁化的串行设计。我在自己的博客中提到自己做项目会把业务划分的很清楚,尽量少的采用线程间通信。这就是一种无锁化的串行设计思想。共享资源的并发访问处理不当,会带来严重的锁竞争,最终会导致性能下降。
☆ 高效的并发编程。有些人在面试时很吃亏(老公,当你看到这篇文章的时候,说的就是你)。CPU直接操作磁盘型的。没有内存,很多东西临场发挥一时加载不上来。说是吃亏究其原因是总结的少,没建立好强大的索引。而好的程序员是体现在细节中的,为什么一说原理怎么看怎么觉得Memcached比Redis先进。而Mina的实现原理上也没有多少劣于Netty的地方。就是因为后者的开发者更为勤奋。比如CAS操作要比加锁性能更好,但是开发维护上要繁琐很多。
不论电影,电视,武侠小说还是实际生活中, 智商高的人最终会被情商高的人打败。精诚所至金石为开是真理。爱一个人会打开心灵的通道,不着一字便可沟通。我在你心底,你就会用我的方式去思考。技术的书,技术的文章,代码后面都隐藏着高人。爱一种技术,多看多想,技术能力和境界都能得到升华。
IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)的更多相关文章
- I/O模型系列之三:IO通信模型BIO NIO AIO
一.传统的BIO 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请 ...
- [转]BIO/NIO/AIO的几个思考
原文:https://www.jianshu.com/p/ff29e028af07 ----------------------------------------------------- BIO/ ...
- Java提供了哪些IO方式?IO, BIO, NIO, AIO是什么?
IO一直是软件开发中的核心部分之一,而随着互联网技术的提高,IO的重要性也越来越重.纵观开发界,能够巧妙运用IO,不但对于公司,而且对于开发人员都非常的重要.Java的IO机制也是一直在不断的完善,以 ...
- (转)也谈BIO | NIO | AIO (Java版)
原文地址: https://my.oschina.net/bluesky0leon/blog/132361 关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一 ...
- 拿搬东西来解释udp tcpip bio nio aio aio异步
[群主]雷欧纳德简单理解 tcpip是有通信确认的面对面通信 有打招呼的过程 有建立通道的过程 有保持通道的确认 有具体传输udp是看到对面的人好像在对面等你 就往对面扔东西[群主]雷欧 ...
- 也谈BIO | NIO | AIO (Java版--转)
关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...
- Netty5序章之BIO NIO AIO演变
Netty5序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使 ...
- 【netty】(1)---BIO NIO AIO演变
BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用的技术. Net ...
- Netty序章之BIO NIO AIO演变
Netty序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用 ...
随机推荐
- Adroid学习之 从源码角度分析-禁止使用回退按钮方案
有时候,不能让用户进行回退操作,如何处理? 查看返回键触发了哪些方法.在打开程序后把这个方法禁止了. 问题:程序在后台驻留,这样就会出现,其他时候也不能使用回退按钮.如何处理,在onpase()时方法 ...
- Java排序算法之堆排序
堆的概念: 堆是一种完全二叉树,非叶子结点 i 要满足key[i]>key[i+1]&&key[i]>key[i+2](最大堆) 或者 key[i]<key[i+1] ...
- 机器学习-scikit learn学习笔记
scikit-learn官网:http://scikit-learn.org/stable/ 通常情况下,一个学习问题会包含一组学习样本数据,计算机通过对样本数据的学习,尝试对未知数据进行预测. 学习 ...
- 迭代的是人,递归的是神。——L. Peter Deutsch
递归,数学里面叫recursion,其实就是递推关系. 中学数学有一部分其实就是递归的非常典型的做法,不过老师们都没怎么扩展,新课标必修五第二章数列应该算是我们第一次接触递推的概念了. 其实说到递归 ...
- MySQL关于check约束无效的解决办法
首先看下面这段MySQL的操作,我新建了一个含有a和b的表,其中a用check约束必须大于0,然而我插入了一条(-2,1,1)的数据,其中a=-2,也是成功插入的. 所以MySQL只是check,但是 ...
- 如何了解您的微软认证情况和MIC ID
- 谁用光了磁盘?Docker System命令详解
译者按: Docker镜像,容器,数据卷以及网络都会占用主机的磁盘空间,这样的话,磁盘很容易就会被用完.这篇博客介绍了一个简单的解决方案 - Docker System命令. 原文: What's e ...
- Android -- 带你从源码角度领悟Dagger2入门到放弃(二)
1,接着我们上一篇继续介绍,在上一篇我们介绍了简单的@Inject和@Component的结合使用,现在我们继续以老师和学生的例子,我们知道学生上课的时候都会有书籍来辅助听课,先来看看我们之前的Stu ...
- JavaScript中screen对象的两个属性
Screen 对象 Screen 对象包含有关客户端显示屏幕的信息. 这里说一下今天用到的两个属性:availHeigth,availWidth avaiHeigth返回显示屏幕的高度 (除 Wind ...
- 消息队列NetMQ 原理分析3-命令产生/处理和回收线程
消息队列NetMQ 原理分析3-命令产生/处理和回收线程 前言 介绍 目的 命令 命令结构 命令产生 命令处理 创建Socket(SocketBase) 创建连接 创建绑定 回收线程 释放Socket ...