Java架构师⾯试之Netty⾯试专题及答案(共10题,含详细解答
6.了解哪⼏种序列化协议? 序列化(编码)是将对象序列化为⼆进制形式(字节数组),主要⽤于⽹络传输、数据持久化等;⽽反序列化(解码)则是将从⽹络、磁盘等读取的字节数组还原成原始对象,主要⽤于⽹络传输对象的解码,以便完成远程调⽤。 影响序列化性能的关键因素:序列化后的码流⼤⼩(⽹络带宽的占⽤)、序列化的性能( CPU 资源占⽤);是否⽀持跨语⾔(异构系统的对接和开发语⾔切换)。 Java 默认提供的序列化:⽆法跨语⾔、序列化后的码流太⼤、序列化的性能差XML, 优点:⼈机可读性好,可指定元素或特性的名称。 缺点:序列化数据只包含数据本⾝以及类的结构,不包括类型标识和程序集信息;只能序列化公共属性和字段;不能序列化⽅法;⽂件庞⼤,⽂件格式复杂,传输占带宽。 适⽤场景:当做配置⽂件存储数据,实时数据转换。
java调用python模型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.
《⼀线⼤⼚Java⾯试题解析+后端开发学习笔记+最新架构讲解视频+实战项⽬源码讲义》
【docs.qq/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
newBuilder();builder.build();
Netty 中的使⽤: ProtobufVarint32FrameDecoder 是⽤于处理半包消息的解码类;
ProtobufDecoder(DefaultInstance())这是创建的 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 包装的 anferTo ⽅法 实现⽂件传输, 可以直接将⽂件缓冲区的数据发送到⽬标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 的 selection
Key。 接下来调⽤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 每次都会新建⼀个缓冲区对象。
嗨,你好呀,未来的架构师,本⽂由Java架构师⾯试⽹收集整理并进⾏编辑发布,谢谢⼤家的⽀持~
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论