第1章 Java的I/O演进之路
1.1 Linux网络I/O模型
fd:file descriptor,文件描述符。linux内核将全部外部设备都看做一个文件来操做,对文件的读写会调用内核提供的命令,返回一个文件描述符。对一个socket的读写也会有相应的socket fd。描述符就是一个指向内核中结构体的数字。
Unix I/O模型分为5类:
①阻塞IO模型
文件操做的默认模型。进程空间调用recvfrom函数,直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才返回,在此期间,进程会被阻塞一直等待。
②非阻塞IO模型
recvfrom函数当发现内核缓冲区没有数据时,直接返回一个EWOULDBLOCK错误,通常经过轮询检查这个状态,看是否有数据到来。
③IO复用模型
linux提供select/poll,进程经过将一个或多个fd传递给select或poll系统调用,阻塞在select操做上,这样select/poll能够经过顺序扫描多个fd帮咱们侦测是否处于就绪状态。但支持的fd数量有限。
linux还提供了epoll系统调用,基于事件驱动方式代替顺序扫描,性能更高。当有fd就绪时,当即回掉函数rollback。
④信号驱动IO模型
首先开启套接口信号驱动IO功能,并经过系统调用sigaction执行一个非阻塞信号处理函数。当数据准备就绪时,就为该进程生成一个SIGIO信号,经过信号回调通知应用程序调用recvfrom来读取数据,并通知主循环函数处理数据。
⑤异步IO模型
告知内核启动某个操做,并让内核在整个操做完成后(包括将数据从内核复制到用户本身的缓冲区)通知咱们。
这种模型与信号驱动模型的主要区别是:信号驱动IO由内核通知咱们什么时候能够开始一个IO操做;而异步IO模型由内核通知咱们IO操做什么时候已经完成。
1.2 I/O多路复用技术
java NIO 的核心类库多路复用器Selector就是基于epoll的多路复用技术实现。
当须要同时处理多个客户端接入请求时,能够利用多线程或IO多路复用技术实现。
IO多路复用:经过把多个IO的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的状况下也能够同时处理多个客户端请求。
IO多路复用最大优点:系统开销小。即系统不须要建立新的额外进程或线程,也不须要维护这些这些进程和线程的运行,下降了系统的维护工做量,节省了系统资源。
IO多路复用主要应用场景:①服务器须要同时处理多个处于监听或链接状态的套接字。②服务器须要同时处理多种网络协议的套接字。
支持IO多路复用的系统调用有select、pselect、poll、epoll。在linux网络编程中,很长时间使用select,但最终选择epoll。用来克服select/poll缺点的方法不仅有epoll,epoll只是一种linux实现方案,在freeBSD下有kqueue。
epoll改进的select缺点以下:
①支持一个进程打开的socket描述符(FD)不受限制(仅受限于操做系统的最大文件句柄数)。
select最大缺陷是单个进程打开的FD是有限制的,默认值1024个,太多网络效率会降低。而epoll在1G内存大概支持10万个。
②IO效率不会随着FD数目的增长而线性降低。
由于select/poll每次调用都会线性扫描所有的集合,致使效率呈现线性降低。而epoll只有活跃的socket才会主动调用callback函数,epoll实现了一个伪AIO。极端状况当所有socket都活跃时,epoll和select性能差很少。
③使用mmap加速内核与用户空间的消息传递。
内核须要把FD消息通知给用户空间,epoll经过内核和用户空间mmap同一块内存来避免没必要要的内存复制。
④epoll的API更加简单。
包括建立一个epoll描述符、添加监听事件、阻塞等待所监听的事件发生、关闭epoll描述符等。
1.3 Java的I/O演进
在JDK 1.4推出Java NIO以前,java都是使用同步阻塞模式(BIO),而这一时期C和C++语言的大型应用都直接使用操做系统提供的异步IO或AIO能力。
JDK 1.4新增了个java.nio包,提供了进行异步IO开发的API和类库,主要类和接口以下:进行异步IO操做的缓冲区ByteBuffer、管道Pipe、各类异步或同步的通道Channel、实现非阻塞IO操做的多路复用器Selector等。但NIO 1.0版仍然存在不足,主要问题为:没有统一的文件属性、API能力比较弱、底层存储系统的一些高级API没法使用、全部文件操做都是同步阻塞调用,不支持异步文件读写操做。
JDK1.7 对NIO作了升级,被称为NIO 2.0版,主要改进三个方面以下:
①提供批量获取文件属性API。
②提供AIO功能,支持基于文件的异步IO操做和针对网络套接字的异步操做。
③完善通道功能,包括对配置和多播数据报的支持等。
第2章 NIO入门
2.1 传统的BIO编程
ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起链接操做。通过三次握手,链接成功以后,Client和Server双方经过输入流和输出流进行同步阻塞式通讯。
同步阻塞IO服务端通讯模型:称作一个客户端链接一个线程。一般由一个独立的Acceptor线程负责监听客户端的链接,它接收到链接请求后为每一个客户端建立一个新的线程进行链路处理,处理完成以后,经过输出流返回应答给客户端,线程销毁。
该模型最大问题:缺少弹性伸缩能力,服务端线程数和客户端访问数呈1:1的正比关系,当线程数膨胀,性能急剧降低,致使宕机和僵死。
2.2 伪异步IO编程
是对传统BIO一个链接一个线程的简单优化,服务端经过一个线程池来处理多个客户端的请求接入,造成客户端个数M:线程池最大线程数N的比例关系,M能够远远大于N。因为底层依然使用同步阻塞IO,因此被称为“伪异步”。
优势:资源占用是可控的,不会致使耗尽和宕机。
缺点:只是简单优化,没法从根本上解决同步IO致使的通讯线程阻塞问题。
2.3 NIO编程
官方叫法是New I/O。而被大多数人接受的更准确叫法是非阻塞IO(Non-block I/O)。
(1)NIO类库简介
①缓冲区Buffer
缓冲区实质是一个数组,提供对数据的结构化访问以及维护读写位置等。全部数据都是用缓冲区处理,任什么时候候访问NIO中的数据,都是经过缓冲区进行操做。常见缓冲区类有7个:ByteBuffer(最经常使用)、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。而java bio是面向流操做的。
②通道Channel
Channel是一个全双工的双向通道,能够读写操做同时进行,能更好的映射底层操做系统的API,由于Unix底层操做系统通道都是全双工的。而java bio 流是单向的,一个流必须是InputStream或OutputString的子类。
Channel能够分为两大类:用于网络多写的SelectableChannel和用于文件操做的FileChannel。ServerSocketChannel和SocketChannel都是SelectableChannel的子类。
③多路复用器Selector
多路复用器提供选择以及就绪的任务的能力。Selector会不断地轮询注册在其上地Channel,若是某个Channel上面发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,而后经过SelectionKey能够获取就绪Channel的集合,进行后续的IO操做。JDK使用epoll实现。
(2)NIO编程优缺点
缺点:NIO编程难度比BIO大很大,编码复杂。
优势:①客户端发起的链接操做都是异步的,经过在多路复用器注册OP_CONNECT等待后续结果。
②SocketChannel的读写操做都是异步的。
③线程模型的优化,一个Selector线程能够同时处理成千上万个客户端链接。
2.4 AIO编程
JDK1.7(NIO 2.0)引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现,是真正的异步IO(所以NIO2.0也称做异步非阻塞IO,而NIO 1.0称做非阻塞IO)。其中异步套接字通道是真正的异步非阻塞IO,对应于Unix网络编程中的事件驱动IO(AIO)。它不须要经过多路复用器Selector对注册的通道进行轮询操做便可实现异步读写,从而简化了NIO的编程模型。主要类有AsynchronousSocketChannel和CompletionHandler(异步操做回调通知接口)。
2.5 四种IO模型对比
第3章 TCP粘包和拆包
3.1 TCP粘包/拆包
TCP是个“流”协议,所谓流,就是没有界限的一串数据。就像河里的流水,它们连成一片,其间并无分界线。
粘包:上层业务的多个小包被封装成一个大的数据包发送。
拆包:上层业务的一个完整的包被拆分红多个数据包发送。
3.2 TCP粘包/拆包解决方案
主要有四种解决方案:
①消息定长。例如每一个报文的大小为固定长度200字节,若是不够,空位补空格。
②在包尾增长回车换行符进行分割。例如FTP协议。
③将消息分为消息头和消息体,消息头中包含表示消息总长度的字段。
④更复杂的应用层协议。
为了解决TCP粘包/拆包致使的半包读写问题,Netty默认提供了多种编解码器用于处理半包。
第4章 编解码技术
4.1 Java序列化缺点
java序列化缺点以下:
①没法跨语言
②序列化后的码流太大
③序列化性能过低
4.2 业界主流编解码框架
Protobuf:google开源,高效的编解码性能,语言无关和平台无关,文本化的数据结构描述语言。
Thrift:facebook开源给apache,支持压缩和可选优化等二进制编解码。
第5章 HTTP协议
5.1 HTTP请求消息
http请求由三部分组成:
①HTTP请求行
请求行以一个方法夫开头,以空格分开,后面跟着URI和协议版本,格式为:Method Request-URI HTTP-Version CRLF(回车和换行)。
②HTTP消息头
③HTTP请求正文
5.2 HTTP响应消息
http响应由三部分组成:
①状态行
状态行格式:HTTP-Version Status-Code(状态码) Reason-Phrase CRLF。
②消息报头
③响应正文
5.3 HTTP协议缺点
http协议缺点以下:
①HTTP协议为半双工协议。
半双工协议指数据能够在客户端和服务器两个方向传输,可是不能同时传输。即同一时刻,只有一个方向上的数据传送。
②HTTP消息冗长而繁琐。
采用文本方式传输,比二进制通讯协议冗长繁琐。
③开销大,不适用于低延迟应用。
容易针对服务器长链接推送的黑客攻击。例如长时间轮询,消耗大量服务器带宽。
第6章 WebSocket协议
6.1 WebSocket入门
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网路技术,浏览器和服务器只须要作一个握手动做,而后,浏览器和服务器之间就造成了一条快速通道,二者就能够直接互相传送数据了。
WebSocket特色以下:
①单一的TCP链接,基于TCP的全双工模式通讯。
②对代理、防火墙和路由器透明。
③无头部信息、Cookie和身份验证。
④无安全开销。
⑤经过“ping/pong”帧保持链路激活。
⑥服务器能够主动传递消息给客户端,再也不须要客户端轮询。
第7章 私有协议栈开发
7.1 私有协议介绍
私有协议也称非标准协议,就是未经国际和国家标准化组织采纳和批准,由某个企业本身制定。
7.2 Netty协议栈开发
Netty私有协议栈开发主要有以下步骤:
①数据结构定义
②消息编解码
③握手和安全认证
④心跳检测机制
⑤断连重连
⑥客户端代码
⑦服务端代码
第8章 Reactor线程模型
8.1 Reactor单线程模型
是指全部的IO操做都在同一个NIO线程上完成,这个惟一NIO线程的职责以下:
①作为NIO服务器,接收客户端的TCP链接。
②作为NIO客户端,向服务器发起TCP链接。
③读取通讯对端的请求或者应答消息。
④向通讯对端发送消息或者应答消息。
经过Acceptor类接收客户端的TCP链接请求消息,当链路创建成功以后,经过Dispatch将对应的ByteBuffer派发到指定的Handler上,进行消息解码,用户线程消息编码后经过NIO线程将消息发送给客户端。
单线程模型只适用于小容量应用场景,对于高负载、大并发应用场景不适用,缘由以下:
①一个NIO线程同时处理成百上千的链路,性能上没法支撑,没法知足海量消息的编码、解码、读取和发送。
②当NIO线程负载太重时,处理速度变慢,会致使大量客户端链接超时,而超时重发会进一步加剧负载。
③可靠性问题,一旦NIO线程崩溃或死循环,会致使整个系统通讯模块不可用。
8.2 Reactor多线程模型
用一组NIO线程来处理IO操做。多线程模型特色以下:
①有专门一个NIO线程Acceptor线程用于监听服务端,接收客户端的TCP链接请求。
②网路IO操做读写等由一个NIO线程池负责。
不足:单独一个Acceptor线程可能会存在性能不足问题。
8.3 主从Reactor多线程模型
用一个Acceptor线程池来处理客户端TCP链接请求,握手,安全认证等。一旦链路创建成功,就将链路注册到后端Reactor线程池的IO线程上,由IO线程负责后续IO操做。
第9章 高性能之道
9.1 传统RPC调用性能分析
传统RPC调用性能差的三宗罪:
①网络传输方式采用同步阻塞I/O。
②序列化性能差。
③线程模型问题。即1:1占用服务器线程资源。
9.2 IO通讯性能三原则
从架构层面,影响IO通讯性能主要有三个要素:传输方式、通讯协议、线程模型。
9.3 Netty高性能之道
netty高性能之道以下:
①异步非阻塞通讯。
②高效的Reactor线程模型。
③无锁化的串行设计。
避免锁竞争带来的性能损耗。
④高效的并发编程。
⑤高性能的序列框架。
netty默认提供了对google Protobuf的支持。
⑥零拷贝。
netty的接收和发送byteBuffer采用Direct Buffer,使用堆外直接内存进行socket读写,不须要进行缓冲区二次拷贝。
⑦内存池。
netty提供了基于内存池的缓冲区重用机制,来进行对象的分配和回收。
⑧灵活的TCP参数配置能力。