常见相干问题
BIO、NIO和AIO的区别
BIO:一个连贯一个线程,客户端有连贯申请时服务器端就须要启动一个线程进行解决。线程开销大。
伪异步IO:将申请连贯放入线程池,一对多,但线程还是很贵重的资源。
NIO:一个申请一个线程,但客户端发送的连贯申请都会注册到多路复用器上,多路复用器轮询到连贯有I/O申请时才启动一个线程进行解决。
AIO:一个无效申请一个线程,客户端的I/O申请都是由OS先实现了再告诉服务器利用去启动线程进行解决
BIO是面向流的,NIO是面向缓冲区的;BIO的各种流是阻塞的。而NIO是非阻塞的;BIO的Stream是单向的,而NIO的channel是双向的。
NIO的特点:事件驱动模型、单线程解决多任务、非阻塞I/O,I/O读写不再阻塞,而是返回
基于block的传输比基于流的传输更高效、更高级的IO函数zero-copy、IO多路复用大大提高了Java网络应用的可伸缩性和实用性。基于Reactor线程模型。
NIO的组成
Buffer:与Channel进行交互,数据是从Channel读入缓冲区,从缓冲区写入Channel中的
flip办法 : 反转此缓冲区,将position给limit,而后将position置为0,其实就是切换读写模式 clear办法 :革除此缓冲区,将position置为0,把capacity的值给limit。
rewind办法 : 重绕此缓冲区,将position置为0
DirectByteBuffer可缩小一次零碎空间到用户空间的拷贝。但Buffer创立和销毁的老本更高,不可控,通常会用内存池来进步性能。间接缓冲区次要调配给那些易受根底零碎的本机I/O 操作影响的大型、长久的缓冲区。如果数据量比拟小的中小利用状况下,能够思考应用heapBuffer,由JVM进行治理。
Channel:示意 IO 源与指标关上的连贯,是双向的,但不能间接拜访数据,只能与Buffer 进行交互。通过源码可知,FileChannel的read办法和write办法都导致数据复制了两次
Selector可使一个独自的线程治理多个Channel,open办法可创立Selector,register办法向多路复用器器注册通道,能够监听的事件类型:读、写、连贯、accept。注册事件后会产生一个SelectionKey:它示意SelectableChannel 和Selector 之间的注册关系,
wakeup办法:使尚未返回的第一个抉择操作立刻返回,唤醒的起因是:注册了新的channel或者事件;channel敞开,勾销注册;优先级更高的事件触发(如定时器事件),心愿及时处理。 Selector在Linux的实现类是EPollSelectorImpl,委托给EPollArrayWrapper实现,其中三个 native办法是对epoll的封装,而EPollSelectorImpl. implRegister办法,通过调用epoll_ctl向epoll实例中注册事件,还将注册的文件描述符(fd)与SelectionKey的对应关系增加到fdToKey中,这个map保护了文件描述符与SelectionKey的映射。 fdToKey有时会变得十分大,因为注册到Selector上的Channel十分多(百万连贯);过期或生效的Channel没有及时敞开。fdToKey总是串行读取的,而读取是在select办法中进行的,该办法是非线程平安的。
Pipe:两个线程之间的单向数据连贯,数据会被写到sink通道,从source通道读取 NIO的服务端建设过程:Selector.open():关上一个Selector;ServerSocketChannel.open():创立服务端的Channel;bind():绑定到某个端口上。并配置非阻塞模式;register():注册Channel和关注的事件到Selector上;select()轮询拿到曾经就绪的事件
Netty的特点
一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的反对
应用更高效的socket底层,对epoll空轮询引起的cpu占用飙升在外部进行了解决,防止了间接应用NIO的陷阱,简化了NIO的解决形式。
采纳多种decoder/encoder 反对,对TCP粘包/分包进行自动化解决
可应用承受/解决线程池,进步连贯效率,对重连、心跳检测的简略反对
可配置IO线程数、TCP参数, TCP接管和发送缓冲区应用间接内存代替堆内存,通过内存池的形式循环利用ByteBuf
通过援用计数器及时申请开释不再援用的对象,升高了GC频率
应用单线程串行化的形式,高效的Reactor线程模型
大量应用了volitale、应用了CAS和原子类、线程安全类的应用、读写锁的应用
Netty的线程模型
Netty通过Reactor模型基于多路复用器接管并解决用户申请,外部实现了两个线程池,boss线程池和work线程池,其中boss线程池的线程负责解决申请的accept事件,当接管到accept事件的申请时,把对应的socket封装到一个NioSocketChannel中,并交给work线程池,其中work线程池负责申请的read和write事件,由对应的Handler解决。
单线程模型:所有I/O操作都由一个线程实现,即多路复用、事件散发和解决都是在一个Reactor线程上实现的。既要接管客户端的连贯申请,向服务端发动连贯,又要发送/读取申请或应答/响应音讯。一个NIO 线程同时解决成千盈百的链路,性能上无奈撑持,速度慢,若线程进入死循环,整个程序不可用,对于高负载、大并发的利用场景不适合。
多线程模型:有一个NIO 线程(Acceptor) 只负责监听服务端,接管客户端的TCP 连贯申请;NIO 线程池负责网络IO 的操作,即音讯的读取、解码、编码和发送;1 个NIO 线程能够同时解决N 条链路,然而1 个链路只对应1 个NIO 线程,这是为了避免产生并发操作问题。但在并发百万客户端连贯或须要平安认证时,一个Acceptor 线程可能会存在性能有余问题。
主从多线程模型:Acceptor 线程用于绑定监听端口,接管客户端连贯,将SocketChannel 从主线程池的Reactor 线程的多路复用器上移除,从新注册到Sub 线程池的线程上,用于解决I/O 的读写等操作,从而保障mainReactor只负责接入认证、握手等操作;
TCP 粘包/拆包的起因及解决办法
TCP是以流的形式来解决数据,一个残缺的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。
TCP粘包/分包的起因: 应用程序写入的字节大小大于套接字发送缓冲区的大小,会产生拆包景象,而应用程序写入数据小于套接字缓冲区大小,网卡将利用屡次写入的数据发送到网络上,这将会产生粘包景象; 进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将产生拆包 以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。
解决办法
音讯定长:FixedLengthFrameDecoder类 包尾减少特殊字符宰割:行分隔符类:LineBasedFrameDecoder或自定义分隔符类 :DelimiterBasedFrameDecoder 将音讯分为音讯头和音讯体:LengthFieldBasedFrameDecoder类。分为有头部的拆包与粘包、长度字段在前且有头部的拆包与粘包、多扩大头部的拆包与粘包。
Netty的零拷贝实现
Netty的接管和发送ByteBuffer采纳DIRECT BUFFERS,应用堆外间接内存进行Socket读写,不须要进行字节缓冲区的二次拷贝。堆内存多了一次内存拷贝,JVM会将堆内存Buffer拷贝一份到间接内存中,而后才写入Socket中。ByteBuffer由ChannelConfig调配,而ChannelConfig创立ByteBufAllocator默认应用Direct Buffer CompositeByteBuf 类能够将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 防止了传统通过内存拷贝的形式将几个小Buffer合并成一个大的Buffer。addComponents办法将 header 与 body 合并为一个逻辑上的 ByteBuf, 这两个 ByteBuf 在CompositeByteBuf 外部都是独自存在的, CompositeByteBuf 只是逻辑上是一个整体 通过 FileRegion 包装的FileChannel.tranferTo办法 实现文件传输, 能够间接将文件缓冲区的数据发送到指标 Channel,防止了传统通过循环write形式导致的内存拷贝问题。 通过 wrap办法, 咱们能够将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而防止了拷贝操作。
Selector BUG:若Selector的轮询后果为空,也没有wakeup或新音讯解决,则产生空轮询,CPU使用率100%,
Netty的解决办法:对Selector的select操作周期进行统计,每实现一次空的select操作进行一次计数,若在某个周期内间断产生N次空轮询,则触发了epoll死循环bug。重建Selector,判断是否是其余线程发动的重建申请,若不是则将原SocketChannel从旧的Selector上去除注册,从新注册到新的Selector上,并将原来的Selector敞开。