概述
Netty
高扩展
责任链模式
- ChannelPipeline 基于责任链模式开发,便于业务逻辑的拦截、定制和扩展
基于接口的开发
- 关键的类库都提供了接口或抽象类,便于用户自定义实现
提供大量的工厂类
- 通过重载这些工厂类,可以按需创建出用户需要的对象
提供大量系统参数
- 供用户按需设置,增强系统的场景定制性
高可靠
链路有效性检测
- 为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用心跳机制的原因是,避免在系统空闲时因网络闪断而断开连接,之后又遇到海量业务冲击导致消息积压无法处理。为了解决这个问题,需要周期性地对链路进行有效性检测,一旦发现问题,可以及时关闭链路,重建 TCP 连接
- 读空闲超时机制
- 写空闲超时机制
内存保护机制
- 通过对象引用计数器对 ByteBuf 进行细粒度的内存申请和释放,对非法的对象引用进行检测和保护
- 可设置的内存容量上限,包括 ByteBuf、线程池线程数等,避免异常请求耗光内存
优雅停机
- JVM 通过注册的 Shutdown Hook 拦截到退出信号量,然后执行退出操作,释放相关模块的资源占用,将缓冲区的消息处理完成或清空,将待刷新的数据持久化到磁盘和数据库中,等到资源回收和缓冲区消息处理完成之后,再退出
原文地址:https://juejin.cn/post/6844903704668160008
Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端
模块组件
Bootstrap、ServerBootstrap
Future、ChannelFuture
Buffer(基于缓冲区)核心
Channel核心
- 当前网络连接的通道的状态
- 网络连接的配置参数 (例如接收缓冲区大小)
- 提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成,调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。
- 支持关联 I/O 操作与对应的处理程序
Selector核心
- 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。
- 当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel
NioEventLoop
-
NioEventLoop 中维护了一个线程(Selector我的理解)和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务
-
I/O 任务
- selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。
-
非 IO 任务
- 添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。
NioEventLoopGroup
- 主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。
ChannelHandler
- ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。
ChannelHandlerContext
- 保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象
ChannelPipline
- 保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。
JDK 原生 NIO 程序的问题
NIO 的类库和 API 繁杂,使用麻烦
需要具备其他的额外技能做铺垫
- 例如熟悉 Java 多线程编程
可靠性能力补齐,开发工作量和难度都非常大
- 例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等。
JDK NIO 的 Bug
-
例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。
-
Netty如何解决
- 对 Selector 的 select 操作周期进行统计,每完成一次空的 select 操作进行一次计数,若在某个周期内连续发生 N 次空轮询,则判断触发了 Epoll 死循环 Bug
此时,Netty 重建 Selector 来解决。判断是否是其他线程发起的重建请求,若不是则将原 SocketChannel 从旧的 Selector 上取消注册,然后重新注册到新的 Selector 上,最后将原来的 Selector 关闭。
- 对 Selector 的 select 操作周期进行统计,每完成一次空的 select 操作进行一次计数,若在某个周期内连续发生 N 次空轮询,则判断触发了 Epoll 死循环 Bug
在 Netty 中,我们看到有使用一个解码器 FixedLengthFrameDecoder,可以用于处理定长消息的问题,能够解决 TCP 粘包拆包问题,十分方便。如果使用 Java NIO ,需要我们自行实现解码器
对 JDK 自带的 NIO 的 API 进行封装,解决上述问题,特点如下
设计优雅
使用方便
- 详细记录的 Javadoc,用户指南和示例
高性能,吞吐量更高,延迟更低;减少资源消耗;最小化不必要的内存复制。
安全
- 完整的 SSL/TLS 和 StartTLS 支持。
社区活跃,不断更新
高性能之处主要来自于其 I/O 模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据。Netty 的非阻塞 I/O 的实现关键是基于 I/O 复用模型
I/O模型
-
Blocking I/O
- 每个请求都需要独立的线程完成数据 Read,业务处理,数据 Write 的完整操作问题
- 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。
- 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费
-
I/O 复用模型
- 在 I/O 复用模型中,会用到 Select,这个函数也会使进程阻塞,但是和阻塞 I/O 所不同的是这两个函数可以同时阻塞多个 I/O 操作。而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数。
BIO与NIO区别
BIO
- BIO 的 Socket 是单向的
- BIO 的各种操作是阻塞的
- BIO 是面向流( Stream )的
- 线程模型是一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理
NIO
- NIO 是面向缓冲区( Buffer )的
- NIO 的各种操作是非阻塞的
- NIO 的 Channel 是双向的
- 一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,基于 多 Reactor 模型
基于 Buffer
传统I/O
- 面向字节流或字符流的,以流式的方式顺序地从一个 Stream 中读取一个或多个字节, 因此也就不能随意改变读取指针的位置。
NIO
- 抛弃了传统的 I/O 流,而是引入了 Channel 和 Buffer 的概念。在 NIO 中,只能从 Channel 中读取数据到 Buffer 中或将数据从 Buffer 中写入到 Channel。
- 基于 Buffer 操作不像传统 IO 的顺序操作,NIO 中可以随意地读取任意位置的数据
事件驱动模型
轮询方式
- 线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑
事件驱动方式
-
发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式,其实是设计模式中观察者模式的思路。
-
事件队列(event queue)
-
分发器(event mediator)
-
事件通道(event channel)
- 分发器与处理器之间的联系渠道
-
事件处理器(event processor)
-
-
优点
- 可扩展性好
- 高性能
Reactor 线程模型
通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。
3 个变种
-
单 Reactor 单线程
- Selector不断轮询客户端请求,通过监听SelectionKey,如果是OP_ACCEPT,创建新的连接就是Accpter,Acceptor 主要任务是构造 Handler,获取到 Client 相关的 SocketChannel 之后,绑定到相应的 Handler 上,Handler处理具体的I/O读写事件
-
单 Reactor 多线程
- 相对于第一种单线程的模式来说,在处理业务逻辑,也就是获取到 IO 的读写事件之后,交由线程池来处理,这样可以减小主 Reactor 的性能开销,从而更专注的做事件分发工作了,从而提升整个应用的吞吐
-
主从 Reactor 多线程
Netty 主要基于主从 Reactors 多线程模型(如下图)做了一定的修改,其中主从 Reactor 多线程模型有多个 Reactor:
-
MainReactor
- 负责客户端的连接请求,并将请求转交给 SubReactor。
-
SubReactor
- 负责相应通道的 IO 读写请求
-
非 IO 请求
- 会直接写入队列,等待 worker threads 进行处理。
-
虽然 Netty 的线程模型基于主从 Reactor 多线程,借用了 MainReactor 和 SubReactor 的结构。但是实际实现上 SubReactor 和 Worker 线程在同一个线程池中
异步处理
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
Netty 中的 I/O 操作是异步的
通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则理解通知指定的监听器。
为什么高性能
线程模型
- 更加优雅的 Reactor 模式实现、灵活的线程模型、利用 EventLoop 等创新性的机制,可以非常高效地管理成百上千的 Channel
内存池设计
- 使用池化的 Direct Buffer 等技术,在提高 IO 性能的同时,减少了对象的创建和销毁。并且,内存池的内部实现是用一颗二叉查找树,更好的管理内存分配情况
内存零拷贝
- 使用 Direct Buffer ,可以使用 Zero-Copy 机制
协议支持
- 提供对 Protobuf 等高性能序列化协议支持
使用更多本地代码
- 直接利用 JNI 调用 Open SSL 等方式,获得比 Java 内建 SSL 引擎更好的性能
- 利用 JNI 提供了 Native Socket Transport ,在使用 Epoll edge-triggered 的情况下,可以有一定的性能提升
内存泄露检测
- 通过引用计数器及时地释放不再被引用的对象,细粒度的内存管理降低了 GC 的频率,减少频繁 GC 带来的延时增大和 CPU 损耗
其他
- 利用反射等技术直接操纵 SelectionKey ,使用数组而不是 Java 容器等
- 实现 FastThreadLocal 类,当请求频繁时,带来更好的性能
使用场景
- RocketMQ
- Dubbo
- Spring WebFlux
- HDFS
序列化协议
选择考虑因素
-
序列化后的字节大小
- 更少的字节数,可以减少网络带宽、磁盘的占用
-
序列化的性能
- 对 CPU、内存资源占用情况
-
是否支持跨语言
- 例如,异构系统的对接和开发语言切换
最后
以上就是傲娇保温杯为你收集整理的NettyNetty的全部内容,希望文章能够帮你解决NettyNetty所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复