Netty架构原理,不怕你看不懂

对于高性能的 RPC 框架,Netty 作为异步通信框架,几乎成为必备品。例如,Dubbo 框架中通信组件,还有 RocketMQ 中生产者和消费者的通信,都使用了 Netty。今天,我们来看看 Netty 的基本架构和原理。

想要了解更多Java架构技术的,可以关注我一下,我后续也会整理更多关于架构技术这一块的知识点分享出来,里面会分享一些:spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化,并发编程这些成为架构师必备的知识体系.

Netty 的特点与 NIO

Netty 是一个异步的、基于事件驱动的网络应用框架,它可以用来开发高性能服务端和客户端。

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

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

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

使用阻赛I/O 处理多个连接

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

当每次客户端请求时,会创建一个 Socket Channel,并将其注册到 Selector 上(多路复用器)。

然后,Selector 关注服务端 IO 读写事件,此时客户端并不用等待 IO 事件完成,可以继续做接下来的工作。

一旦,服务端完成了 IO 读写操作,Selector 会接到通知,同时告诉客户端 IO 操作已经完成。

接到通知的客户端,就可以通过 SocketChannel 获取需要的数据了。

NIO 机制与 Selector

上面描述的过程有点异步的意思,不过,Selector 实现的并不是真正意义上的异步操作。

因为 Selector 需要通过线程阻塞的方式监听 IO 事件变更,只是这种方式没有让客户端等待,是 Selector 在等待 IO 返回,并且通知客户端去获取数据。真正“异步 IO”(AIO)这里不展开介绍,有兴趣可以自行查找。

说好了 NIO 再来谈谈 Netty,Netty 作为 NIO 的实现,它适用于服务器/客户端通讯的场景,以及针对于 TCP 协议下的高并发应用。

对于开发者来说,它具有以下特点:

对 NIO 进行封装,开发者不需要关注 NIO 的底层原理,只需要调用 Netty 组件就能够完成工作。

对网络调用透明,从 Socket 建立 TCP 连接到网络异常的处理都做了包装。

对数据处理灵活, Netty 支持多种序列化框架,通过“ChannelHandler”机制,可以自定义“编/解码器”。

对性能调优友好,Netty 提供了线程池模式以及 Buffer 的重用机制(对象池化),不需要构建复杂的多线程模型和操作队列。

从一个简单的例子开始

开篇讲到了,为了满足高并发下网络请求,引入了 NIO 的概念。Netty 是针对 NIO 的实现,在 NIO 封装,网络调用,数据处理以及性能优化等方面都有不俗的表现。

学习架构最容易的方式就是从实例入手,从客户端访问服务端的代码来看看 Netty 是如何运作的。再一次介绍代码中调用的组件以及组件的工作原理。

假设有一个客户端去调用一个服务端,假设服务端叫做 EchoServer,客户端叫做 EchoClient,用 Netty 架构实现代码如下。

服务端代码

构建服务器端,假设服务器接受客户端传来的信息,然后在控制台打印。首先,生成 EchoServer,在构造函数中传入需要监听的端口号。

构造函数中传入需要监听的端口号

接下来就是服务的启动方法:

启动 NettyServer 的 Start 方法

Server 的启动方法涉及到了一些组件的调用,例如 EventLoopGroup,Channel。这些会在后面详细讲解。

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

创建 EventLoopGroup。

创建 ServerBootstrap。

指定所使用的 NIO 传输 Channel。

使用指定的端口设置套接字地址。

添加一个 ServerHandler 到 Channel 的 ChannelPipeline。

异步地绑定服务器;调用 sync() 方法阻塞等待直到绑定完成。

获取 Channel 的 CloseFuture,并且阻塞当前线程直到它完成。

关闭 EventLoopGroup,释放所有的资源。

NettyServer 启动以后会监听某个端口的请求,当接受到了请求就需要处理了。在 Netty 中客户端请求服务端,被称为“入站”操作。

可以通过 ChannelInboundHandlerAdapter 实现,具体内容如下:

处理来自客户端的请求

从上面的代码可以看出,服务端处理的代码包含了三个方法。这三个方法都是根据事件触发的。

他们分别是:

当接收到消息时的操作,channelRead。

消息读取完成时的方法,channelReadComplete。

出现异常时的方法,exceptionCaught。

客户端代码

客户端和服务端的代码基本相似,在初始化时需要输入服务端的 IP 和 Port。

need-to-insert-img

同样在客户端启动函数中包括以下内容:

need-to-insert-img

客户端启动程序的顺序:

创建 Bootstrap。

指定 EventLoopGroup 用来监听事件。

定义 Channel 的传输模式为 NIO(Non-BlockingInputOutput)。

设置服务器的 InetSocketAddress。

在创建 Channel 时,向 ChannelPipeline 中添加一个 EchoClientHandler 实例。

连接到远程节点,阻塞等待直到连接完成。

阻塞,直到 Channel 关闭。

关闭线程池并且释放所有的资源。

客户端在完成以上操作以后,会与服务端建立连接从而传输数据。同样在接受到 Channel 中触发的事件时,客户端会触发对应事件的操作。

need-to-insert-img

例如 Channel 激活,客户端接受到服务端的消息,或者发生异常的捕获。

从代码结构上看还是比较简单的。服务端和客户端分别初始化创建监听和连接。然后分别定义各自的 Handler 处理对方的请求。

need-to-insert-img

服务端/客户端初始化和事件处理

Netty 核心组件

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

①Channel

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

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

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

②EventLoop 和 EventLoopGroup

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

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

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

这个机制(组件)就是 EventLoop。在 Netty 中每个 Channel 都会被分配到一个 EventLoop。一个 EventLoop 可以服务于多个 Channel。

每个 EventLoop 会占用一个 Thread,同时这个 Thread 会处理 EventLoop 上面发生的所有 IO 操作和事件(Netty 4.0)。

need-to-insert-img

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值