答:Netty是一个高性能的网络编程框架,基于NIO的非阻塞式IO模型,可以帮助开发者快速开发高性能、高可靠性的网络应用程序。
答:Netty的核心组件包括:Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipeline等。
答:Channel是Netty中的一个核心组件,代表一个可以进行读写操作的通道,可以用于网络数据的读写、文件的读写等操作。
答:EventLoop是Netty中的另一个核心组件,它负责处理所有的事件,包括读事件、写事件、连接事件、断开连接事件等。
答:ChannelFuture是Netty中的一个异步操作类,用于获取异步操作的结果,例如:写操作、连接操作等。
答:ChannelHandler是Netty中的一个组件,用于处理各种事件,包括读事件、写事件、连接事件、断开连接事件等。
答:ChannelPipeline是Netty中的一个核心组件,它负责管理各种ChannelHandler,用于处理网络数据的读写、协议解析等操作。
答:Netty的优势主要包括:高性能、高可靠性、易于扩展、灵活性高、易于使用等。
答:Netty的应用场景非常广泛,主要包括:网络通信、分布式系统、高并发服务器、实时数据处理等。
答:与其他网络编程框架相比,Netty具有更高的性能、更好的可扩展性和更丰富的功能,同时也更易于使用和维护。
因为 Netty 具有下面这些优点,并且相比于直接使用 JDK 自带的NIO相关的API 来说更加易用。
答:TCP 粘包/拆包 就是你基于 TCP 发送数据的时候,出现了多个字符串“粘”在了一起或者一个字符串被“拆”开的问题。
在 Java 中自带的有实现 Serializable 接口来实现序列化,但由于它性能、安全性等原因一般情况下是不会被使用到的。通常情况下,我们使用 Protostuff、Hessian2、json 序列方式比较多,另外还有一些序列化性能非常好的序列化方式也是很好的选择:
答:我们知道 TCP 在进行读写之前,server 与 client 之间必须提前建立一个连接。建立连接的过程,需要我们常说的三次握手,释放/关闭连接的话需要四次挥手。这个过程是比较消耗网络资源并且有时间延迟的。所谓,短连接说的就是 server 端 与 client 端建立连接之后,读写完成之后就关闭掉连接,如果下一次再要互相发送消息,就要重新连接。
短连接的优点很明显,就是管理和实现都比较简单,缺点也很明显,每一次的读写都要建立连接必然会带来大量网络资源的消耗,并且连接的建立也需要耗费时间。
长连接说的就是 client 向 server 双方建立连接之后,即使client 与server 完成一次读写,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
长连接的可以省去较多的 TCP 建立和关闭的操作,降低对网络资源的依赖,节约时间。对于频繁请求资源的客户来说,非常适用长连接。
答:在 TCP 保持长连接的过程中,可能会出现断网等网络异常出现,异常发生的时候,client 与 server 之间如果没有交互的话,他们是无法发现对方已经掉线的。为了解决这个问题, 我们就需要引入 心跳机制 。
心跳机制的工作原理是: 在 client 与 server 之间在一定时间内没有数据交互时, 即处于idle 状态时, 客户端或服务器就会发送一个特殊的数据包给对方, 当接收方收到这个数据报文后, 也立即发送一个特殊的数据报文, 回应发送方, 此即一个 PING-PONG交互。所以, 当某一端收到心跳消息后, 就知道了对方仍然在线, 这就确保 TCP 连接的有效性. TCP 实际上自带的就有长连接选项,本身是也有心跳包机制,也就是TCP的选项:SO_KEEPALIVE。
但是,TCP 协议层面的长连接灵活性不够。所以,一般情况下我们都是在应用层协议上实现自定义信跳机制的,也就是在 Netty 层面通过编码实现。通过Netty实现心跳机制的话,核心类是 IdleStateHandler 。
答:零复制(英语:Zero-copy;也译零拷贝)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
在 OS 层面上的 Zero-copy 通常指避免在 用户态(User-space) 与内核态(Kernel-space) 之间来回拷贝数据。而在 Netty 层面 ,零拷贝主要体现在对于数据操作的优化。Netty 中的零拷贝体现在以下几个方面:
Netty 有两种发送消息的方式:
BIO 是面向流的,NIO 是面向缓冲区的;BIO 的各种流是阻塞的。而 NIO 是非阻塞的;BIO 的Stream 是单向的,而 NIO 的 channel 是双向的。
NIO 的特点:事件驱动模型、单线程处理多任务、非阻塞 I/O,I/O 读写不再阻塞,而是返回0、基于 block 的传输比基于流的传输更高效、更高级的 IO 函数 zero-copy、IO 多路复用大大提高了 Java 网络应用的可伸缩性和实用性。基于 Reactor 线程模型。在 Reactor 模式中,事件分发器等待某个事件或者可应用或个操作的状态发生,事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。
如在 Reactor 中实现读:注册读就绪事件和相应的事件处理器、事件分发器等待事件、事件到来,激活分发器,分发器调用事件对应的处理器、事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
答:序列化(编码)是将对象序列化为二进制形式(字节数组),主要用于网络传输、数据持久
化等;而反序列化(解码)则是将从网络、磁盘等读取的字节数组还原成原始对象,主要用
于网络传输对象的解码,以便完成远程调用。影响序列化性能的关键因素:序列化后的码流大小(网络带宽的占用)、序列化的性能(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)
在 Netty 主要靠 NioEventLoopGroup 线程池来实现具体的线程模型的。我们实现服务端的时候,一般会初始化两个线程组:
单线程模型 :
一个线程需要执行处理所有的 accept、read、decode、process、encode、send事件。对于高负载、高并发,并且对性能要求比较高的场景不适用。
多线程模型:
一个 Acceptor 线程只负责监听客户端的连接,一个 NIO 线程池负责具体处理accept、read、decode、process、encode、send 事件。满足绝大部分应用场景,并发连接量不大的时候没啥问题,但是遇到并发连接大的时候就可能会出现问题,成为性能瓶颈。
主从多线程模型:
从一个 主线程 NIO 线程池中选择一个线程作为 Acceptor 线程,绑定监听端口,接收客户端连接的连接,其他线程负责后续的接入认证等工作。连接建立完成后,SubNIO线程池负责具体处理 I/O 读写。如果多线程模型无法满足你的需求的时候,可以考虑使用主从多线程模型
select:它仅仅知道了,有 I/O 事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以 select 具有 O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
poll:poll 本质上和 select 没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd 对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
epoll:epoll 可以理解为 event poll,不同于忙轮询和无差别轮询,epoll 会把哪个流发生了怎样的 I/O 事件通知我们。所以我们说 epoll 实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了 O(1)),通过红黑树和双链表数据结构,并结合回调机制,造就了 epoll 的高效,epoll_create(),epoll_ctl()和epoll_wait()系统调用。