Netty学习

什么是 Netty ?

Netty 是一款提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于 NIO 的客户、服务器端编程框架。使用 Netty 可以确保你快速和简单地开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty 相当简化和流线化了网络应用的编程开发过程,例如,TCP 和 UDP 的 socket 服务开发。

Netty 具有什么特性?

分类 Netty的特性
设计 1. 统一的 API ,支持多种传输类型( 阻塞和非阻塞的 )
2. 简单而强大的线程模型
3. 真正的无连接数据报套接字( UDP )支持
4. 连接逻辑组件( ChannelHander 中顺序处理消息 )以及组件复用( 一个 ChannelHandel 可以被多个ChannelPipeLine 复用 )
易于使用 1. 详实的 Javadoc 和大量的示例集
2. 不需要超过 JDK 1.6+ 的依赖
性能 拥有比 Java 的核心 API 更高的吞吐量以及更低的延迟( 得益于池化和复用 ),更低的资源消耗以及最少的内存复制
健壮性 1. 不会因为慢速、快速或者超载的连接而导致 OutOfMemoryError
2. 消除在高速网络中 NIO 应用程序常见的不公平读 / 写比率
安全性 完整的 SSL/TLS 以及 StartTLs 支持,可用于受限环境下,如 Applet 和 OSGI
社区驱动 发布快速而且频繁

为什么选择 Netty ?

使用简单:API 使用简单,开发门槛低。
功能强大:预置了多种编解码功能,支持多种主流协议。
定制能力强:可以通过 ChannelHandler 对通信框架进行灵活的扩展。
性能高:通过与其它业界主流的 NIO 框架对比,Netty 的综合性能最优。
成熟稳定:Netty 修复了已经发现的所有 JDK NIO BUG,业务开发人员不需要再为 NIO 的 BUG 而烦恼。
社区活跃:版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会被加入。
案例丰富:经历了大规模的商业应用考验,质量已经得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它可以完全满足不同行业的商业应用。
实际上,这个也是我们做技术选型的一些参考点,不仅仅适用于 Netty ,也同样适用于其他技术栈。

为什么说 Netty 使用简单?

我们假设要搭建一个 Server 服务器,使用 Java NIO 的步骤如下:
创建 ServerSocketChannel 。
绑定监听端口,并配置为非阻塞模式。
创建 Selector,将之前创建的 ServerSocketChannel 注册到 Selector 上,监听 SelectionKey.OP_ACCEPT 。
循环执行 Selector#select() 方法,轮询就绪的 Channel。
轮询就绪的 Channel 时,如果是处于 OP_ACCEPT 状态,说明是新的客户端接入,调用 ServerSocketChannel#accept() 方法,接收新的客户端。
设置新接入的 SocketChannel 为非阻塞模式,并注册到 Selector 上,监听 OP_READ 。
如果轮询的 Channel 状态是 OP_READ ,说明有新的就绪数据包需要读取,则构造 ByteBuffer 对象,读取数据。
这里,解码数据包的过程,需要我们自己编写。

使用 Netty 的步骤有?

创建 NIO 线程组 EventLoopGroup 和 ServerBootstrap。
设置 ServerBootstrap 的属性:线程组、SO_BACKLOG 选项,设置 NioServerSocketChannel 为 Channel
设置业务处理 Handler 和 编解码器 Codec 。
绑定端口,启动服务器程序。
在业务处理 Handler 中,处理客户端发送的数据,并给出响应。

那么相比 Java NIO,使用 Netty 开发程序,都简化了哪些步骤呢?

无需关心 OP_ACCEPT、OP_READ、OP_WRITE 等等 IO 操作,Netty 已经封装,对我们在使用是透明无感的。
使用 boss 和 worker EventLoopGroup ,Netty 直接提供多 Reactor 多线程模型。
在 Netty 中,我们看到有使用一个解码器 FixedLengthFrameDecoder,可以用于处理定长消息的问题,能够解决 TCP 粘包拆包问题,十分方便。如果使用 Java NIO ,需要我们自行实现解码器。
说说业务中 Netty 的使用场景?
构建高性能、低时延的各种 Java 中间件,Netty 主要作为基础通信框架提供高性能、低时延的通信服务。例如:
RocketMQ ,分布式消息队列。
Dubbo ,服务调用框架。
Spring WebFlux ,基于响应式的 Web 框架。
HDFS ,分布式文件系统。
公有或者私有协议栈的基础通信框架,例如可以基于 Netty 构建异步、高性能的 WebSocket、Protobuf 等协议的支持。
各领域应用,例如大数据、游戏等,Netty 作为高性能的通信框架用于内部各模块的数据分发、传输和汇总等,实现模块之间高性能通信。
说说 Netty 如何实现高性能?
线程模型 :更加优雅的 Reactor 模式实现、灵活的线程模型、利用 EventLoop 等创新性的机制,可以非常高效地管理成百上千的 Channel 。
内存池设计 :使用池化的 Direct Buffer 等技术,在提高 IO 性能的同时,减少了对象的创建和销毁。并且,内吃吃的内部实现是用一颗二叉查找树,更好的管理内存分配情况。
内存零拷贝 :使用 Direct Buffer ,可以使用 Zero-Copy 机制。
协议支持 :提供对 Protobuf 等高性能序列化协议支持。

使用更多本地代码。例如:
直接利用 JNI 调用 Open SSL 等方式,获得比 Java 内建 SSL 引擎更好的性能。
利用 JNI 提供了 Native Socket Transport ,在使用 Epoll edge-triggered 的情况下,可以有一定的性能提升。
其它:
利用反射等技术直接操纵 SelectionKey ,使用数组而不是 Java 容器等。
实现 FastThreadLocal 类,当请求频繁时,带来更好的性能。
….

Netty 的高性能如何体现?

性能是设计出来的,而不是测试出来的。那么,Netty 的架构设计是如何实现高性能的呢?
线程模型 :采用异步非阻塞的 I/O 类库,基于 Reactor 模式实现,解决了传统同步阻塞 I/O 模式下服务端无法平滑处理客户端线性增长的问题。
堆外内存 :TCP 接收和发送缓冲区采用直接内存代替堆内存,避免了内存复制,提升了 I/O 读取和写入性能。
内存池设计 :支持通过内存池的方式循环利用 ByteBuf,避免了频繁创建和销毁 ByteBuf 带来的性能消耗。
参数配置 :可配置的 I/O 线程数目和 TCP 参数等,为不同用户提供定制化的调优参数,满足不同的性能场景。
队列优化 :采用环形数组缓冲区,实现无锁化并发编程,代替传统的线程安全容器或锁。
并发能力 :合理使用线程安全容器、原子类等,提升系统的并发能力。
降低锁竞争 :关键资源的使用采用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的 CPU 资源消耗问题。
内存泄露检测 :通过引用计数器及时地释放不再被引用的对象,细粒度的内存管理降低了 GC 的频率,减少频繁 GC 带来的时延增大和 CPU 损耗。

Netty 的高可靠如何体现?

链路有效性检测:由于长连接不需要每次发送消息都创建链路,也不需要在消息完成交互时关闭链路,因此相对于短连接性能更高。为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用心跳机制的原因是,避免在系统空闲时因网络闪断而断开连接,之后又遇到海量业务冲击导致消息积压无法处理。为了解决这个问题,需要周期性地对链路进行有效性检测,一旦发现问题,可以及时关闭链路,重建 TCP 连接。为了支持心跳,Netty 提供了两种链路空闲检测机制:
读空闲超时机制:连续 T 周期没有消息可读时,发送心跳消息,进行链路检测。如果连续 N 个周期没有读取到心跳消息,可以主动关闭链路,重建连接。
写空闲超时机制:连续 T 周期没有消息需要发送时,发送心跳消息,进行链路检测。如果连续 N 个周期没有读取对方发回的心跳消息,可以主动关闭链路,重建连接。

内存保护机制:Netty 提供多种机制对内存进行保护,包括以下几个方面:
通过对象引用计数器对 ByteBuf 进行细粒度的内存申请和释放,对非法的对象引用进行检测和保护。
可设置的内存容量上限,包括 ByteBuf、线程池线程数等,避免异常请求耗光内存。
优雅停机:优雅停机功能指的是当系统推出时,JVM 通过注册的 Shutdown Hook 拦截到退出信号量,然后执行推出操作,释放相关模块的资源占用,将缓冲区的消息处理完成或清空,将待刷新的数据持久化到磁盘和数据库中,等到资源回收和缓冲区消息处理完成之后,再退出。
Netty 的可扩展如何体现?
可定制、易扩展。

责任链模式 :ChannelPipeline 基于责任链模式开发,便于业务逻辑的拦截、定制和扩展。
基于接口的开发 :关键的类库都提供了接口或抽象类,便于用户自定义实现。
提供大量的工厂类 :通过重载这些工厂类,可以按需创建出用户需要的对象。
提供大量系统参数 :供用户按需设置,增强系统的场景定制性。
简单介绍 Netty 的核心组件?

Netty 有如下六个核心组件:

Bootstrap & ServerBootstrap
Channel
ChannelFuture
EventLoop & EventLoopGroup
ChannelHandler
ChannelPipeline

说说 Netty 的逻辑架构?

Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构如下图所示:

Netty 逻辑架构图
在这里插入图片描述

Reactor 通信调度层:由一系列辅助类组成,包括 Reactor 线程 NioEventLoop 及其父类,NioSocketChannel 和 NioServerSocketChannel 等等。该层的职责就是监听网络的读写和连接操作,负责将网络层的数据读到内存缓冲区,然后触发各自网络事件,例如连接创建、连接激活、读事件、写事件等。将这些事件触发到 pipeline 中,由 pipeline 管理的职责链来进行后续的处理。
职责链 ChannelPipeline:负责事件在职责链中的有序传播,以及负责动态地编排职责链。职责链可以选择监听和处理自己关心的事件,拦截处理和向后传播事件。
业务逻辑编排层:业务逻辑编排层通常有两类,一类是纯粹的业务逻辑编排,一类是应用层协议插件,用于特定协议相关的会话和链路管理。由于应用层协议栈往往是开发一次到处运行,并且变动较小,故而将应用协议到 POJO 的转变和上层业务放到不同的 ChannelHandler 中,就可以实现协议层和业务逻辑层的隔离,实现架构层面的分层隔离。
TCP 粘包 / 拆包的原因?应该这么解决?

概念

TCP 是以流的方式来处理数据,所以会导致粘包 / 拆包。

拆包:一个完整的包可能会被 TCP 拆分成多个包进行发送。
粘包:也可能把小的封装成一个大的数据包发送。

原因

应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象。而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包现象。
待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包。
以太网帧的 payload(净荷)大于 MTU(默认为 1500 字节)进行 IP 分片拆包。
接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

解决

在 Netty 中,提供了多个 Decoder 解析类,如下:

① FixedLengthFrameDecoder ,基于固定长度消息进行粘包拆包处理的。
② LengthFieldBasedFrameDecoder ,基于消息头指定消息长度进行粘包拆包处理的。
③ LineBasedFrameDecoder ,基于换行来进行消息粘包拆包处理的。
④ DelimiterBasedFrameDecoder ,基于指定消息边界方式进行粘包拆包处理的。
实际上,上述四个 FrameDecoder 实现可以进行规整:

① 是 ② 的特例,固定长度是消息头指定消息长度的一种形式。
③ 是 ④ 的特例,换行是于指定消息边界方式的一种形式。
了解哪几种序列化协议?

概念

序列化(编码),是将对象序列化为二进制形式(字节数组),主要用于网络传输、数据持久化等。
反序列化(解码),则是将从网络、磁盘等读取的字节数组还原成原始对象,主要用于网络传输对象的解码,以便完成远程调用。

选型

在选择序列化协议的选择,主要考虑以下三个因素:

序列化后的字节大小。更少的字节数,可以减少网络带宽、磁盘的占用。
序列化的性能。对 CPU、内存资源占用情况。
是否支持跨语言。例如,异构系统的对接和开发语言切换。

方案

如果对序列化工具了解不多的胖友,可能一看有这么多优缺点会比较懵逼,可以先记得有哪些序列化工具,然后在慢慢熟悉它们的优缺点。

重点,还是知道【选型】的考虑点。

【重点】Java 默认提供的序列化
无法跨语言;序列化后的字节大小太大;序列化的性能差。
【重点】XML 。
优点:人机可读性好,可指定元素或特性的名称。
缺点:序列化数据只包含数据本身以及类的结构,不包括类型标识和程序集信息;只能序列化公共属性和字段;不能序列化方法;文件庞大,文件格式复杂,传输占带宽。
适用场景:当做配置文件存储数据,实时数据转换。
【重点】JSON ,是一种轻量级的数据交换格式。
优点:兼容性高、数据格式比较简单,易于读写、序列化后数据较小,可扩展性好,兼容性好。与 XML 相比,其协议比较简单,解析速度比较快。
缺点:数据的描述性比 XML 差、不适合性能要求为 ms 级别的情况、额外空间开销比较大。
适用场景(可替代 XML ):跨防火墙访问、可调式性要求高、基于Restful API 请求、传输数据量相对小,实时性要求相对低(例如秒级别)的服务。
【了解】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 文件,直接导包即可。
目前,微博 RPC 框架 Motan 在使用它。
【了解】Jboss Marshaling ,可以直接序列化 Java 类, 无须实 java.io.Serializable 接口。
【了解】Message Pack ,一个高效的二进制序列化格式。
【重点】Hessian ,采用二进制协议的轻量级 remoting on http 服务。
目前,阿里 RPC 框架 Dubbo 的默认序列化协议。
【重要】kryo ,是一个快速高效的Java对象图形序列化框架,主要特点是性能、高效和易用。该项目用来序列化对象到文件、数据库或者网络。
目前,阿里 RPC 框架 Dubbo 的可选序列化协议。
【重要】FST ,fast-serialization 是重新实现的 Java 快速对象序列化的开发包。序列化速度更快(2-10倍)、体积更小,而且兼容 JDK 原生的序列化。要求 JDK 1.7 支持。
目前,阿里 RPC 框架 Dubbo 的可选序列化协议。
原生的 NIO 存在 Epoll Bug 是什么?Netty 是怎么解决的?

Java NIO Epoll BUG

Java NIO Epoll 会导致 Selector 空轮询,最终导致 CPU 100% 。
官方声称在 JDK 1.6 版本的 update18 修复了该问题,但是直到 JDK 1.7 版本该问题仍旧存在,只不过该 BUG 发生概率降低了一些而已,它并没有得到根本性解决。
Netty 解决方案
对 Selector 的 select 操作周期进行统计,每完成一次空的 select 操作进行一次计数,若在某个周期内连续发生 N 次空轮询,则判断触发了 Epoll 死循环 Bug 。
此处空的 select 操作的定义是,select 操作执行了 0 毫秒。
此时,Netty 重建 Selector 来解决。判断是否是其他线程发起的重建请求,若不是则将原 SocketChannel 从旧的 Selector 上取消注册,然后重新注册到新的 Selector 上,最后将原来的 Selector 关闭。

什么是 Netty 空闲检测?

在 Netty 中,提供了 IdleStateHandler 类,正如其名,空闲状态处理器,用于检测连接的读写是否处于空闲状态。如果是,则会触发 IdleStateEvent 。

IdleStateHandler 目前提供三种类型的心跳检测,通过构造方法来设置。代码如下:
// IdleStateHandler.java

public IdleStateHandler(
        int readerIdleTimeSeconds,
        int writerIdleTimeSeconds,
        int allIdleTimeSeconds) {
    this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
         TimeUnit.SECONDS);
}

readerIdleTimeSeconds 参数:为读超时时间,即测试端一定时间内未接受到被测试端消息。
writerIdleTimeSeconds 参数:为写超时时间,即测试端一定时间内向被测试端发送消息。
allIdleTimeSeconds 参数:为读或写超时时间。
另外,空闲检测和心跳机制是两件事。只是说,因为我们使用 IdleStateHandler 的目的,就是检测到连接处于空闲,通过心跳判断其是否还是有效的连接。
虽然说,TCP 协议层提供了 Keeplive 机制,但是该机制默认的心跳时间是 2 小时,依赖操作系统实现不够灵活。因而,我们才在应用层上,自己实现心跳机制。

Netty 如何实现重连?

客户端,通过 IdleStateHandler 实现定时检测是否空闲,例如说 15 秒。
如果空闲,则向服务端发起心跳。
如果多次心跳失败,则关闭和服务端的连接,然后重新发起连接。
服务端,通过 IdleStateHandler 实现定时检测客户端是否空闲,例如说 90 秒。
如果检测到空闲,则关闭客户端。
注意,如果接收到客户端的心跳请求,要反馈一个心跳响应给客户端。通过这样的方式,使客户端知道自己心跳成功。

Netty 为什么要实现内存管理?

在 Netty 中,IO 读写必定是非常频繁的操作,而考虑到更高效的网络传输性能,Direct ByteBuffer 必然是最合适的选择。但是 Direct ByteBuffer 的申请和释放是高成本的操作,那么进行池化管理,多次重用是比较有效的方式。但是,不同于一般于我们常见的对象池、连接池等池化的案例,ByteBuffer 是有大小一说。又但是,申请多大的 Direct ByteBuffer 进行池化又会是一个大问题,太大会浪费内存,太小又会出现频繁的扩容和内存复制!!!所以呢,就需要有一个合适的内存管理算法,解决高效分配内存的同时又解决内存碎片化的问题。

  • 38
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值