在 Netty 中做耗时的,不可预料的操作,比如数据库,网络请求,会严重影响 Netty 对 Socket 的处理速度, 而解决方法就是将耗时任务添加到异步线程池中。
- handler 中加入线程池
- Context 中添加线程池
handler 中加入线程池:
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
public class Handler extends ChannelInboundHandlerAdapter {
// group 就是充当业务线程池, 可以将任务提交到该线程池中
static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Handler 的线程是=" + Thread.currentThread().getName());
// 将任务提交到 group 线程池
group.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
// 接收客户端消息
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
// 休眠 10s
Thread.sleep(10 * 1000);
System.out.println("group submit 的 call 线程是=" + Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, netty".getBytes(StandardCharsets.UTF_8)));
return null;
}
});
group.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
// 接收客户端消息
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
// 休眠 10s
Thread.sleep(10 * 1000);
System.out.println("group submit 的 call 线程是=" + Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, netty".getBytes(StandardCharsets.UTF_8)));
return null;
}
});
group.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
// 接收客户端消息
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
// 休眠 10s
Thread.sleep(10 * 1000);
System.out.println("group submit 的 call 线程是=" + Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, netty".getBytes(StandardCharsets.UTF_8)));
return null;
}
});
System.out.println("go on");
}
}
- 当 I/O 线程轮询到一个 socket 事件,然后,I/O 线程开始处理,当走到耗时 handler 的时候,将耗时任务交给业务线程池
- 当耗时任务执行完毕再执行 pipeline write 方法的时候,会将这个任务还给 I/O 线程
write 方法的源码(在 AbstractChannelHandlerContext 类):
private void write(Object msg, boolean flush, ChannelPromise promise) {
ObjectUtil.checkNotNull(msg, "msg");
try {
if (isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);
// cancelled
return;
}
} catch (RuntimeException e) {
ReferenceCountUtil.release(msg);
throw e;
}
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
// 当判定下一个outbound 的 executor 线程不是当前线程的时候,会将当前的工作封装成 task,然后放入 mpsc 队列中,等待 I/O 任务执行完毕后执行队列中的任务
final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
if (!safeExecute(executor, task, promise, m, !flush)) {
task.cancel();
}
}
}
Context 中添加线程池:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
/**
* Echoes back any received data from a client.
*/
public final class EchoServer {
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
// 创建业务线程池
static final EventExecutorGroup group = new DefaultEventExecutorGroup(2);
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx = ServerUtil.buildSslContext();
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
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(new LoggingHandler(LogLevel.INFO));
// p.addLast(serverHandler);
// 如果我们在addLast 添加 handler 前面执行 EventExecutorGroup, 那么该 Handler 会优先加入到该线程池中
p.addLast(group, serverHandler);
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
AbstractChannelHandlerContext invokeChannelRead:
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
// 加入线程池后,会走异步run方法
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
两种方式比较:
- 第一种方式在 handler 中添加异步,可能更加的自由,比如如果需要访问数据库,那就异步,如果不需要,就不异步,异步会拖长接口响应时间。因为需要将任务放进 mpscTask 中。如果 I/O 时间短,task 很多,可能一个循环下来,都没有时间执行整个 task,导致相应时间达不到指标。
- 第二种方式是 Netty 标准方式(即加入到队列),但是,这样做会将整个 handler 都交给业务线程池。 不论耗时不耗时,都会加入到队列里,不够灵活。