BIO:
一个请求一个线程,线程开销大。
伪异步:
将请求连接放入线程池,一对多。
NIO:
一个请求一个线程,但客户端发送的请求都会注册到多路复用器上,多路复用器轮询到连接有io请求时才启动一个线程进行处理。
AIO:
一个有效请求一个线程,客户端的io请求都是由os先完成后再通知服务器应用去启动线程进行处理。
BIO的stream是单向的,阻塞的,NIO的channel是双向的是面向缓冲区的,非阻塞的。
NIO特点:
事件驱动模型、单线程处理多任务、非阻塞io、io操作不再阻塞,而是返回0、基于block的传输比基于stream的传输更高效。io多路复用大大的提高了java网络应用的可伸缩性和实用性。基于Reactor线程实现。
在Reactor模式中,事件分发器等待某个事件或者可应用或可操作的状态发生,时间分发器就把这个时间传给实现注册好的时间处理函数或回调函数,有后者做实际的读写操作。如在Reactor中实现读:注册读就绪时间和相应的事件处理器,事件分发器等等待事件到来。事件到来,激活分发器,分发器调用事件对应的处理器、事件处理器完成实际的读操作,处理读到的数据,注册新的时间,然后返还控制权。
Buffer:
与channel进行交互,数据从channel读入缓冲区,从缓冲区写入channel中的方法。filp():反转缓冲区,将posttion给limit,然后将position置位0。clear()清除缓冲区,将position置位0,将capacity传给limit。rewind()将position位置设置为0
DirectByteBuffer:可减少一次系统空间到用户空间的拷贝。但Buffer的创建和销毁成本很高,不可控。可以采用线程池来提高性能。直接缓冲区主要分配给那些易受基础系统的本机io操作影响的大型持久缓冲区。如果数据量比较小的中小应用情况下,可以考虑heapBuffer,有jvm管理。
channel:表示io源与目标打开的连接,双向,但不可直接访问数据,只能有buffer进行交互。
selector:可使用单独线程管理多个channel、Selector.open()方法可创建Selector。register方法向多路复用器注册通道。可以监听的时间类型:读写连接accept。注册时间后会产生一个SelectionKey。
SelectionKey: 表示SelectableChannel和Selector之间的注册关系。wakeup方法:使尚未返回的第一个选择操作立即返回,唤醒的原因是:注册了新的channel或者时间;channel关闭,取消注册;优先级更高的时间触发。
Pipe:两个线程之间的单向数据连接,数据会被写到sink通道,从source通道读取。
NIO服务端建立过程:
Selector.Open():打开一个Selector;ServerSocketChannel.open():创建服务端的channel;bind():绑定到某个端口,并配置非阻塞模式;register():注册channel和关注事件到Selector上;select()轮询拿到已经就绪的事件。
高性能、异步事件驱动的NIO框架,提供了对TCP/UDP和文件传输的支持
使用更高效的Socket底层,对epoll空轮询引起的cpu飙升在内部进行了处理,避免了直接使用nio的陷阱,简化了nio的处理方式。
采用encoder、decoder支持
可使用接受、处理线程池提高连接效率,对重连和心跳检测都有支持。
可配置io线程数,tcp参数,tcp接受和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用bytebuf
通过引用计数器集市申请释放不再引用的对象,降低了GC频率
通过单线程串行化的方式,高效的reactor线程模型
大量使用volitale、cas和原子类、线程安全类的使用和读写锁的使用
使用Reactor模型基于多路复用器接受并处理用户请求,内部实现了两个线程池,boss线程池和worker线程池、其中boss线程池的线程负责处理请求的accept事件,将接受到的accept事件的对应的socket封装到一个nioSocketChannel中,并交给work线程池,其中worker线程池负责处理read和write事件,由对应的handler处理。
单线程模型:所有io操作由一个线程完成,也就是多路复用、时间分发、处理都有在一个reactor线程上单独完成的。既要接受客户端的连接请求、向服务端发起连接。也要发送、读取请求或应答、响应消息。一个Nio线程同时处理成百上千的链路,性能上无法支撑,速度慢。若线程进入死循环,整个程序将不可用,对于高负载大并发的应用场景不合适。
多线程模型:有一个Nio线程acceptor只负责监听服务端,接受tcp连接请求;nio连接池负责网络io的操作,也就是消息的接受解码编码和发送;一个nio线程可以同时处理n条链路,但是一个链路只对应一个nio线程,为了避免并发操作问题的发生。
主从线程模型:acceptor线程用于绑定监听端口,接受客户端连接,将SocketChannel从住线程池的Reactor线程的多路复用器上移除,重新注册到sub线程池的线程上,用于处理io的读写等操作,从而保证mainReactor只负责认证和握手操作。
序列化是将对象转换为二进制编码(字节数据),主要用于网络传输、数据持久化等;而反序列化则是从网络或磁盘读取字节数组后还原成原对象,主要用于网络传输对象的解码,以便完成远程调用。
影响序列化性能的关键因素有:序列化后的码流大小、序列化的性能(cpu占用)、是否跨语言
无法跨语言、序列化后码流大、序列化的性能差
优点
人机可读性性好,可以指定元素的特性和名称。
缺点
序列化的数据只包含数据本身以及类的结构,不包含类型表示和程序集信息。只能系列化公共属性和字段,无法序列化方法;文件庞大,文件格式复杂
优点
兼容性高,数据格式简单,易于读写,序列化后数据较小,可扩展性好,兼容性好
缺点
数据描述性较差。
接口简单易用,是java中最快的json库
不仅是序列化协议,还是一个rpc框架。
优点
序列化后体积小、速度快、支持多种语言和丰富的数据类型
缺点
跨防火墙访问时,不安全,不具可读性,不可与其他传输层协议如http协议搭配使用。无法持久化数据。
将数据以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的pojo对象和protobuf相关的属性和方法。
优点
序列化后码流小,性能高,结构化数据存储格式,通过标识字段的顺序可以实现协议的前向兼容,结构化的文档更容易管理和维护。
缺点
需要依赖于工具生成代码
试用场景
对性能要求高的RPC调用,具有良好的跨防火墙的访问属性,适合应用层对象的持久化。
bool/double/float/int32/int64/String/bytes/enum/message
限定符:required:必须赋值,不可为空 optional:非必须赋值可以为空 repeated:可以重复任意次数,包含0次 枚举:只能用指定的常量集中的一个值作为其值
每个消息都必须有一个required字段,包含0或多个optional字段;repeated标识的字段可以包含0或多个数据;[1,15]之内的标识号在编码的时候都会占用一个字节,[16,2047]之内的标识号则占用两个字节,标识号一定不能重复、使用消息类型也可以将消息嵌套任意多层、可用嵌套消息类型来代替组。
不要更改任意已有字段的数值标识;不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但保留标识号不可被重用。新增的字段必须是optional或repeated。因为旧版本无法读取和写入新增的required限定符的字段。
netty的接受和发送bytebuffer采用direct buffers,使用堆外直接内存进行socket读写,不需要进行字节缓冲区的二次拷贝。堆内存多了一次内存拷贝,jvm会将堆内存buffer拷贝一份到直接内存中,然后再写入socket中。bytebuffer由channelConfig分配,而channelConfig创建ByteBufAllocator默认使用Direct Buffer。
心跳:
对服务端:定时清除限制会话inactive
对客户端:用来检测会话是否断开,是否重来,检测网络延迟,其中idleStateHandler类用来检测会话状态。
串行无锁化设计:
也就说是消息的处理尽可能在同一个线程内完成,期间不可切换线程,这样就避免了多线程的竞争和同步锁。表面上,串行设计对cpu利用率不高,并发不够。但是通过调整nio线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。
可靠性:
链路的有效性检测:链路空闲检测机制,读写空闲超时机制。 内存保护机制:通过内存池冲用ByteBuf
byteBuf的解码保护,优雅停机,不再接受新消息,推出前的预处理操作、资源释放操作。
安全性:支持ssl单向认证,双向认证和第三方ca认证。
高并发编程的体现:
volatile的大量、正确使用;cas和院子类的广泛使用;线程安全容器的使用;通过读写锁提升并发性能;
io通信三原则: 传输Aio、协议http、线程(主从多线程)