深度了解Netty

很多情况下,我们只是会使用市面上优秀的框架,例如:Spring全家桶、Dubbo、HttpClient、Netty等。但却不知道这些框架底层的设计思想,也不会去阅读框架的源码。在使用上遇到问题的时候,也只是胡乱百度。

最近在工作中,接触网络I/O比较多。背景:使用Socket编写客户端,发送TCP自定义协议。在做性能测试时,TPS指标始终不如人意。也尝试过使用Netty,但是在压测时,会出现OOM情况。既然Netty是市面上公认的优秀的IO框架,那么我带着这个问题,尝试性的打开Netty源码进行阅读,通过Debug方式,查看调用栈,一步一步排查出影响问题所在。

为何要深度去理解一款框架:

  • 学习前辈的编码习性,命名规范,注释规范。
  • 理解框架中代码的设置模式。
  • 有助于在日常工作中,提升编码效率与质量。

本文以Netty的NIO进行举例讲解,在阅读本文前,需要对java-NIO和Netty有一定的使用经验与了解。


Netty分为五个模块,每个模块都负责相应的功能,模块之间也会存在依赖性。以下为Netty的模块

  1. bootstrap Netty服务启动项,启动客户端或服务端
  2. bufferjava.nio.ByteBuffer进行封装,提供更简洁的操作API,IO操作以及编解码都是通过buffer模块进行读写
  3. channeljava.nio.channels.Channel进行封装,底层依然是java提供的Channel,在应用层开发接触的是Netty的Channel
  4. handler 通道读写处理器,Netty已内置了几个,只需要编写编解码处理器
  5. util 工具包,为Netty服务提供工具类、日志处理、并发包执行器

模块以及模块下的组件

挑选重要的组件进行了解

util

包路径:io.netty.util.concurrent

异步操作

  • Future Netty所有操作皆为异步的,例如:启动服务/发起连接/注册通道
  • Promise Future包装类,为Future操作结果赋值

并发处理

  • EventExecutor 通道事件的执行器,继承java.util.concurrent.ExecutorService
  • EventExecutorGroup 管理EventExecutor

channel 核心

包路径:io.netty.channel

通道

  • Channel 通道,表示一个连接或者服务端
  • ChannelConfig 通道相关的配置

异步结果

  • ChannelFuture 继承util包下的Future
  • ChannelPromise 继承util包下的Promise

读取事件循环执行器

  • EventLoop 循环检查&执行通道的事件,事件类型:READ、WRITE、CONNECT、ACCEPT
  • EventLoopGroup 管理EventLoop

通道IO回调处理器

  • ChannelPipeline 处理通道事件的链,将事件结果回调至ChannelHandler,例如:绑定/连接/IO,责任链模式
  • ChannelHandlerContext 当前通道的上下文处理器,调用handler回调方法
  • ChannelHandler 通道事件回调处理器的顶级接口
  • ChannelOutboundHandler 继承ChannelHandler,处理通道事件
  • ChannelInboundHandler 继承ChannelHandler,处理通道事件

初始化

  • ChannelInitializer 继承ChannelInboundHandler,处理通道初始化事件,实现了handlerAdded

NIO

  • NioEventLoop 继承EventLoop处理NIO通道IO事件
  • NioEventLoopGroup 继承EventLoopGroup,管理NioEventLoop
  • NioSocketChannel 包装java.nio.channels.SelectableChannel

handler

包路径:io.netty.handler

编解码的核心两个基类,都继承ChannelHandler,也就是都是回调处理器

  • ByteToMessageDecoder 读取通道字节,转换为对象。
  • MessageToByteEncoder 将对象转换为字节,写入通道

buffer

包路径:io.netty.buffer

  • ByteBuf buffer的顶级基类,提供了writeBytes(InputStream in, int length)writeBytes(ScatteringByteChannel in, int length)setBytes(int index, InputStream in, int length)setBytes(int index, ScatteringByteChannel in, int length)对通道的消息进行读取
  • ByteBufAllocator 分配buffer

bootstrap

包路径:io.netty.bootstrap

  • ServerBootstrap 服务端启动
  • Bootstrap 客户端启动

模块-组件的UML类图(继承关系/依赖关系)

异步操作结果:Futrue,Promise

Netty中基本所有的操作都是异步的,既然是异步的,那么需要获取到操作结果,那就需要用到Futrue
操作项有:绑定端口(bind)、建立连接(connect)、关闭连接(disconnect)、关闭通道(close)、写数据(write)等…

大部分的操作都是返回DefaultChannelPromise,通过isSuccesscause判断成功或失败。

在这里插入图片描述

事件执行器

通道事件执行器的顶级接口,定义事件执行器的抽象方法,且制定好模板方法,在模板方法中再调用子类实现的抽象方法。
具备有事件执行器组的理念,负责管理执行器。

  • EventExecutor执行器的接口,该接口的实现类处理事件执行
  • EventExecutorGroup管理EventExecutor,类似于线程池,由他的实现类创建和获取EventExecutor实例。

在这里插入图片描述

通道操作&通道IO事件执行器

  • EventLoop事件执行器的顶级接口,处理通道的IO事件
  • EventLoopGroup管理EventLoop,提供创建和获取EventLoop实例

NIO举例

  • NioEventLoopGroup管理NioEventLoop
  • NioEventLoop NIO通道的事件执行器

在启动Netty服务时传入的bossGroupworkerGroup就是EventLoopGroup的实现类

new ServerBootstrap().group(bossGroup, workerGroup)

在这里插入图片描述

NIO 设计图

其它IO也是和这个设计一样,区别于通道事件的实现

  1. 创建NioServerSocketChannel实例,并通过BossGroup注册至某个NioEventLoop实例上。
  2. 每一个NioEventLoop实例都有各自的Selector,接收到注册通道事件时,将其再注册至Selector
  3. NioEventLoop的线程轮询Selector,查询IO事件,并处理事件。
  4. IO事件再由ChannelPipeline链式调用ChannelHandler
    在这里插入图片描述

内部流程图

服务端:ServerBootstrap

继承AbstractBootstrap,因为启动客户端的Bootstrap也是继承它,所以仅提供服务端和客户端共有的方法实现。

启动服务端

代码如下,可查阅相关注释

  1. 创建ServerBootstrap实例
  2. 设置boos,worker事件执行器组:EventLoopGroup
  3. 设置通道类型:Channel
  4. 设置NIO_SOCKET参数
  5. 设置buffer分配器:默认是:UnpooledByteBufAllocator非池化buffer分配器
  6. 设置在每次读取通道数据时, 分配多大空间的buffer,默认是:AdaptiveRecvByteBufAllocator自适应buffer分配器基于读取到的字节数,动态调整分配buffer大小
  7. 设置childHandler客户端IO事件处理器,当每次有新的连接时,调用该方法进行初始化通道的Handler
// 创建ServerBootstrap实例
        this.bootstrap = new ServerBootstrap();

        // 服务端监听客户端连接的事件组, 只需要一个线程数即可. 只会创建一个服务端, 也只会注册到一个NioEventLoop上
        bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("boss"));

        // 处理客户端IO事件组,
        workerGroup = new NioEventLoopGroup(10, new DefaultThreadFactory("worker"));

        this.bootstrap.group(bossGroup, workerGroup)
                // 启动所使用的通道实现类, 会使用反射进行实例化
                .channel(NioServerSocketChannel.class)
                // NIO_SOCKET 参数
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)

                /**
                 * Buffer分配器, PooledByteBufAllocator 池化Buffer
                 */
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)

                /**
                 * 在每次读取通道数据时, 分配多大空间的buffer, 固定1024
                 * 参考: {@link io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read()}
                 */
                .childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(1024))

                // 设置客户端通道IO事件回调, 当有新的通道建立连接时, 调用initChannel方法, 初始化Handler链
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // 往通道Handler链上添加自定义的处理器, 编解码器,
                        ch.pipeline()
                                .addLast("handler", new MyHandler());
                    }
                });

        // 绑定端口, IO操作返回Future
        ChannelFuture channelFuture = this.bootstrap.bind(9000);

        // 同步等待结果, 如果发生InterruptedException, 则中断当前线程:Thread.currentThread().interrupt();
        channelFuture.syncUninterruptibly();

        if (channelFuture.isSuccess()) {
            System.out.println("服务启动成功");
        } else {
            // 失败原因
            channelFuture.cause().printStackTrace();
        }

        // 获取通道
        channel = channelFuture.channel();

服务端绑定端口

前文有提到Netty的绑定端口事件是异步,下图说明绑定端口的事件流程图

同步

同步操作步骤,就是将异步回调配置好

  1. 反射创建NioServerSocketChannel实例
  2. 配置通道的optionattr
  3. 在通道处理器链上添加ChannelInitializer,当有客户端建立连接时,回调该处理器。
  4. 将通道注册至EventLoop

在这里插入图片描述

异步

将Channel注册至Selector,并监听端口

在这里插入图片描述

其他IO操作失败大同小异

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Me_Liu_Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值