Netty深度剖析(1)— 纵观全局

当前网络上讲Netty的文章已经汗牛充栋了,但成体系的并不多,多是讲解一些零散的知识点,或者又是关注的版本比较旧,一些设计在新版本中已经发生了变化。为此,我想花一些时间重新整理一份相对完善,紧跟当前最新版本的netty的学习系列文章,也顺便加深一下自身对netty的理解。这个系列的内容会涵盖netty的基本用法,技术架构,设计亮点等方面,欢迎有兴趣的小伙伴关注这个系列的更新,和我一起学习提高~

为啥要学习Netty

Netty 是Java领域一个非常重要的网络编程框架。可能有的小伙伴会有疑问,Java这么多框架,我平时的工作也不怎么涉及网络编程,为什么要学习Netty呢?这么说吧,Netty在Java网络编程领域的重要性,完全可以和Spring系列框架在应用开发中的统治地位相提并论,基本上你能够叫出名字的用java写的中间件,或者java版本的中间件客户端,基本内部都是使用netty作为网络通讯的实现的,包括Dubbo、RocketMQ、Elasticsearch、HBase、gRPC、Redisson等等。理解了Netty的基本原理才能更有效的对这些框架/中间件进行相应的配置调优,出现问题时也更容易排查。

而且,Netty作为Java网络编程的标杆,它的高性能设计让人印象非常深刻,线程调度,内存分配,对象池等方面Netty都封装了相应的高性能组件。原本Java相对于C,C++这类语言,在性能上是不占什么优势的,但是就是凭借着Netty的出色设计,至少在网络编程这部分Java已经能够和这些语言掰一掰手腕了。这些组件不仅仅可以用在网络编程上,在其它领域也是能够发挥重要作用的。可以说,了解了Netty的相关设计原理,对于高性能Java编程这一部分,你就已经能够达到一个不低的高度了。

所以不管你日常工作是不是与网络编程相关,只要你还在使用Java进行应用开发,那么大概率你的项目与Netty多多少少都会产生联系的,那还有啥理由不去学习呢。

Netty概览

”Talk is cheap. Show me the code“ 让我们还是先从一段代码开始吧:

EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
      .channel(NioServerSocketChannel.class)
      .childHandler(new ChannelInitializer<SocketChannel>() {
          @Override
          public void initChannel(SocketChannel ch) throws Exception {
              ch.pipeline().addLast(new SimpleChannelInboundHandler<ByteBuf>() {
                  @Override
                  protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
                      System.out.println("Received: " + msg.toString(CharsetUtil.UTF_8));
                  }

                  @Override
                  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                      cause.printStackTrace();
                      ctx.close();
                  }
              });
          }
      });
b.bind(8080).sync();

这段代码的主要作用就是创建一个TCP Server,将接受到的信息打印到控制台。功能虽然简单,但足以让我们了解Netty的一些核心组件。我们首先创建了两个EventLoopGroup,这个组件可以简单的理解成一个线程池组件,然后创建了负责整个TCP Server启动的ServerBootstrap,并为它指定了一系列配置。其中指定的两个group分别负责接收连接和处理连接的IO事件,而NioServerSocketChannel只是Channel组件的一种具体实现,这里是指定需要创建的Channel类型。而childHandler可以指定通道相关事件的处理方式,比如消息的收发,异常处理等等。

有了基本的概念以后,我们再来看看netty的逻辑架构。Netty 的逻辑处理架构为典型网络分层架构设计,共分为网络通信层、事件调度层、服务编排层,每一层各司其职,如下图:

在这里插入图片描述

网络通信层

这一层的主要职责是执行网络 I/O 的操作。它支持多种网络协议和 I/O 模型的连接操作。当网络数据读取到内核缓冲区后,会触发各种网络事件,这些网络事件会分发给事件调度层进行处理。

网络通信层的核心组件包含BootStrap、ServerBootStrap、Channel三个组件。

  • BootStrap & ServerBootStrap

Bootstrap 是“引导”的意思,它主要负责整个 Netty 程序的启动、初始化、服务器连接等过程,它相当于一条主线,串联了 Netty 的其他核心组件。

如下图所示,Netty 中的引导器共分为两种类型:一个为用于客户端引导的 Bootstrap,可以连接一个远端的Server;另一个为用于服务端引导的 ServerBootStrap,用于创建一个本地的Server,它们都继承自抽象类 AbstractBootstrap。

在这里插入图片描述

Bootstrap 和 ServerBootStrap 十分相似,区别主要是 ServerBootStrap 可以绑定两个 EventLoopGroup,这两个 EventLoopGroup 通常称为 Boss 和 Worker。

ServerBootStrap采用的是典型的Reactor模式,Boss Group负责接收连接,生成连接事件并派发给Worker Group,而连接具体的IO事件都是由Worker Group进行处理的。

有了 Bootstrap 组件,我们可以更加方便地配置和启动 Netty 应用程序,它是整个 Netty 的入口,串接了 Netty 所有核心组件的初始化工作。

  • Channel

Channel是通道的意思,可以看作netty对“网络连接”的抽象,它是网络通信的载体,提供了基本的 API 用于网络 I/O 操作,如 register、bind、connect、read、write、flush 等。Netty 自己实现的 Channel 是以 JDK NIO Channel 为基础的,相比较于 JDK NIO,Netty 的 Channel 提供了更高层次的抽象,同时屏蔽了底层 Socket 的复杂性,赋予了 Channel 更加强大的功能,你在使用 Netty 时基本不需要再与 Java Socket 类直接打交道。

Netty中有多种不同的Channel实现,每一种实现都代表了一种IO模型或者一种协议类型,从名称上还是比较容易区分它们的作用的。比如NioDatagramChannel 是基于Java NIO的 异步UDP连接实现,而EpollServerSocketChannel 是基于Epoll IO模型的 TCP Server端连接实现。

事件调度层

事件调度层的职责是聚合Channel产生的各种IO事件,并分发给对应的线程进行处理。事件调度层的核心组件包括 EventLoopGroup、EventLoop

它们两者的关系可以参考下图:

在这里插入图片描述

EventLoopGroup 其实就是一个线程池,而EventLoop就是它管理的线程。真正负责处理IO事件实际是EventLoop,每当一个Channel创建出来之后,与Channel相关联的EventLoopGroup就会从自己管理的EventLoop中找到一个与该Channel进行绑定,后续Channel产生的所有IO事件,如 accept、connect、read、write等等,都是交给这个EventLoop进行处理的。

EventLoopGroup  1 <----> n  EventLoop   1  <-----> 1  Channel

和Channel一样,EventLoopGroup也有很多不同的实现,主要是适配不同的IO模型。其中最常用的就是NioEventLoopGroup。需要注意的是,实际使用时EventLoopGroup的类型一般是和Channel的类型相匹配的,要保持IO模型的一致性。比如我们例子中使用的是NioEventLoopGroup,那么channel的实现类也应该用NioServerSocketChannel;如果是在支持Epoll的环境中,为了最大化性能,我们可以使用EpollEventLoopGroup搭配EpollServerSocketChannel来创建ServerBootstrap。

服务编排层

服务编排层的职责是负责组装各类服务,用以实现网络事件处理器的动态编排和有序传播。其核心组件包括 ChannelPipeline、ChannelHandler、ChannelHandlerContext。

  • ChannelPipeline & ChannelHandler

ChannelPipeline 采用了典型的责任链的设计模式,负责按顺序组装各种 ChannelHandler,实际数据的编解码以及加工处理操作都是由 ChannelHandler 完成的。当 I/O 读写事件触发时,ChannelPipeline 会依次调用 ChannelHandler 列表对 Channel 的数据进行拦截和处理,如下图所示:
在这里插入图片描述

从上面也能够看出ChannelHandler实际上还分了入站 ChannelInboundHandler 和出站 ChannelOutboundHandler两种,这个其实还是比较好理解的,比如对于客户端来说,数据发往服务端那就叫在出站,而接收到服务端的数据则叫做入站,服务端也是类似的。通过这种类似双向链表的结构,ChannelPipeline就能够提供非常灵活,强大的处理逻辑编排能力。

ChannelPipeline 是线程安全的,因为刚才也说到过,每一个新的 Channel 同一时间只会绑定一个EventLoop, 意味着这个Channel的ChannelPipeline都是在这个EventLoop中执行的,所以一定是线程安全的。

  • ChannelHandlerContext

ChannelPipeline 会为每个新加入的 ChannelHandler 都会绑定一个单独的 ChannelHandlerContext 实例。它的主要作用有两个:

  1. 封装一些所有ChannelHandler都需要使用的通用逻辑实现,比如触发各种通道的IO事件,执行读,写,flush等,这样才会让ChannelPipeline继续向前推进到下一个ChannelHandler;

  2. ChannelHandler可以通过Context访问与之关联的Channel和EventLoop的相关信息;

总结

本次主要还是简单梳理了一下Netty架构的大致脉络以及一些核心组件的基本概念,我们最后再用一张图来总结Netty的内部逻辑的扭转过程吧:

在这里插入图片描述

在这里插入图片描述
欢迎关注我的公号—飞空之羽的技术手札,阅读更多有深度的技术好文~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值