写点什么

netty 系列之:netty 架构概述

发布于: 21 小时前

简介 Netty 为什么这么优秀,它在 JDK 本身的 NIO 基础上又做了什么改进呢?它的架构和工作流程如何呢?请走进今天的 netty 系列文章之:netty 架构概述。


netty 架构图 netty 的主要作用就是提供一个简单的 NIO 框架可以和上层的各种协议相结合,最终实现高性能的服务器。下面是 netty 官网提供的架构图:


从上图可以看到 netty 的核心主要分成三部分,分别是可扩展的 event model、统一的 API、和强大的 Byte Buffer。这三个特性是 netty 的制胜法宝。


下面会从这几个方面对 netty 的特性进行详细说明,务必让读者了解到 netty 的优秀之处。


丰富的 Buffer 数据机构首先从最底层的 Buffer 数据结构开始,netty 提供了一个 io.netty.buffer 的包,该包里面定义了各种类型的 ByteBuf 和其衍生的类型。


netty Buffer 的基础是 ByteBuf 类,这是一个抽象类,其他的 Buffer 类基本上都是由该类衍生而得的,这个类也定义了 netty 整体 Buffer 的基调。


netty 重写 ByteBuf 其目的是让最底层的 ByteBuf 比 JDK 自带的更快更适合扩展。具体而言,netty 的 ByteBuf 要比 JDK 中的 ByteBuffer 要快,同时,扩展也更加容易,大家可以根据需要 Buf 进行自定义。另外 netty 有一些内置的复合缓冲区类型,所以可以实现透明的零拷贝。对于动态缓冲区类型来说,可以和 StringBuffer 一样按需扩展,非常好用。


零拷贝什么是零拷贝呢?零拷贝的意思是在需要拷贝的时候不做拷贝。我们知道数据在使用底层协议进行传输的过程中是会被封装成为一个个的包进行传输。当传输的数据过大,一个包放不下的时候,还需要对数据进行拆分,目的方在接收到数据之后,需要对收到的数据进行组装,一般情况下这个组装的操作是对数据的拷贝,将拆分过后的对象拷贝到一个长的数据空间中。


比如下面的例子所示,将底层的 TCP 包组合称为顶层的 HTTP 包,但是并没有进行拷贝:


具体怎么拷贝呢?在上一篇文章中,我们知道 netty 提供了一个工具类方法 Unpooled,这个工具类中有很多 wrapped 开头的方法,我们举几个例子:


public static ByteBuf wrappedBuffer(byte[]... arrays) {return wrappedBuffer(arrays.length, arrays);}


public static ByteBuf wrappedBuffer(ByteBuf... buffers) {return wrappedBuffer(buffers.length, buffers);}


public static ByteBuf wrappedBuffer(ByteBuffer... buffers) {return wrappedBuffer(buffers.length, buffers);}上面三个方法分别是封装 byte 数组、封装 ByteBuf 和封装 ByteBuffer,这些方法都是零拷贝。大家可以在实际的项目中根据实际情况,自行选用。


统一的 API 一般来说,在传统的 JDK 的 IO API 中,根据传输类型或者协议的不同,使用的 API 也是不同的。我们需要对不同的传输方式开发不同的应用程序,不能做到统一。这样的结果就是无法平滑的迁移,并且在程序扩展的时候需要进行额外的处理。


什么是传输方式呢?这里是指以什么样的方式来实现 IO,比如传统的阻塞型 IO,我们可以称之为 OIO,java 的 new IO 可以称之为 NIO,异步 IO 可以称之为 AIO 等等。


并且 JDK 中的传统 IO 和 NIO 是割裂的,如果在最开始你使用的是传统 IO,那么当你的客户数目增长到一定的程度准备切换到 NIO 的时候,就会发现切换起来异常复杂,因为他们是割裂的。


为了解决这个问题,netty 提供了一个统一的类 Channel 来提供统一的 API。


先看下 Channel 中定义的方法:


从上图我们可以看到使用 Channel 可以判断 channel 当前的状态,可以对其进行参数配置,可以对其进行 I/O 操作,还有和 channel 相关的 ChannelPipeline 用来处理 channel 关联的 IO 请求和事件。


使用 Channel 就可以对 NIO 的 TCP/IP,OIO 的 TCP/IP,OIO 的 UDP/IP 和本地传输都能提供很好的支持。


传输方式的切换,只需要进行很小的成本替换。


当然,如果你对现有的实现都不满意的话,还可以对核心 API 进行自定义扩展。


事件驱动 netty 是一个事件驱动的框架,事件驱动框架的基础就是事件模型,Netty 专门为 IO 定义了一个非常有效的事件模型。可以在不破坏现有代码的情况下实现自己的事件类型。netty 中自定义的事件类型通过严格的类型层次结构跟其他事件类型区分开来,所以可扩展性很强。


netty 中的事件驱动是由 ChannelEvent、ChannelHandler 和 ChannelPipeline 共同作用的结果。其中 ChannelEvent 表示发生的事件,ChannelHandler 定义了如何对事件进行处理,而 ChannelPipeline 类似一个拦截器,可以让用户自行对定义好的 ChannelHandler 进行控制,从而达到控制事件处理的结果。


我们看一个简单的自定义 Handler:


public class MyHandler extends SimpleChannelInboundHandler<Object> {


@Overridepublic void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {    // 对消息进行处理    ByteBuf in = (ByteBuf) msg;    try {        log.info("收到消息:{}",in.toString(io.netty.util.CharsetUtil.US_ASCII));    }finally {        ReferenceCountUtil.release(msg);    }}
@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { //异常处理 cause.printStackTrace(); ctx.close();}
复制代码


}上面的例子中,我们定义了如何对接收到的消息和异常进行处理。在后续的文章中我们会详细对 ChannelEvent、ChannelHandler 和 ChannelPipeline 之间的交互使用进行介绍。


其他优秀的特性除了上面提到的三大核心特性之外,netty 还有其他几个优点,方便程序员的开发工作。


比如对 SSL / TLS 的支持,对 HTTP 协议的实现,对 WebSockets 实现和 Google Protocol Buffers 的实现等等,表明 netty 在各个方面多个场景都有很强的应用能力。


总结 netty 是由三个核心组件构成:缓冲区、通道和事件模型,通过理解这三个核心组件是如何相互工作的,那么再去理解建立在 netty 之上的高级功能就不难了。


本文的例子可以参考:learn-netty4


本文已收录于 http://www.flydean.com/03-netty-architecture/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 21 小时前阅读数: 4
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:netty架构概述