关于java:Java架构师面试之Netty面试专题及答案共10题含详细解答

31次阅读

共计 9710 个字符,预计需要花费 25 分钟才能阅读完成。

1.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 中实现读:注册读就绪事件和相应的事件处理器、事件散发器期待事件、事件到来,激活散发器,散发器调用事件对应的处理器、事件处理器实现理论的读操作,解决读到的数据,注册新的事件,而后返还控制权。

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

3.Netty 的特点?
一个高性能、异步事件驱动的 NIO 框架,它提供了对 TCP、UDP 和文件传输的反对应用更高效的 socket 底层,对 epoll 空轮询引起的 cpu 占用飙升在外部进行了解决,防止了间接应用 NIO 的陷阱,简化了 NIO 的解决形式。采纳多种 decoder/encoder 反对,对 TCP 粘包 / 分包进行自动化解决

可应用承受 / 解决线程池,进步连贯效率,对重连、心跳检测的简略反对可配置 IO 线程数、TCP 参数,TCP 接管和发送缓冲区应用间接内存代替堆内存,通过内存池的形式循环利用 ByteBuf 通过援用计数器及时申请开释不再援用的对象,升高了 GC 频率应用单线程串行化的形式,高效的 Reactor 线程模型大量应用了 volitale、应用了 CAS 和原子类、线程安全类的应用、读写锁的应用

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

5.TCP 粘包 / 拆包的起因及解决办法?
TCP 是以流的形式来解决数据,一个残缺的包可能会被 TCP 拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。

TCP 粘包 / 分包的起因:

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

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

解决办法

音讯定长:FixedLengthFrameDecoder 类包尾减少特殊字符宰割:行分隔符类:LineBasedFrameDecoder 或自定义分隔符类:

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

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

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

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

长处:人机可读性好,可指定元素或个性的名称。

毛病:序列化数据只蕴含数据自身以及类的构造,不包含类型标识和程序集信息;只能序列化公共属性和字段;不能序列化办法;文件宏大,文件格式简单,传输占带宽。

实用场景:当做配置文件存储数据,实时数据转换。

JSON,是一种轻量级的数据交换格局

长处:兼容性高、数据格式比较简单,易于读写、序列化后数据较小,可扩展性好,兼容性好、与 XML 相比,其协定比较简单,解析速度比拟快。

毛病:数据的描述性比 XML 差、不适宜性能要求为 ms 级别的状况、额定空间开销比拟大。

实用场景(可代替XML):跨防火墙拜访、可调式性要求高、基于 Webbrowser 的 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)

7. 如何抉择序列化协定?
具体场景

对于公司间的零碎调用,如果性能要求在 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()办法

8.Netty 的零拷贝实现?
Netty 的接管和发送 ByteBuffer 采纳 DIRECT BUFFERS,应用堆外间接内存进行 Socket 读写,不须要进行字节缓冲区的二次拷贝。堆内存多了一次内存拷贝,JVM 会将堆内存 Buffer 拷贝一份到间接内存中,而后才写入 Socket 中。ByteBuffer 由 ChannelConfig 调配,而 ChannelConfig 创立 ByteBufAllocator 默认应用 Direct BufferCompositeByteBuf 类能够将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 防止了传统通过内存拷贝的形式将几个小 Buffer 合并成一个大的 Buffer。addComponents 办法将 header 与 body 合并为一个逻辑上的 ByteBuf, 这两个 ByteBuf 在 CompositeByteBuf 外部都是独自存在的, CompositeByteBuf 只是逻辑上是一个整体通过 FileRegion 包装的 FileChannel.tranferTo 办法 实现文件传输, 能够间接将文件缓冲区的数据发送到指标 Channel,防止了传统通过循环 write 形式导致的内存拷贝问题。通过 wrap 办法, 咱们能够将 byte[] 数组、ByteBuf、ByteBuffer 等包装成一个 NettyByteBuf 对象, 进而防止了拷贝操作。Selector BUG:若 Selector 的轮询后果为空,也没有 wakeup 或新音讯解决,则产生空轮询,CPU 使用率 100%;

Netty 的解决办法:对 Selector 的 select 操作周期进行统计,每实现一次空的 select 操作进行一次计数,若在某个周期内间断产生 N 次空轮询,则触发了 epoll 死循环 bug。重建 Selector,判断是否是其余线程发动的重建申请,若不是则将原 SocketChannel 从旧的 Selector 上去除注册,从新注册到新的 Selector 上,并将原来的 Selector 敞开。

9.Netty 的高性能体现在哪些方面?
心跳,对服务端:会定时革除闲置会话 inactive(netty5),对客户端: 用来检测会话是否断开,是否重来,检测网络提早,其中 idleStateHandler 类 用来检测会话状态

串行无锁化设计,即音讯的解决尽可能在同一个线程内实现,期间不进行线程切换,这样就防止了多线程竞争和同步锁。外表上看,串行化设计仿佛 CPU 利用率不高,并发水平不够。然而,通过调整 NIO 线程池的线程参数,能够同时启动多个串行化的线程并行运行,这种部分无锁化的串行线程设计相比一个队列 - 多个工作线程模型性能更优。

可靠性,链路有效性检测:链路闲暇检测机制,读 / 写闲暇超时机制;内存保护机制:通过内存池重用 ByteBuf;ByteBuf 的解码爱护;优雅停机:不再接管新音讯、退出前的预处理操作、资源的开释操作。

Netty 安全性:反对的平安协定:SSL V2 和 V3,TLS,SSL 单向认证、双向认证和第三方 CA 认证。

高效并发编程的体现:volatile 的大量、正确应用;CAS 和原子类的宽泛应用;线程平安容器的应用;通过读写锁晋升并发性能。IO 通信性能三准则:传输(AIO)、协定(Http)、线程(主从多线程)

流量整型的作用(变压器):避免因为上下游网元性能不平衡导致上游网元被压垮,业务流中断;避免因为通信模块承受音讯过快,后端业务线程解决不及时导致撑死问题。

TCP 参数配置:SO_RCVBUF 和 SO_SNDBUF:通常倡议值为 128K 或者 256K;

SO_TCPNODELAY:NAGLE 算法通过将缓冲区内的小封包主动相连,组成较大的封包,阻止大量小封包的发送阻塞网络,从而进步网络应用效率。然而对于时延敏感的利用场景须要敞开该优化算法;

10.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 每次都会新建一个缓冲区对象。

正文完
 0