共计 3908 个字符,预计需要花费 10 分钟才能阅读完成。
常见相干问题
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 敞开。