DefaultEventExecutorGroup characteristics Netty- Failure Cases of concurrent

To improve performance, if the user to achieve ChannelHandler contain or may lead to complex business logic synchronous blocking, often need to improve concurrency through the thread pool, thread pool is added, there are two strategies: custom thread pool ChannelHandler execution of business, and by Netty the EventExecutorGroup mechanism for parallel execution ChannelHandler.


   Case reproduce
   can not be executed in parallel analysis
   optimization strategy


Case reproduce

     Netty Server using the built-in parallel DefaultEventExecutorGroup to invoke the business of Handler, the relevant code:

public class ConcurrentPerformanceServer {

    static final EventExecutorGroup executor = new DefaultEventExecutorGroup(100);

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline p = socketChannel.pipeline();
                            p.addLast(executor, new ConcurrentPerformanceServerHandler());
                        }
                    });
            ChannelFuture f = b.bind(8888).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }


    }

}

     We created when the server initializes a thread count of 100 of EventExecutorGroup, and bind it to the business Handler, so that you can realize I O threads and isolation / business logic processing threads, while concurrently Handler, improve performance .
     In business Handler by random dormancy model complex time-consuming business operations, while utilizing timed task processing performance statistics periodically server thread pool. The relevant code:


public class ConcurrentPerformanceServerHandler extends ChannelInboundHandlerAdapter {

    AtomicInteger counter = new AtomicInteger(0);
    static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        scheduledExecutorService.scheduleAtFixedRate(() ->{
            int qps = counter.getAndSet(0);
            System.out.println("The Server QPS is : " + qps);
        },0, 1000, TimeUnit.MILLISECONDS);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ((ByteBuf)msg).release();
        counter.incrementAndGet();
        Random random = new Random();
        TimeUnit.MILLISECONDS.sleep(random.nextInt(1000));
    }
}

     Long to establish a TCP connection to the server speed 100QPS pressure measurement, the following code between the client and the server:


public class ConcurrentPerformanceClientHandler extends ChannelInboundHandlerAdapter {

    static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        scheduledExecutorService.scheduleAtFixedRate(() ->{
            for(int i = 0; i < 100; i++){
                ByteBuf firstMessage = Unpooled.buffer(100);
                for(int k = 0; k < firstMessage.capacity(); k++){
                    firstMessage.writeByte((byte) i);
                }
                ctx.writeAndFlush(firstMessage);
            }
        },0, 1000, TimeUnit.MILLISECONDS);
    }
}

     Test results:
Here Insert Picture Description
     Throughput single digits, time-consuming business here in the 100ms ~ 1000ms, it was suspected that business Handler did not execute concurrently, but is single-threaded execution. Check the server thread stack: Here Insert Picture Description
     find business set 100 thread only one run. Since a single thread of execution comprises Handler is responsible for the business logic operations, so the performance is not high.


Analysis can not be performed in parallel

     Source code analysis, see the bind DefaultEventExecutorGroup to business ChannelHandler code, as follows (DefaultChannelPipeline category):


public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        AbstractChannelHandlerContext newCtx;
        synchronized(this) {
            checkMultiplicity(handler);
            newCtx = this.newContext(group, this.filterName(name, handler), handler);
            this.addLast0(newCtx);
		//省略后续代码
    }

     其中newContext具体代码为创建一个DefaultChannelHandlerContext类返回,创建过程会调用childExecutor(group)方法,从EventExecutorGroup中选择一个EventExecutor绑定到DefaultChannelHandlerContext,相关代码如下:


 private EventExecutor childExecutor(EventExecutorGroup group) {
 
       Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
       if (childExecutors == null) {
           childExecutors = this.childExecutors = new IdentityHashMap(4);
       }

       EventExecutor childExecutor = (EventExecutor)childExecutors.get(group);
       if (childExecutor == null) {
           childExecutor = group.next();
           childExecutors.put(group, childExecutor);
       }

       return childExecutor;
     
 
}

     通过group.next()方法,从EventExecutorGroup中选择一个EventExecutor,存放到EventExecutorMap中。对于某个具体的TCP连接,绑定到业务ChannelHandler实例上的线程池为DefaultEventExecutor,因此调用的就是DefaultEventExecutor的execute方法,由于DefaultEventExecutor继承自SingleThreadEventExecutor,所以执行execute方法就是把Runnable放入任务队列由单线程执行。
     所以无论消费端有多少个线程来并发压测某条链路,对于服务端都只有一个DefaultEventExecutor线程来执行业务ChannelHandler,无法实现并行调用。


优化策略

  1. 如果所有客户端的并发连接数小于业务需要配置的线程数,建议将请求消息封装成任务,投递到后端业务线程池执行,ChannelHandler不需要处理复杂业务逻辑,也不需要绑定EventExecutorGroup。
public class ConcurrentPerformanceServerHandler extends ChannelInboundHandlerAdapter {

    AtomicInteger counter = new AtomicInteger(0);
    static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    static ExecutorService executorService = Executors.newFixedThreadPool(100);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        scheduledExecutorService.scheduleAtFixedRate(() ->{
            int qps = counter.getAndSet(0);
            System.out.println("The Server QPS is : " + qps);
        },0, 1000, TimeUnit.MILLISECONDS);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ((ByteBuf)msg).release();
        executorService.execute(() ->{
            counter.incrementAndGet();
            Random random = new Random();
            try {
                TimeUnit.MILLISECONDS.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

结果展示:

The Server QPS is : 59
The Server QPS is : 55
The Server QPS is : 61
The Server QPS is : 68
The Server QPS is : 43
The Server QPS is : 78

QPS明显上升。

  1. 如果所有客户端并发连接数大于或等于业务需要配置的线程数,则可以为业务ChannelHandler绑定EventExecutorGroup,并在业务ChannelHandler中执行各种业务逻辑。客戶端创建10个TCP连接,每个连接每秒发送1条请求信息,同时将之前DefaultEventExecutorGroup的大小设置为10,则整体QPS也是10,线程堆栈情况:

Here Insert Picture Description

Published 12 original articles · won praise 11 · views 671

Guess you like

Origin blog.csdn.net/MarchRS/article/details/104406070