10-Netty 高性能架构设计-任务队列(普通任务, 定时任务)
TaskQueue 任务队列
任务队列中的Task有三种典型使用场景
- 用户程序自定义的普通任务 [举例说明]
- 用户自定义定时任务
- 非当前Reactor线程调用Channel的各种方法
- 例如在推送系统的业务线程里面, 根据用户的标识, 找到对应的Channel引用,然后调用Write类方法向该用户推送消息, 就会进入到该场景,最终的write会提交到任务队列中后被异步消费
问题
如果在读取或者操作时碰到非常耗时的业务, 那么他就会阻塞在业务执行的地方,我们希望他可以异步执行,而不是阻塞事件, 我们可以提交到该Channel对应的NioEventLoop的taskQueue中
修改NettyServerHandler的ChannelRead方法
Thread.sleep(10 * 1000); ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端 我读取完成喽", StandardCharsets.UTF_8)); System.out.println("go on....");
这样测试,我们服务器端,channelRead方法就会阻塞10秒才返回
用户自定义普通任务
使用用户自定义普通任务解决
ctx.channel().eventLoop().execute(new Runnable() { @Override public void run() { try { Thread.sleep(10 * 1000); ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端 我读取完成喽", StandardCharsets.UTF_8)); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("发生异常"); } } });
通过上下文对象获取Channel,获取EventLoop,并提交一个任务
测试可以,这样的话,ChannelRead事件不会阻塞,并且任务是在10秒后返回的
但是这里如果是多任务那么时间是累加的
// 应为是任务队列所以第一个任务返回是延迟10秒 ctx.channel().eventLoop().execute(new Runnable() { @Override public void run() { try { Thread.sleep(10 * 1000); ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端 我读取完成喽", StandardCharsets.UTF_8)); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("发生异常"); } } }); // 第二个任务返回是 上面的 10 + 下面的 20 = 30 秒 ctx.channel().eventLoop().execute(new Runnable() { @Override public void run() { try { Thread.sleep(20 * 1000); ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端 我读取完成喽", StandardCharsets.UTF_8)); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("发生异常"); } } });
用户自定义定时任务
// 用户自定义定时任务 ctx.channel().eventLoop().schedule(() -> { try { Thread.sleep(10 * 1000); ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端 我读取完成喽", StandardCharsets.UTF_8)); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("发生异常"); } }, 5, TimeUnit.SECONDS);
延时5秒
执行测试 ok
那么普通任务和延时任务并存,那么他的执行时间如何呢?
经过测试一个普通任务10秒,一个定时任务5秒+执行10秒, 总共会在20秒执行完成,在10秒返回一个, 在20秒返回一个
预测普通任务和延时任务同时开始, 普通任务开始执行,延时任务开始倒计时,在延时任务倒计时5秒后进入等待,而普通任务10秒执行完成后延时任务直接开始执行,所以在20秒返回
方案再说明
- Netty抽象出两组线程池, BOSSGroup专门负责接收客户端连接, WorkerGroup专门负责网络读写操作
- NioEventLoop表示一个不断循环执行处理任务的线程, 每个NioEventLoop都有一个Selector, 用于监听绑定在其上的Socket网络通道
- NioEventLoop内部采用串行化设计, 从消息的读取->解码->处理->编码->发送,始终由IO线程NioEventLoop负责
- NioEventLoopGroup 下包含多个NioEventLoop
- 每个NioEventLoop中都包含一个Selector,和一个TaskQueue
- 每个NioEventLoop的selector上可以注册监听多个NioChannel
- 每个NioChannel只会绑定在唯一的NioEventLoop上
- 每个NioChannel都绑定有一个自己的ChannelPipeline