【Netty】模型篇五:任务队列 taskQueue

前面两篇文章提到了 BossGroup 和 WorkerGroup ,它们的类型是 NioEventLoopGroup,NioEventLoopGroup 相当于一个 事件循环组,这个组中 含有多个事件循环 ,每一个事件循环是 NioEventLoop,而NioEventLoop 中有两个重要属性SelectorTaskQueue那么这个 TaskQueue,即任务队列,有什么作用呢?

我们前面分析了在 事件循环(NioEventLoop) 的过程中,我们会在 pipline 中调用 Handler 来处理我们的业务,那么假如在某一个 Handler有一个长时间的操作,这就势必会造成 pipiline 的阻塞,这是我们就可以将这个耗时的处理提交到 TaskQueue 进行异步的执行。

1 TaskQueue 的3种典型使用场景

  • 场景一:用户程序自定义的普通任务
  • 场景二:用户自定义定时任务
  • 场景三:非当前 Reactor 线程调用 Channel 的各种方法。
    • 例如在 推送系统 的业务线程里面,根据 用户的标识,找到对应的 Channel 引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。最终的 Write 会提交到任务队列中后被 异步消费

2 代码演示

2.1 问题分析

首先我们 在 NettyServerHandler 的 channelRead() 方法中模拟一下耗时的业务:

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
	    // 比如这里我们有一个非常耗时的业务,我们想让它异步的执行
	    // 只需要将它提交到该 channel 对应的 NioEventLoop 的 TaskQueue 中
	    Thread.sleep(5 * 1000);// 模拟耗时业务
	    // 执行完业务向客户端返回消息
	    ctx.writeAndFlush(Unpooled.copiedBuffer("耗时业务执行完毕...",CharsetUtil.UTF_8));
	    System.out.println("go on...");
	}
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    
    
	    // writeAndFlush 是 write + flush
	    // 将数据写入到缓存,并刷新
	    // 一般我们会对这个发送的数据进行编码
	    ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端...",CharsetUtil.UTF_8));
	}
}

此时客户端必然是等待 5秒 后才能收到消息:耗时业务执行完毕…;然后再收到消息:hello,客户端…。

2.2 解决方案一:用户程序自定义的普通任务

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
    // 比如这里我们有一个非常耗时的业务,我们想让它异步的执行
    // 只需要将它提交到该 channel 对应的 NioEventLoop 的 TaskQueue 中

    // 解决方案1:用户程序自定义的普通程序
    ctx.channel().eventLoop().execute(new Runnable() {
    
    
        public void run() {
    
    
            try {
    
    
                Thread.sleep(5 * 1000);// 模拟耗时业务
                // 执行完业务向客户端返回消息
                ctx.writeAndFlush(Unpooled.copiedBuffer("耗时业务执行完毕...",CharsetUtil.UTF_8));
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    });

    System.out.println("go on...");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    
    
   // writeAndFlush 是 write + flush
   // 将数据写入到缓存,并刷新
   // 一般我们会对这个发送的数据进行编码
   ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端...",CharsetUtil.UTF_8));
}

这是我们启动程序发现:首先收到消息:hello,客户端…;然后过 5秒 收到消息:耗时业务执行完毕…
在这里插入图片描述
验证也很简单,Debug查看:ctx ==》 pipline ==》channel 》eventLoop》taskQueue,看一下 taskQueue 的size就行了。

2.3 解决方案二:用户自定义定时任务

注意:这种解决方案任务是提交到 scheduleTaskQueue 中。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
    // 比如这里我们有一个非常耗时的业务,我们想让它异步的执行
    // 只需要将它提交到该 channel 对应的 NioEventLoop 的 TaskQueue 中

    // 解决方案2:用户自定义定时任务,该任务提交到 scheduleTaskQueue 中
    ctx.channel().eventLoop().schedule(new Runnable() {
    
    
       @Override
       public void run() {
    
    
           try {
    
    
               Thread.sleep(5 * 1000);// 模拟耗时业务
               // 执行完业务向客户端返回消息
               ctx.writeAndFlush(Unpooled.copiedBuffer("耗时业务执行完毕...",CharsetUtil.UTF_8));
           } catch (InterruptedException e) {
    
    
               e.printStackTrace();
           }
       }
    },5, TimeUnit.SECONDS);
    System.out.println("go on...");
}

验证也很简单,Debug查看:ctx ==》 pipline ==》channel 》eventLoop》scheduleTaskQueue,看一下 scheduleTaskQueue 的 size 就行了。
在这里插入图片描述

3 方案再说明

  • Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。
  • NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个selector,用于监听绑定在其上的 socket 网络通道。
  • NioEventLoop 内部采用串行化设计,从消息的 读取 ==》解码 ==》处理 ==》编码 ==》发送,始终由IO 线程 NioEventLoop 负责
  • NioEventLoopGroup 下包含多个 NioEventLoop,每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue,每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel,每个 NioChannel 只会绑定在唯一的 NioEventLoop 上,每个 NioChannel 都绑定有一个自己的 ChannelPipeline。

猜你喜欢

转载自blog.csdn.net/qq_36389060/article/details/124495400