Netty是什么
Netty 是一个高性能、异步事件驱动的 NIO 框架,基于 JAVA NIO 提供的 API 实现。它提供了对
TCP、 UDP 和文件传输的支持,作为一个异步 NIO 框架, Netty 的所有 IO 操作都是异步非阻塞
的, 通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
Netty 高性能
多路复用通讯方式
- Selector:
在 Java NIO 中,Selector 是一个多路复用器,它允许单个线程同时管理多个通道的 I/O 操作。Netty 内部使用 Selector 实现多路复用。
异步通信NIO
Netty 的异步通信通过以下几个方面实现:
- 非阻塞 I/O: Netty 使用非阻塞 I/O 操作,确保 I/O 操作不会阻塞线程。这意味着在执行 I/O 操作时,线程可以继续处理其他任务,而不是被阻塞。
- 事件驱动: Netty 是事件驱动的框架,通过事件循环模型(Reactor 模式)来处理 I/O 事件。事件处理是异步的,事件会被分发到相应的处理器进行处理。
- 回调机制: Netty 提供了回调机制,通过异步的 API 调用来处理 I/O 操作的结果,避免了同步等待的开销。
零拷贝
- Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,
不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,
JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,
消息在发送过程中多了一次缓冲区的内存拷贝。 - Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样
方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的
Buffer。 - Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,
避免了传统通过循环 write 方式导致的内存拷贝问题
内存池
随着 JVM 虚拟机和 JIT 即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓
冲区 Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽
量重用缓冲区, Netty 提供了基于内存池的缓冲
Reactor线程模型
Reactor 单线程模型
Reactor 单线程模型 是最简单的 Reactor 模式实现,它在单个线程中处理所有的 I/O 事件。该模型通常适用于负载较低、事件处理逻辑简单的场景。
工作原理
- 事件循环: 在单线程中运行 Reactor 实例,这个线程负责监听 I/O 事件,并将这些事件分发给相应的事件处理器。
- 单线程处理: 所有的 I/O 事件(如读、写、连接等)都由单个线程处理,线程通过 Selector 监听事件并调用注册的处理程序。
优点
- 简单: 实现简单,易于理解和维护。
- 避免线程切换: 由于只有一个线程,所以没有线程切换的开销。
缺点
- 可扩展性差: 由于所有事件都在一个线程中处理,这可能导致性能瓶颈,尤其是在高负载的情况下。
- 单点故障: 如果处理逻辑中的某个操作阻塞,会影响整个线程的性能。
适用场景
- 低负载、事件处理简单的应用场景。
Reactor 多线程模型
Reactor 多线程模型 使用多个线程来处理 I/O 事件,这种模型通过将处理逻辑分散到多个线程中,提高了系统的并发处理能力。
工作原理
- 多线程 Reactor: 在多线程模型中,Reactor 实例使用多个线程来处理 I/O 事件。每个线程都有一个 EventLoop,负责处理事件并将这些事件分发给对应的 ChannelHandler。
- 事件分发: 事件通过多个 Reactor 实例(线程)进行处理,每个线程可能处理不同的 Channel 或者不同的 I/O 操作。
优点
- 高性能: 多线程可以提高并发处理能力,适用于高负载的应用场景。
- 响应更快: 分散到多个线程中处理事件,可以避免单线程的瓶颈,提高系统的整体响应速度。
缺点
- 复杂性增加: 实现和维护更加复杂,需要处理线程间的同步和协调。
- 线程开销: 线程的创建和上下文切换会带来一定的开销。、
适用场景
- 中高负载的应用场景,尤其是需要处理大量并发连接和高并发请求的场景。
Reactor 主从多线程模型
Reactor 主从多线程模型 是一种更为复杂和灵活的实现方式,结合了主线程和从线程的优势,以处理高并发的网络 I/O。
工作原理
- 主线程(Boss Thread): 主线程负责监听新的连接请求。它接收新的连接并将其注册到从线程池中的 EventLoop 中进行处理。
- 从线程(Worker Threads): 从线程池负责处理已经接受的连接的 I/O 操作(如读、写)。每个从线程处理一部分连接的事件,通常通过 Selector 来监听 I/O 事件。
优点
- 高效的连接管理: 主线程负责接受连接和管理连接的生命周期,而从线程专注于处理 I/O 操作,提高了连接的处理效率。
- 负载均衡: 通过将连接的 I/O 操作分散到多个从线程中,可以平衡负载,避免单线程处理过多的连接。
缺点
- 实现复杂: 需要合理设计和实现主线程和从线程的协调机制。
- 线程切换开销: 可能存在一定的线程切换开销,但通常比单线程模型要小。
适用场景
- 高负载、高并发的应用场景,尤其适合需要处理大量连接和高并发请求的场景。
序列化框架
Netty 默认提供了对 Google Protobuf 的支持,通过扩展 Netty 的编解码接口,用户可以实现其它的高性能序列化框架,例如 Thrift 的压缩二进制编解码框架
序列化(Serialization)
序列化 是将对象转化为字节流的过程,这样对象就可以被存储到磁盘或者通过网络传输到其他系统。序列化的目的是将对象的状态保存下来,以便在需要时恢复。一般也将序列化称为编码,主要用于网络传输、数据持久化等;
反序列化(Deserialization)
反序列化 是将字节流转换回对象的过程。它的目的是将之前序列化保存的数据恢复成原始的对象,以便进行进一步的处理。一般也将反序列化称为解码(Decode),主要用于网络传输对象的解码,以便完成远程调用。
框架
Java 原生序列化
缺点:
-
无法跨语言:我认为这对于Java序列化的发展是致命的“失误”,因为Java序列化后的字节数组,其它语言无法进行反序列化。
-
序列化后的码流太大:相对于目前主流的序列化协议,Java序列化后的码流太大;
JSON
SON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集, JSON采用与编程语言无关的文本格式,但是也使用了类C语言(包括C, C++, C#, Java, JavaScript, Perl, Python等)的习惯,简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
-
优点
- 前后兼容性高
- 数据格式比较简单,易于读写
- 序列化后数据较小,可扩展性好,兼容性好
- 与XML相比,其协议比较简单,解析速度比较快
-
缺点
- 数据的描述性比XML差
- 不适合性能要求为ms级别的情况
- 额外空间开销比较大
Protobuf
-
优点:
- 高效:Protobuf 使用紧凑的二进制格式,序列化和反序列化速度快,数据量小。
- 跨语言支持:支持多种编程语言,如 Java、C++、Python、Go 等。
- 易于维护:有明确的 schema 定义,支持向前和向后兼容的字段修改。
-
缺点:
- 学习曲线:需要定义 .proto 文件,并使用工具生成代码,可能需要一些学习时间。
- 缺乏自描述性:生成的二进制数据不包含字段名称等信息,解析时需要依赖 schema。
-
使用场景:
- 高性能应用:需要高效的数据传输和存储。
- 跨语言通信:在不同编程语言之间进行数据交换。
- 复杂数据模型:数据结构复杂,需要定义清晰的 schema。
Thrift
-
优点:
- 多语言支持:支持多种编程语言,包括 Java、C++、Python、PHP、Go 等。
- 灵活性:支持多种传输协议和序列化格式,如二进制、JSON 等。
- RPC 支持:内置 RPC 框架,方便进行远程调用。
-
缺点:
- 复杂性:有较多的配置选项和复杂的环境依赖。
- 性能:在某些情况下,性能可能不如 Protobuf 和 Avro。
-
使用场景:
- 分布式系统:需要支持远程过程调用(RPC)和服务定义。
- 跨语言系统:在多个语言中实现数据交换和服务调用。
- 协议选择:需要根据具体需求选择不同的传输协议和序列化格式。
Avro
-
定义:
Avro属于Apache Hadoop的一个子项目。 Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美,Avro的产生解决了JSON的冗长和没有IDL的问题 -
优点
- 支持丰富的数据类型
- 简单的动态语言结合功能
- 具有自我描述属性
- 提高了数据解析速度
- 快速可压缩的二进制数据形式
- 可以实现远程过程调用RPC
- 支持跨编程语言实现
-
缺点
- 对于习惯于静态类型语言的用户不直观
-
适用场景
- 在Hadoop中做Hive、Pig和MapReduce的持久化数据格式
总结
序列化 是将对象转化为字节流的过程,用于存储或传输。
反序列化 是将字节流转回对象的过程,以便在接收端处理。
在 Netty 中,序列化和反序列化通过编码器和解码器来实现,支持多种序列化框架,包括 Java 原生序列化、JSON、Protobuf、 Thrift、 Avro 。
选择适合的序列化框架和实现方法可以优化网络通信的性能和效率。