1.Netty 是什么?

Netty是 一个异步事件驱动的网络应用程序框架,用于疾速开发可保护的高性能协定服务器和客户端。Netty是基于nio的,它封装了jdk的nio,让咱们应用起来更加办法灵便。

2.Netty 的特点是什么?

  • 高并发:Netty 是一款基于 NIO(Nonblocking IO,非阻塞IO)开发的网络通信框架,比照于 BIO(Blocking I/O,阻塞IO),他的并发性能失去了很大进步。
  • 传输快:Netty 的传输依赖于零拷贝个性,尽量减少不必要的内存拷贝,实现了更高效率的传输。
  • 封装好:Netty 封装了 NIO 操作的很多细节,提供了易于应用调用接口。

3.Netty 的劣势有哪些?

  • 应用简略:封装了 NIO 的很多细节,应用更简略。
  • 功能强大:预置了多种编解码性能,反对多种支流协定。
  • 定制能力强:能够通过 ChannelHandler 对通信框架进行灵便地扩大。
  • 性能高:通过与其余业界支流的 NIO 框架比照,Netty 的综合性能最优。
  • 稳固:Netty 修复了曾经发现的所有 NIO 的 bug,让开发人员能够专一于业务自身。
  • 社区沉闷:Netty 是沉闷的开源我的项目,版本迭代周期短,bug 修复速度快。

4.Netty 的利用场景有哪些?

典型的利用有:阿里分布式服务框架 Dubbo,默认应用 Netty 作为根底通信组件,还有 RocketMQ 也是应用 Netty 作为通信的根底。

5.Netty 高性能体现在哪些方面?

  • IO 线程模型:同步非阻塞,用起码的资源做更多的事。
  • 内存零拷贝:尽量减少不必要的内存拷贝,实现了更高效率的传输。
  • 内存池设计:申请的内存能够重用,次要指间接内存。外部实现是用一颗二叉查找树治理内存分配情况。
  • 串形化解决读写:防止应用锁带来的性能开销。
  • 高性能序列化协定:反对 protobuf 等高性能序列化协定。

6.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读写不再阻塞,而是返回0、基于block的传输比基于流的传输更高效、更高级的IO函数zero-copy、IO多路复用大大提高了Java网络应用的可伸缩性和实用性。基于Reactor线程模型。

在Reactor模式中,事件散发器期待某个事件或者可利用或个操作的状态产生,事件散发器就把这个事件传给当时注册的事件处理函数或者回调函数,由后者来做理论的读写操作。如在Reactor中实现读:注册读就绪事件和相应的事件处理器、事件散发器期待事件、事件到来,激活散发器,散发器调用事件对应的处理器、事件处理器实现理论的读操作,解决读到的数据,注册新的事件,而后返还控制权。

7.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()轮询拿到曾经就绪的事件

8.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只负责接入认证、握手等操作;

9.TCP 粘包/拆包的起因及解决办法?

TCP是以流的形式来解决数据,一个残缺的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。

TCP粘包/分包的起因:

应用程序写入的字节大小大于套接字发送缓冲区的大小,会产生拆包景象,而应用程序写入数据小于套接字缓冲区大小,网卡将利用屡次写入的数据发送到网络上,这将会产生粘包景象;

进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将产生拆包 以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。

解决办法

音讯定长:FixedLengthFrameDecoder类

包尾减少特殊字符宰割:

  • 行分隔符类:LineBasedFrameDecoder
  • 或自定义分隔符类 :DelimiterBasedFrameDecoder

将音讯分为音讯头和音讯体:LengthFieldBasedFrameDecoder类。分为有头部的拆包与粘包、长度字段在前且有头部的拆包与粘包、多扩大头部的拆包与粘包。

10.什么是 Netty 的零拷贝?

Netty 的零拷贝次要蕴含三个方面:

  • Netty 的接管和发送 ByteBuffer 采纳 DIRECT BUFFERS,应用堆外间接内存进行 Socket 读写,不须要进行字节缓冲区的二次拷贝。如果应用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到间接内存中,而后才写入 Socket 中。相比于堆外间接内存,音讯在发送过程中多了一次缓冲区的内存拷贝。
  • Netty 提供了组合 Buffer 对象,能够聚合多个 ByteBuffer 对象,用户能够像操作一个 Buffer 那样不便的对组合 Buffer 进行操作,防止了传统通过内存拷贝的形式将几个小 Buffer 合并成一个大的 Buffer。
  • Netty 的文件传输采纳了 transferTo 办法,它能够间接将文件缓冲区的数据发送到指标 Channel,防止了传统通过循环 write 形式导致的内存拷贝问题。

11.Netty 中有哪种重要组件?

  • Channel:Netty 网络操作抽象类,它除了包含根本的 I/O 操作,如 bind、connect、read、write 等。
  • EventLoop:次要是配合 Channel 解决 I/O 操作,用来解决连贯的生命周期中所产生的事件。
  • ChannelFuture:Netty 框架中所有的 I/O 操作都为异步的,因而咱们须要 ChannelFuture 的 addListener()注册一个 ChannelFutureListener 监听事件,当操作执行胜利或者失败时,监听就会主动触发返回后果。
  • ChannelHandler:充当了所有解决入站和出站数据的逻辑容器。ChannelHandler 次要用来解决各种事件,这里的事件很宽泛,比方能够是连贯、数据接管、异样、数据转换等。
  • ChannelPipeline:为 ChannelHandler 链提供了容器,当 channel 创立时,就会被主动调配到它专属的 ChannelPipeline,这个关联是永久性的。

12.Netty 发送音讯有几种形式?

Netty 有两种发送音讯的形式:

  • 间接写入 Channel 中,音讯从 ChannelPipeline 当中尾部开始挪动;
  • 写入和 ChannelHandler 绑定的 ChannelHandlerContext 中,音讯从 ChannelPipeline 中的下一个 ChannelHandler 中挪动。

13.默认状况 Netty 起多少线程?何时启动?

Netty 默认是 CPU 处理器数的两倍,bind 完之后启动。

14.理解哪几种序列化协定?

序列化(编码)是将对象序列化为二进制模式(字节数组),次要用于网络传输、数据长久化等;而反序列化(解码)则是将从网络、磁盘等读取的字节数组还原成原始对象,次要用于网络传输对象的解码,以便实现近程调用。

影响序列化性能的关键因素:序列化后的码流大小(网络带宽的占用)、序列化的性能(CPU资源占用);是否反对跨语言(异构零碎的对接和开发语言切换)。

Java默认提供的序列化:无奈跨语言、序列化后的码流太大、序列化的性能差

XML,长处:人机可读性好,可指定元素或个性的名称。毛病:序列化数据只蕴含数据自身以及类的构造,不包含类型标识和程序集信息;只能序列化公共属性和字段;不能序列化办法;文件宏大,文件格式简单,传输占带宽。实用场景:当做配置文件存储数据,实时数据转换。

JSON,是一种轻量级的数据交换格局,长处:兼容性高、数据格式比较简单,易于读写、序列化后数据较小,可扩展性好,兼容性好、与XML相比,其协定比较简单,解析速度比拟快。毛病:数据的描述性比XML差、不适宜性能要求为ms级别的状况、额定空间开销比拟大。实用场景(可代替XML):跨防火墙拜访、可调式性要求高、基于Web browser的Ajax申请、传输数据量绝对小,实时性要求绝对低(例如秒级别)的服务。

Fastjson,采纳一种“假设有序疾速匹配”的算法。长处:接口简略易用、目前java语言中最快的json库。毛病:过于重视快,而偏离了“规范”及功能性、代码品质不高,文档不全。实用场景:协定交互、Web输入、Android客户端

Thrift,不仅是序列化协定,还是一个RPC框架。长处:序列化后的体积小, 速度快、反对多种语言和丰盛的数据类型、对于数据字段的增删具备较强的兼容性、反对二进制压缩编码。毛病:使用者较少、跨防火墙拜访时,不平安、不具备可读性,调试代码时绝对艰难、不能与其余传输层协定独特应用(例如HTTP)、无奈反对向长久层间接读写数据,即不适宜做数据长久化序列化协定。实用场景:分布式系统的RPC解决方案

Avro,Hadoop的一个子项目,解决了JSON的简短和没有IDL的问题。长处:反对丰盛的数据类型、简略的动静语言联合性能、具备自我形容属性、进步了数据解析速度、疾速可压缩的二进制数据模式、能够实现近程过程调用RPC、反对跨编程语言实现。毛病:对于习惯于动态类型语言的用户不直观。实用场景:在Hadoop中做Hive、Pig和MapReduce的长久化数据格式。

Protobuf,将数据结构以.proto文件进行形容,通过代码生成工具能够生成对应数据结构的POJO对象和Protobuf相干的办法和属性。长处:序列化后码流小,性能高、结构化数据存储格局(XML JSON等)、通过标识字段的程序,能够实现协定的前向兼容、结构化的文档更容易治理和保护。毛病:须要依赖于工具生成代码、反对的语言绝对较少,官网只反对Java 、C++ 、python。实用场景:对性能要求高的RPC调用、具备良好的跨防火墙的拜访属性、适宜应用层对象的长久化

其它

protostuff 基于protobuf协定,但不须要配置proto文件,间接导包即可 Jboss marshaling 能够间接序列化java类, 毋庸实java.io.Serializable接口 Message pack 一个高效的二进制序列化格局 Hessian 采纳二进制协定的轻量级remoting onhttp工具 kryo 基于protobuf协定,只反对java语言,须要注册(Registration),而后序列化(Output),反序列化(Input)

15.如何抉择序列化协定?

具体场景

对于公司间的零碎调用,如果性能要求在100ms以上的服务,基于XML的SOAP协定是一个值得思考的计划。 基于Web browser的Ajax,以及Mobile app与服务端之间的通信,JSON协定是首选。对于性能要求不太高,或者以动静类型语言为主,或者传输数据载荷很小的的使用场景,JSON也是十分不错的抉择。 对于调试环境比拟顽劣的场景,采纳JSON或XML可能极大的进步调试效率,升高零碎开发成本。 当对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro之间具备肯定的竞争关系。 对于T级别的数据的长久化利用场景,Protobuf和Avro是首要抉择。如果长久化后的数据存储在hadoop子项目里,Avro会是更好的抉择。

对于长久层非Hadoop我的项目,以动态类型语言为主的利用场景,Protobuf会更合乎动态类型语言工程师的开发习惯。因为Avro的设计理念偏差于动静类型语言,对于动静语言为主的利用场景,Avro是更好的抉择。 如果须要提供一个残缺的RPC解决方案,Thrift是一个好的抉择。 如果序列化之后须要反对不同的传输层协定,或者须要跨防火墙拜访的高性能场景,Protobuf能够优先思考。 protobuf的数据类型有多种:bool、double、float、int32、int64、string、bytes、enum、message。protobuf的限定符:required: 必须赋值,不能为空、optional:字段能够赋值,也能够不赋值、repeated: 该字段能够反复任意次数(包含0次)、枚举;只能用指定的常量集中的一个值作为其值;

protobuf的根本规定:每个音讯中必须至多留有一个required类型的字段、蕴含0个或多个optional类型的字段;repeated示意的字段能够蕴含0个或多个数据;[1,15]之内的标识号在编码的时候会占用一个字节(罕用),[16,2047]之内的标识号则占用2个字节,标识号肯定不能反复、应用音讯类型,也能够将音讯嵌套任意多层,可用嵌套音讯类型来代替组。

protobuf的音讯降级准则:不要更改任何已有的字段的数值标识;不能移除曾经存在的required字段,optional和repeated类型的字段能够被移除,但要保留标号不能被重用。新增加的字段必须是optional或repeated。因为旧版本程序无奈读取或写入新增的required限定符的字段。

编译器为每一个音讯类型生成了一个.java文件,以及一个非凡的Builder类(该类是用来创立音讯类接口的)。如:UserProto.User.Builder builder = UserProto.User.newBuilder();builder.build();

Netty中的应用:ProtobufVarint32FrameDecoder 是用于解决半包音讯的解码类;ProtobufDecoder(UserProto.User.getDefaultInstance())这是创立的UserProto.java文件中的解码类;ProtobufVarint32LengthFieldPrepender 对protobuf协定的音讯头上加上一个长度为32的整形字段,用于标记这个音讯的长度的类;ProtobufEncoder 是编码类

将StringBuilder转换为ByteBuf类型:copiedBuffer()办法

16.Netty 反对哪些心跳类型设置?

  • readerIdleTime:为读超时工夫(即测试端肯定工夫内未承受到被测试端音讯)。
  • writerIdleTime:为写超时工夫(即测试端肯定工夫外向被测试端发送音讯)。
  • allIdleTime:所有类型的超时工夫。

17.Netty 和 Tomcat 的区别?

  • 作用不同:Tomcat 是 Servlet 容器,能够视为 Web 服务器,而 Netty 是异步事件驱动的网络应用程序框架和工具用于简化网络编程,例如TCP和UDP套接字服务器。
  • 协定不同:Tomcat 是基于 http 协定的 Web 服务器,而 Netty 能通过编程自定义各种协定,因为 Netty 自身本人能编码/解码字节流,所有 Netty 能够实现,HTTP 服务器、FTP 服务器、UDP 服务器、RPC 服务器、WebSocket 服务器、Redis 的 Proxy 服务器、MySQL 的 Proxy 服务器等等。

18.NIOEventLoopGroup源码?

NioEventLoopGroup(其实是MultithreadEventExecutorGroup) 外部保护一个类型为 EventExecutor children [], 默认大小是处理器核数 * 2, 这样就形成了一个线程池,初始化EventExecutor时NioEventLoopGroup重载newChild办法,所以children元素的理论类型为NioEventLoop。

线程启动时调用SingleThreadEventExecutor的构造方法,执行NioEventLoop类的run办法,首先会调用hasTasks()办法判断以后taskQueue是否有元素。如果taskQueue中有元素,执行 selectNow() 办法,最终执行selector.selectNow(),该办法会立刻返回。如果taskQueue没有元素,执行 select(oldWakenUp) 办法

select ( oldWakenUp) 办法解决了 Nio 中的 bug,selectCnt 用来记录selector.select办法的执行次数和标识是否执行过selector.selectNow(),若触发了epoll的空轮询bug,则会重复执行selector.select(timeoutMillis),变量selectCnt 会逐步变大,当selectCnt 达到阈值(默认512),则执行rebuildSelector办法,进行selector重建,解决cpu占用100%的bug。

rebuildSelector办法先通过openSelector办法创立一个新的selector。而后将old selector的selectionKey执行cancel。最初将old selector的channel从新注册到新的selector中。rebuild后,须要从新执行办法selectNow,查看是否有已ready的selectionKey。

接下来调用processSelectedKeys 办法(解决I/O工作),当selectedKeys != null时,调用processSelectedKeysOptimized办法,迭代 selectedKeys 获取就绪的 IO 事件的selectkey寄存在数组selectedKeys中, 而后为每个事件都调用 processSelectedKey 来解决它,processSelectedKey 中别离解决OP_READ;OP_WRITE;OP_CONNECT事件。

最初调用runAllTasks办法(非IO工作),该办法首先会调用fetchFromScheduledTaskQueue办法,把scheduledTaskQueue中曾经超过提早执行工夫的工作移到taskQueue中期待被执行,而后顺次从taskQueue中取工作执行,每执行64个工作,进行耗时查看,如果已执行工夫超过事后设定的执行工夫,则进行执行非IO工作,防止非IO工作太多,影响IO工作的执行。

每个NioEventLoop对应一个线程和一个Selector,NioServerSocketChannel会被动注册到某一个NioEventLoop的Selector上,NioEventLoop负责事件轮询。

Outbound 事件都是申请事件, 发起者是 Channel,解决者是 unsafe,通过 Outbound 事件进行告诉,流传方向是 tail到head。Inbound 事件发起者是 unsafe,事件的解决者是 Channel, 是告诉事件,流传方向是从头到尾。

内存管理机制,首先会预申请一大块内存Arena,Arena由许多Chunk组成,而每个Chunk默认由2048个page组成。Chunk通过AVL树的模式组织Page,每个叶子节点示意一个Page,而两头节点示意内存区域,节点本人记录它在整个Arena中的偏移地址。当区域被调配进来后,两头节点上的标记位会被标记,这样就示意这个两头节点以下的所有节点都已被调配了。大于8k的内存调配在poolChunkList中,而PoolSubpage用于调配小于8k的内存,它会把一个page宰割成多段,进行内存调配。

ByteBuf的特点:反对主动扩容(4M),保障put办法不会抛出异样、通过内置的复合缓冲类型,实现零拷贝(zero-copy);不须要调用flip()来切换读/写模式,读取和写入索引离开;办法链;援用计数基于AtomicIntegerFieldUpdater用于内存回收;PooledByteBuf采纳二叉树来实现一个内存池,集中管理内存的调配和开释,不必每次应用都新建一个缓冲区对象。UnpooledHeapByteBuf每次都会新建一个缓冲区对象。

Netty简介

Netty是 一个异步事件驱动的网络应用程序框架,用于疾速开发可保护的高性能协定服务器和客户端。

JDK原生NIO程序的问题

JDK原生也有一套网络应用程序API,然而存在一系列问题,次要如下:

  • NIO的类库和API繁冗,应用麻烦,你须要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
  • 须要具备其它的额定技能做铺垫,例如相熟Java多线程编程,因为NIO编程波及到Reactor模式,你必须对多线程和网路编程十分相熟,能力编写出高质量的NIO程序
  • 可靠性能力补齐,开发工作量和难度都十分大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异样码流的解决等等,NIO编程的特点是性能开发绝对容易,然而可靠性能力补齐工作量和难度都十分大
  • JDK NIO的BUG,例如臭名远扬的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官网宣称在JDK1.6版本的update18修复了该问题,然而直到JDK1.7版本该问题仍旧存在,只不过该bug产生概率升高了一些而已,它并没有被基本解决

Netty的特点

Netty的对JDK自带的NIO的API进行封装,解决上述问题,次要特点有:

  • 设计优雅 实用于各种传输类型的对立API - 阻塞和非阻塞Socket 基于灵便且可扩大的事件模型,能够清晰地拆散关注点 高度可定制的线程模型 - 单线程,一个或多个线程池 真正的无连贯数据报套接字反对(自3.1起)
  • 使用方便 具体记录的Javadoc,用户指南和示例 没有其余依赖项,JDK 5(Netty 3.x)或6(Netty 4.x)就足够了
  • 高性能 吞吐量更高,提早更低 缩小资源耗费 最小化不必要的内存复制
  • 平安 残缺的SSL / TLS和StartTLS反对
  • 社区沉闷,不断更新 社区沉闷,版本迭代周期短,发现的BUG能够被及时修复,同时,更多的新性能会被退出

Netty常见应用场景

Netty常见的应用场景如下:

  • 互联网行业 在分布式系统中,各个节点之间须要近程服务调用,高性能的RPC框架必不可少,Netty作为异步高新能的通信框架,往往作为根底通信组件被这些RPC框架应用。 典型的利用有:阿里分布式服务框架Dubbo的RPC框架应用Dubbo协定进行节点间通信,Dubbo协定默认应用Netty作为根底通信组件,用于实现各过程节点之间的外部通信。
  • 游戏行业 无论是手游服务端还是大型的网络游戏,Java语言失去了越来越宽泛的利用。Netty作为高性能的根底通信组件,它自身提供了TCP/UDP和HTTP协定栈。 十分不便定制和开发公有协定栈,账号登录服务器,地图服务器之间能够不便的通过Netty进行高性能的通信
  • 大数据畛域 经典的Hadoop的高性能通信和序列化组件Avro的RPC框架,默认采纳Netty进行跨界点通信,它的Netty Service基于Netty框架二次封装实现

有趣味的读者能够理解一下目前有哪些开源我的项目应用了 Netty:Related projects

Netty高性能设计

Netty作为异步事件驱动的网络,高性能之处次要来自于其I/O模型和线程解决模型,前者决定如何收发数据,后者决定如何解决数据

I/O模型

用什么样的通道将数据发送给对方,BIO、NIO或者AIO,I/O模型在很大水平上决定了框架的性能

阻塞I/O

传统阻塞型I/O(BIO)能够用下图示意:

特点

  • 每个申请都须要独立的线程实现数据read,业务解决,数据write的残缺操作

问题

  • 当并发数较大时,须要创立大量线程来解决连贯,系统资源占用较大
  • 连贯建设后,如果以后线程临时没有数据可读,则线程就阻塞在read操作上,造成线程资源节约

I/O复用模型

在I/O复用模型中,会用到select,这个函数也会使过程阻塞,然而和阻塞I/O所不同的的,这两个函数能够同时阻塞多个I/O操作,而且能够同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数

Netty的非阻塞I/O的实现要害是基于I/O复用模型,这里用Selector对象示意:

Netty的IO线程NioEventLoop因为聚合了多路复用器Selector,能够同时并发解决成千盈百个客户端连贯。当线程从某客户端Socket通道进行读写数据时,若没有数据可用时,该线程能够进行其余工作。线程通常将非阻塞 IO 的闲暇工夫用于在其余通道上执行 IO 操作,所以独自的线程能够治理多个输出和输入通道。

因为读写操作都是非阻塞的,这就能够充沛晋升IO线程的运行效率,防止因为频繁I/O阻塞导致的线程挂起,一个I/O线程能够并发解决N个客户端连贯和读写操作,这从根本上解决了传统同步阻塞I/O一连贯一线程模型,架构的性能、弹性伸缩能力和可靠性都失去了极大的晋升。

基于buffer

传统的I/O是面向字节流或字符流的,以流式的形式程序地从一个Stream 中读取一个或多个字节, 因而也就不能随便扭转读取指针的地位。

在NIO中, 摈弃了传统的 I/O流, 而是引入了Channel和Buffer的概念. 在NIO中, 只能从Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel。

基于buffer操作不像传统IO的程序操作, NIO 中能够随便地读取任意地位的数据

线程模型

数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的音讯如何派发,线程模型的不同,对性能的影响也十分大。

事件驱动模型

通常,咱们设计一个事件处理模型的程序有两种思路

  • 轮询形式 线程一直轮询拜访相干事件发生源有没有产生事件,有产生事件就调用事件处理逻辑。
  • 事件驱动形式 产生事件,主线程把事件放入事件队列,在另外线程一直循环生产事件列表中的事件,调用事件对应的解决逻辑处理事件。事件驱动形式也被称为音讯告诉形式,其实是设计模式中观察者模式的思路。

以GUI的逻辑解决为例,阐明两种逻辑的不同:

  • 轮询形式 线程一直轮询是否产生按钮点击事件,如果产生,调用解决逻辑
  • 事件驱动形式 产生点击事件把事件放入事件队列,在另外线程生产的事件列表中的事件,依据事件类型调用相干事件处理逻辑

这里借用O’Reilly 大神对于事件驱动模型解释图

次要包含4个根本组件:

  • 事件队列(event queue):接管事件的入口,存储待处理事件
  • 散发器(event mediator):将不同的事件散发到不同的业务逻辑单元
  • 事件通道(event channel):散发器与处理器之间的分割渠道
  • 事件处理器(event processor):实现业务逻辑,解决实现后会收回事件,触发下一步操作

能够看出,绝对传统轮询模式,事件驱动有如下长处:

  • 可扩展性好,分布式的异步架构,事件处理器之间高度解耦,能够不便扩大事件处理逻辑
  • 高性能,基于队列暂存事件,能不便并行异步处理事件

Reactor线程模型

Reactor是反应堆的意思,Reactor模型,是指通过一个或多个输出同时传递给服务处理器的服务申请的事件驱动解决模式。 服务端程序处理传入多路申请,并将它们同步分派给申请对应的解决线程,Reactor模式也叫Dispatcher模式,即I/O多了复用对立监听事件,收到事件后散发(Dispatch给某过程),是编写高性能网络服务器的必备技术之一。

Reactor模型中有2个要害组成:

  • Reactor Reactor在一个独自的线程中运行,负责监听和散发事件,分发给适当的处理程序来对IO事件做出反馈。 它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人
  • Handlers 处理程序执行I/O事件要实现的理论事件,相似于客户想要与之交谈的公司中的理论官员。Reactor通过调度适当的处理程序来响应I/O事件,处理程序执行非阻塞操作

取决于Reactor的数量和Hanndler线程数量的不同,Reactor模型有3个变种

  • 单Reactor单线程
  • 单Reactor多线程
  • 主从Reactor多线程

能够这样了解,Reactor就是一个执行while (true) { selector.select(); …}循环的线程,会源源不断的产生新的事件,称作反应堆很贴切。

篇幅关系,这里不再具体开展Reactor个性、优缺点比拟,有趣味的读者能够参考我之前另外一篇文章:《了解高性能网络模型》

Netty线程模型

Netty次要基于主从Reactors多线程模型(如下图)做了肯定的批改,其中主从Reactor多线程模型有多个Reactor:MainReactor和SubReactor:

  • MainReactor负责客户端的连贯申请,并将申请转交给SubReactor
  • SubReactor负责相应通道的IO读写申请
  • 非IO申请(具体逻辑解决)的工作则会间接写入队列,期待worker threads进行解决

这里援用Doug Lee大神的Reactor介绍:Scalable IO in Java外面对于主从Reactor多线程模型的图

特地阐明的是: 尽管Netty的线程模型基于主从Reactor多线程,借用了MainReactor和SubReactor的构造,然而理论实现上,SubReactor和Worker线程在同一个线程池中:

EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();ServerBootstrap server = new ServerBootstrap();server.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)123456

下面代码中的bossGroup 和workerGroup是Bootstrap构造方法中传入的两个对象,这两个group均是线程池

  • bossGroup线程池则只是在bind某个端口后,取得其中一个线程作为MainReactor,专门解决端口的accept事件,每个端口对应一个boss线程
  • workerGroup线程池会被各个SubReactor和worker线程充分利用

异步解决

异步的概念和同步绝对。当一个异步过程调用收回后,调用者不能立即失去后果。理论解决这个调用的部件在实现后,通过状态、告诉和回调来告诉调用者。

Netty中的I/O操作是异步的,包含bind、write、connect等操作会简略的返回一个ChannelFuture,调用者并不能立即取得后果,通过Future-Listener机制,用户能够不便的被动获取或者通过告诉机制取得IO操作后果。

当future对象刚刚创立时,处于非实现状态,调用者能够通过返回的ChannelFuture来获取操作执行的状态,注册监听函数来执行实现后的操,常见有如下操作:

  • 通过isDone办法来判断以后操作是否实现
  • 通过isSuccess办法来判断已实现的以后操作是否胜利
  • 通过getCause办法来获取已实现的以后操作失败的起因
  • 通过isCancelled办法来判断已实现的以后操作是否被勾销
  • 通过addListener办法来注册监听器,当操作已实现(isDone办法返回实现),将会告诉指定的监听器;如果future对象已实现,则了解告诉指定的监听器

例如上面的的代码中绑定端口是异步操作,当绑定操作解决完,将会调用相应的监听器解决逻辑

    serverBootstrap.bind(port).addListener(future -> {        if (future.isSuccess()) {            System.out.println(new Date() + ": 端口[" + port + "]绑定胜利!");        } else {            System.err.println("端口[" + port + "]绑定失败!");        }    });12345678

相比传统阻塞I/O,执行I/O操作后线程会被阻塞住, 直到操作实现;异步解决的益处是不会造成线程阻塞,线程在I/O操作期间能够执行别的程序,在高并发情景下会更稳固和更高的吞吐量。

Netty架构设计

后面介绍完Netty相干一些实践介绍,上面从性能个性、模块组件、运作过程来介绍Netty的架构设计

性能个性

  • 传输服务 反对BIO和NIO
  • 容器集成 反对OSGI、JBossMC、Spring、Guice容器
  • 协定反对 HTTP、Protobuf、二进制、文本、WebSocket等一系列常见协定都反对。 还反对通过履行编码解码逻辑来实现自定义协定
  • Core外围 可扩大事件模型、通用通信API、反对零拷贝的ByteBuf缓冲对象

模块组件

Bootstrap、ServerBootstrap

Bootstrap意思是疏导,一个Netty利用通常由一个Bootstrap开始,次要作用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动疏导类,ServerBootstrap是服务端启动疏导类。

Future、ChannelFuture

正如后面介绍,在Netty中所有的IO操作都是异步的,不能立即得悉音讯是否被正确处理,然而能够过一会等它执行实现或者间接注册一个监听,具体的实现就是通过Future和ChannelFutures,他们能够注册一个监听,当操作执行胜利或失败时监听会主动触发注册的监听事件。

Channel

Netty网络通信的组件,可能用于执行网络I/O操作。 Channel为用户提供:

  • 以后网络连接的通道的状态(例如是否关上?是否已连贯?)
  • 网络连接的配置参数 (例如接收缓冲区大小)
  • 提供异步的网络I/O操作(如建设连贯,读写,绑定端口),异步调用意味着任何I / O调用都将立刻返回,并且不保障在调用完结时所申请的I / O操作已实现。调用立刻返回一个ChannelFuture实例,通过注册监听器到ChannelFuture上,能够I / O操作胜利、失败或勾销时回调告诉调用方。
  • 反对关联I/O操作与对应的处理程序

不同协定、不同的阻塞类型的连贯都有不同的 Channel 类型与之对应,上面是一些罕用的 Channel 类型

  • NioSocketChannel,异步的客户端 TCP Socket 连贯
  • NioServerSocketChannel,异步的服务器端 TCP Socket 连贯
  • NioDatagramChannel,异步的 UDP 连贯
  • NioSctpChannel,异步的客户端 Sctp 连贯
  • NioSctpServerChannel,异步的 Sctp 服务器端连贯 这些通道涵盖了 UDP 和 TCP网络 IO以及文件 IO.

Selector

Netty基于Selector对象实现I/O多路复用,通过 Selector, 一个线程能够监听多个连贯的Channel事件, 当向一个Selector中注册Channel 后,Selector 外部的机制就能够主动一直地查问(select) 这些注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络连接实现等),这样程序就能够很简略地应用一个线程高效地治理多个 Channel 。

NioEventLoop

NioEventLoop中保护了一个线程和工作队列,反对异步提交执行工作,线程启动时会调用NioEventLoop的run办法,执行I/O工作和非I/O工作:

  • I/O工作 即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys办法触发。
  • 非IO工作 增加到taskQueue中的工作,如register0、bind0等工作,由runAllTasks办法触发。

两种工作的执行工夫比由变量ioRatio管制,默认为50,则示意容许非IO工作执行的工夫与IO工作的执行工夫相等。

NioEventLoopGroup

NioEventLoopGroup,次要治理eventLoop的生命周期,能够了解为一个线程池,外部保护了一组线程,每个线程(NioEventLoop)负责解决多个Channel上的事件,而一个Channel只对应于一个线程。

ChannelHandler

ChannelHandler是一个接口,解决I / O事件或拦挡I / O操作,并将其转发到其ChannelPipeline(业务解决链)中的下一个处理程序。

ChannelHandler自身并没有提供很多办法,因为这个接口有许多的办法须要实现,方便使用期间,能够继承它的子类:

  • ChannelInboundHandler用于解决入站I / O事件
  • ChannelOutboundHandler用于解决出站I / O操作

或者应用以下适配器类:

  • ChannelInboundHandlerAdapter用于解决入站I / O事件
  • ChannelOutboundHandlerAdapter用于解决出站I / O操作
  • ChannelDuplexHandler用于解决入站和出站事件

ChannelHandlerContext

保留Channel相干的所有上下文信息,同时关联一个ChannelHandler对象

ChannelPipline

保留ChannelHandler的List,用于解决或拦挡Channel的入站事件和出站操作。 ChannelPipeline实现了一种高级模式的拦挡过滤器模式,使用户能够齐全管制事件的解决形式,以及Channel中各个的ChannelHandler如何互相交互。

下图援用Netty的Javadoc4.1中ChannelPipline的阐明,形容了ChannelPipeline中ChannelHandler通常如何解决I/O事件。 I/O事件由ChannelInboundHandler或ChannelOutboundHandler解决,并通过调用ChannelHandlerContext中定义的事件流传办法(例如ChannelHandlerContext.fireChannelRead(Object)和ChannelOutboundInvoker.write(Object))转发到其最近的处理程序。

                                                 I/O Request                                            via Channel or                                        ChannelHandlerContext                                                      |  +---------------------------------------------------+---------------+  |                           ChannelPipeline         |               |  |                                                  \|/              |  |    +---------------------+            +-----------+----------+    |  |    | Inbound Handler  N  |            | Outbound Handler  1  |    |  |    +----------+----------+            +-----------+----------+    |  |              /|\                                  |               |  |               |                                  \|/              |  |    +----------+----------+            +-----------+----------+    |  |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |  |    +----------+----------+            +-----------+----------+    |  |              /|\                                  .               |  |               .                                   .               |  | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|  |        [ method call]                       [method call]         |  |               .                                   .               |  |               .                                  \|/              |  |    +----------+----------+            +-----------+----------+    |  |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |  |    +----------+----------+            +-----------+----------+    |  |              /|\                                  |               |  |               |                                  \|/              |  |    +----------+----------+            +-----------+----------+    |  |    | Inbound Handler  1  |            | Outbound Handler  M  |    |  |    +----------+----------+            +-----------+----------+    |  |              /|\                                  |               |  +---------------+-----------------------------------+---------------+                  |                                  \|/  +---------------+-----------------------------------+---------------+  |               |                                   |               |  |       [ Socket.read() ]                    [ Socket.write() ]     |  |                                                                   |  |  Netty Internal I/O Threads (Transport Implementation)            |  +-------------------------------------------------------------------+123456789101112131415161718192021222324252627282930313233343536373839

入站事件由自下而上方向的入站处理程序解决,如图左侧所示。 入站Handler处理程序通常解决由图底部的I / O线程生成的入站数据。 通常通过理论输出操作(例如SocketChannel.read(ByteBuffer))从近程读取入站数据。

出站事件由高低方向解决,如图右侧所示。 出站Handler处理程序通常会生成或转换出站传输,例如write申请。 I/O线程通常执行理论的输入操作,例如SocketChannel.write(ByteBuffer)。

在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应, 它们的组成关系如下:

一个 Channel 蕴含了一个 ChannelPipeline, 而 ChannelPipeline 中又保护了一个由 ChannelHandlerContext 组成的双向链表, 并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最初一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不烦扰。

工作原理架构

初始化并启动Netty服务端过程如下:

    public static void main(String[] args) {        // 创立mainReactor        NioEventLoopGroup boosGroup = new NioEventLoopGroup();        // 创立工作线程组        NioEventLoopGroup workerGroup = new NioEventLoopGroup();        final ServerBootstrap serverBootstrap = new ServerBootstrap();        serverBootstrap                  // 组装NioEventLoopGroup                 .group(boosGroup, workerGroup)                 // 设置channel类型为NIO类型                .channel(NioServerSocketChannel.class)                // 设置连贯配置参数                .option(ChannelOption.SO_BACKLOG, 1024)                .childOption(ChannelOption.SO_KEEPALIVE, true)                .childOption(ChannelOption.TCP_NODELAY, true)                // 配置入站、出站事件handler                .childHandler(new ChannelInitializer<NioSocketChannel>() {                    @Override                    protected void initChannel(NioSocketChannel ch) {                        // 配置入站、出站事件channel                        ch.pipeline().addLast(...);                        ch.pipeline().addLast(...);                    }    });        // 绑定端口        int port = 8080;        serverBootstrap.bind(port).addListener(future -> {            if (future.isSuccess()) {                System.out.println(new Date() + ": 端口[" + port + "]绑定胜利!");            } else {                System.err.println("端口[" + port + "]绑定失败!");            }        });}
  • 根本过程如下:
  • 1 初始化创立2个NioEventLoopGroup,其中boosGroup用于Accetpt连贯建设事件并散发申请, workerGroup用于解决I/O读写事件和业务逻辑
  • 2 基于ServerBootstrap(服务端启动疏导类),配置EventLoopGroup、Channel类型,连贯参数、配置入站、出站事件handler
  • 3 绑定端口,开始工作

联合下面的介绍的Netty Reactor模型,介绍服务端Netty的工作架构图:

server端蕴含1个Boss NioEventLoopGroup和1个Worker NioEventLoopGroup,NioEventLoopGroup相当于1个事件循环组,这个组里蕴含多个事件循环NioEventLoop,每个NioEventLoop蕴含1个selector和1个事件循环线程。

每个Boss NioEventLoop循环执行的工作蕴含3步:

  • 1 轮询accept事件
  • 2 解决accept I/O事件,与Client建设连贯,生成NioSocketChannel,并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上 *3 解决工作队列中的工作,runAllTasks。工作队列中的工作包含用户调用eventloop.execute或schedule执行的工作,或者其它线程提交到该eventloop的工作。

每个Worker NioEventLoop循环执行的工作蕴含3步:

  • 1 轮询read、write事件;
  • 2 处I/O事件,即read、write事件,在NioSocketChannel可读、可写事件产生时进行解决
  • 3 解决工作队列中的工作,runAllTasks。

其中工作队列中的task有3种典型应用场景

  • 1 用户程序自定义的一般工作
ctx.channel().eventLoop().execute(new Runnable() {    @Override    public void run() {        //...    }});1234567
  • 2 非以后reactor线程调用channel的各种办法 例如在推送零碎的业务线程外面,依据用户的标识,找到对应的channel援用,而后调用write类办法向该用户推送音讯,就会进入到这种场景。最终的write会提交到工作队列中后被异步生产。
  • 3 用户自定义定时工作
ctx.channel().eventLoop().schedule(new Runnable() {    @Override    public void run() {    }}, 60, TimeUnit.SECONDS);1234567

总结

当初稳固举荐应用的支流版本还是Netty4,Netty5 中应用了 ForkJoinPool,减少了代码的复杂度,然而对性能的改善却不显著,所以这个版本不举荐应用,官网也没有提供下载链接。

Netty 入门门槛绝对较高,其实是因为这方面的材料较少,并不是因为他有多难,大家其实都能够像搞透 Spring 一样搞透 Netty。在学习之前,倡议先了解透整个框架原理构造,运行过程,能够少走很多弯路。

作者:thinkwon
起源:https://thinkwon.blog.csdn.ne...
本文首发于公众号:Java版web我的项目,欢送关注获取更多精彩内容