在 Netty 中做耗时的,不可预料的操作,比如数据库,网络请求,会严重影响 Netty 对 Socket 的处理速度。
而解决方法就是将耗时任务添加到异步线程池中。但就添加线程池这步操作来讲,可以有 2 种方式,而且这 2种方式实现的区别也蛮大的
方式一:在handler中加入线程池
static final EventExecutorGroup GROUP = new DefaultEventExecutorGroup(16);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
GROUP.submit(() -> {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String s = new String(req, StandardCharsets.UTF_8);
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(s);
String reqs = "hello";
ByteBuf resp = Unpooled.copiedBuffer(reqs.getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(resp);
return null;
});
System.out.println("go");
}
说明:
1.在ChannelRead方法中,模拟了一个耗时10s的操作,这里我们将这个任务提交到一个自定义的业务线程池中,这样就不会阻塞Netty的IO线程
逻辑图如下
说明:
1.解释上图:当IO线程轮询到一个socket事件,然后,IO线程开始处理,当走到耗时handler的时候,将任务交给业务线程池
2.当耗时任务执行完毕后再执行pipeline write 方法时,会将这个任务交给IO线程
write方法说明
1.当判定下个outbound 的executor线程不是当前线程的时候,会将当前的任务封装成为task,然后放入mpsc队列中,等待IO任务执行完毕后执行队列中的任务
方式二:在context中添加线程池
在pipeline中添加handler的时候,添加一个线程池
static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(group, new EchoServerHandler());
}
});
说明:
1.handler 中的代码就是使用普通的方式来处理耗时任务
2.当我们调用addLast方法添加线程池后,handler将优先使用这个线程池,如果不添加,将使用IO线程
两种方式的比较
1.第一种方式在handler种添加异步,可能更加自由,比如如果需要访问数据库等操作,就进行异步,如果不需要,就不异步,异步会拖长接口响应时间。因为需要将任务放进mpscTask种,如果IO时间很短,,task很多,可能一个循环下来,都没时间执行整个task,导致响应时间不达标
2.第二种方式是Netty标准方式(即加入到队列),但是,这么做会将整个handler都交给业务线程池,不论是否耗时,都会加入到队列中,不够灵活