Java 架构师面试之 Netty 面试专题及答案(共 10 题,含详细解答
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();
评论