Netty 任务异步线程池源码剖析

23 篇文章 2 订阅

在 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 都交给业务线程池。 不论耗时不耗时,都会加入到队列里,不够灵活。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值