赣州新闻网

首页 / 科技

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

2020-02-11 · 赣州新闻网 · 科技

迎关注我的头条号:Wooola,10年Java软件开发及架构设计经验,专注于Java、Go语言、微服务架构,致力于每天分享原创文章、快乐编码和开源技术。

对于高性能的 RPC 框架,Netty 作为异步通信框架,几乎成为必备品。例如,Dubbo 框: d + R ] y架中通信组件,还有 Roi I 1 AcketMQ 中生产o @ ; m ^ 3 } |者和消费者的通信,都使用了 Netty。今天,我们来看看 Netty 的基本B % E i $ h ]架构和原理。

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

Netty 的特点与 NIO

Netty 是一个异步的、基于事件驱动的网络应用= 2 q d e框架,它可9 ! 0 - F e d , O以用来开发高性能服务端和客户端。

以前编写网络调用程序的时候,我们都会在客户- o /端创建一个 Socket,通过这个 Socket 连接到服务端。

服务端根据这个 Socket 创建一个 Thread,用来发出请求。客户端在发起调用以后,需要等待服务端处理完成,才能继续后面的操作。这样线程会出现等待的状态。

如果客户端请求数越多,服务端( f 3 G 8 D创建的处理线程也( e S G会越多,JVM 如此多的线程并不是一件容易的事。

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

使用阻塞 I} ] ^ 9/O 处理多个连接

为了解0 z # ] ! a 0决上述的问题,推出了 NIO 的概念,也就是(Non-blocking I/O)。其中,Selector 机制就是 NIO 的核心。

当每次客户端请求时,会创建一个 Socket Cq y _ l 1hannei J i i 到了一些组件的调用,例如 EventLoopGroup,Channel。这些会在后面详细讲解。

这里有个大致的印象就好:

  • 创建 EventLoopGroup。
  • 创建 ServerB; ! - l p tootstrap。
  • 指定所使用的 NIO 传K D A g 4 o输 Channe# $ B [ E ~l。
  • 使用指定的h f @ k E v ) |端口设置套接字地址。
  • 添加一个 ServerHandler 到 Channel 的 ChannelPipeline。
  • 异步地绑定服务器;调用 sync() 方法阻塞等待直到绑定完成。
  • 获取 Channel 的 CloseFuture,并且阻. r % Z w塞当前线程直到它完成。
  • 关闭 EventLoopGroup,释放所有的资源。

NettyServer 启动以后会监听某个端o { 0 o Jty 核心组件

通过上面的简单例子,发现有些 Netty 组件在服务初始化以及通讯时被用到,下面就来介绍一下这些组件的用途和关系。

①ChanD T $nel

通过上面例子可以看出,当客户端和服务端连接的时a ] K r D候会建立一个 Channel。

这个 Channel 我们可以理解为 Socket 连接,它负责基本的 IO 操作,例如:bind(),u a : (connect(),read(),write() 等等。

简w J C E y V Y D单的说,Channel 就是代表连接,实体之间的连接,程序之间的连接,文件之间的连接,设备之间的连接。同时它也是数据入站和出站的载体。

②EventLoop 和 EventLoopGrouD M =p

既然有了 Channel 连接服务,让信息之间可以流动。如果服务发出的消息称作“出站”消息,服务接受的消息称作“入站”消息。那么消息的“出站”/“入站”就会产生事件(Event)。

例如:连接已激活;数据读取;用户事件;异常事件;打开链接;关闭链接等等。

顺着这个思路往下想,有了数据,数据的流动产生事件,那么就有一个机制去监3 $ d m =控和协调事件。

这个机制(组件)就是 Event( Q R y 3Loop。在 Netty 中每个 Channel 都r 0 u t # F 8 w F 8 S Y h系

在异步传输的情况下,一个 EventLoop 是可以处理多个 Channel 中产生的事件的,它主要的工作就是事件的发现以及通& a ! # 4 ;知。

相对于以前一个 Chh r ? v ] V # ]annel 就占用一个 Thred : ` { v d C fad 的情况。Netty 的方式就要合理 U Kndler(出站事件处理器E | 3 4)

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

假设每次请求都会触发事件,而由 ChannelHandler 来处理这些事件q K : 4 N ` S,这个事件Z H x 6 !的处理顺序是P T %由 ChannelPipeline 来决定的8 % / 6 2 z p B。

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

ChannelHanlder 处理,出站/入站的事件y l K - ) N k

Ch- 6 @ M EannelPipeline 为 ChannelHandler 链提供了容器。到 Channel 被创建的时候,会被 Netty 框架自动分配到 ChannelPipeline 上。

ChannelPipeline 保证 ChannelHandler 按照一定顺序处理事件,当事件触发以后,会将数据通过 ChannelPipeline 按照J b [ N r a | ? U一定的顺序通过 ChannelHandler。

说白了,ChannelPipeline 是负责“排队”的。这里的“排w Q . i : H 5 l队”是处理事件的顺序。

同时,ChannelPipeline 也{ H 个用于w a / : I读: ~ M . Y取数据,一个用于写入数据。这M u p两个索引通过在字节数组中移动,来P { | ; B V 4 H定位需要读或者写信息的位置。

当从 ByteBuf 读取,它的 readerIndv H ) tex(读索引} ) ) ! i } 2)将会根据读取的字节数递增。

同样,当写 ByteBuf 时,它的 writerIndex 也会根据写入的C v i X + # D I字节数进行递增。

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

ByteBuf 读写索引图例

需要注意的是极限的情{ = L V |况是 readerIZ , L = G ^ % 5ndex 刚好读到了 write@ r : S h j SrIndex 写入的地方。

如果 readerIndex 超过了 w] Z g ` 1 ? H LriterIndex 的时候,Netty 会抛出 IndexOutOf-BoundsEx) x Hception 异常。

ByteBuf 使用模式

谈了 ByteBuf 的工作原理以后,再来看看它的使用模式。

根据存放缓冲区的不同分为三类:

  • 堆缓冲区,ByR , ) ) q % B xteBuf 将数据存储在 JVM 的堆中,通过数组实现,可以做到快速分配。由于在堆上被 JVM 管理,在不被使用时可以快速释放。可以通过 ByteBuf.array() 来获取 bye h X i c ( Qte 数据。
  • 直接缓冲区,在 JVM 的堆之^ 5 7 { . E u外直接分配内存,用来存储数据。其不占用堆空间,使用时需要考虑内存容量。它在使用 Socket 传递时性能较好,因为间接从缓冲区发送数据,4 _ # ^ H = 5在发送之前 JVM 会先将数据复制d v ]到直接% w @ P缓冲区B I - ? 8 N ) q x再进行发送。由于,直接缓冲区的数据分配在堆之外,) w d W + [ v通过 JVM 进行垃圾回收,并且分配时也需要做复制的操作,因此使用成本较高。
  • 复合缓冲区 { 3 K G,顾名思义就是将上述两类缓冲区聚合在一起。Netty 提供了一个 CompsiteByteBuf,可以将堆缓冲区和直接缓冲区的数据放在一起,让使用更加方便。

ByteBuf 的分配

聊完了结构和使用模式,再来看看 ByteBuf 是如何分配缓冲区的数据的。

Netty 提供了两种 ByteBufAllocator 的实( e ^ x )现,他们分别是:

  • PooledByteBufAllocator,实现了 ByteBuf 的对象的池化,提高性能减少内存碎片。
  • Unpooled-ByteBU [ C | i T SufAllocator,没有实现对象的池化,每次会生成新的对象实例。

对象池化的技术和线程池,比较相似,主要目o B z Q Z 3 X m的是提高内存的使用率。池化的简单实现思路,是在d ! n h b M ~ a JVM 堆内存上构A ! @ 9 ( a i [ E * l u s心M V 2 E / a组件配置到程序中O F * V s y j O *,并且让他们运行起来。

从 BootX N 4 : & j @strap 的继承结构来看,分为两类分别是 BootsZ i J N L ( - h,一个对应客户端的引导,另一个对应服务端的引导。

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

支持客户端和服务端的程序引导

客户端引导 Boo0 L # : ! [ N ~tstrap,主要有两个方法 bind() 和 connect()。Bootstrap 通过 bind() 方法创建一个 Channel。

在 bind() 之后,通过调用 connect() 方法来创建 Channel 连接。

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

Bootstrap 通过 bind 和 connect 方法创建连接

服务端引导 ServerBootstrap,与客户端不同的是在 Bind() 方法之后会创建一个 ServerChannel,它不仅会创建新的 Channel 还会管理已经存在的 Channel。

从 Spring Boot 程序启动深入理解 Netty 异步架构原理

ServerBootstrap 通过 bind 方法创建/管理连接

通过上面的描述,服务端和客户端的引导1 y j [ 7 A I ,存在两个区别:

  • ServerBootstrap(服务端引导)绑定一个端口,用来监听客户端的连接请求。而 Bootstrap(客户U * & f y | : h端引导)只要知道服务端 IP 和0 Q e Port 建立连接就可以了。
  • Bootstrap(客户端引导)需要一个 EventLoop: l K lGroup,@ K 7 6 D ?但是 ServerBootstrap(服务端引导)则需要两个 EventLoopGroup。因为服务器需要两组不同的 Channel。第一组 Ser_ ~ @ + #verChannel 自身监听本地端口的套接字。第二组用来监听客户端请求的套接字。e g 4 L w [ +
从 Spring Boot 程序启动深入理解 Netty 异步架构原理

ServerBoots0 H * Ctrap 有两组 Ee X E ? K H m NventLoopGroup


总结

我Q l w : M们从 NIO 入手,谈到了 Selector 的核心机制。然后通过介绍 Ned t ( 的事件,并且通知执行者。EventloopGroup 的容器,负责生成和管理 EventLoop。

ChannelPipeline 作为 ChannelHandler 的容器会绑定到 Channel 上,然后由 ChannelHandler 提供具体事件处理。另外,ChannelHa* m gndlerContext 为 ChannelHandler 和 ChannelPipeline 提供信息共享。

ByteBuf 作为 Netty 的数据容器,通过字节数组的N } 3 m方式存储数据,并且通过读索引和写索引来引导读写操作。+ 6 # ~ ^ K

上述的核心组件都是通过 Bootstrap 来配置并且引导启动的,Bootstrap 启动方式虽然一致,但是针对客户端和服务端有些许的区别。


来源:https://mp.weixin.qq.com/s/Sosyv2pRrd + Q g $ L s HB8ry471mk5w6g

原文《高性能底层怎么运作?一文帮你吃透NetL 2 % U C H 6ty架构原理》

侵删

会昌客户端等待组件线程调用服务端原理创建