Netty4之業務線程池的使用

此文章是基於Netty4.1,一般在使用Netty做服務端開發時,通常會定義I/O線程池及業務線程池。I/O線程池顧名思義用於處理網絡連接及維護Channel的相關事件(一般像心跳及編解碼都可以使用I/O線程池)。當需要處理比較耗時的業務邏輯也共用I/O線程池話會對整個服務的吞吐量有比較大的影響(曾經遇到過)。所以在生產環境中建議定義業務線程池。下面說說如何使用業務線程池及業務線程池處理邏輯的原理。

下面是一個Netty服務端初始化的簡單例子:

 

public class NettyServer {
    public static void main(String[] args) throws Exception {
        new NettyServer().start("127.0.0.1", 8081);
    }

    public void start(String host, int port) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        EventLoopGroup bossGroup = new NioEventLoopGroup(0, executorService);//Boss I/O線程池,用於處理客戶端連接,連接建立之後交給work I/O處理
        EventLoopGroup workerGroup = new NioEventLoopGroup(0, executorService);//Work I/O線程池
        EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(2);//業務線程池
        ServerBootstrap server = new ServerBootstrap();//啓動類
        server.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 3));
                pipeline.addLast(businessGroup, new ServerHandler());
            }
        });
        server.childOption(ChannelOption.TCP_NODELAY, true);
        server.childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
        server.childOption(ChannelOption.SO_SNDBUF, 32 * 1024);
        InetSocketAddress addr = new InetSocketAddress(host, port);
        server.bind(addr).sync().channel();//重啓服務
    }
}

此文章主要是介紹對業務線程池的使用,其他Netty相關知識就不再說明。例子中initChannel()表示初始化一個Channel,並向Channel的Pipeline中添加處理的邏輯的Handler形成一個處理鏈,其中我們對ServerHandler這個處理器使用了一個業務線程池。下面看addList()的邏輯:

 

@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    if (handlers == null) {
        throw new NullPointerException("handlers");
    }
    for (ChannelHandler h: handlers) {
        if (h == null) {
            break;
        }
        addLast(executor, null, h);
    }
    return this;
}

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);
        newCtx = newContext(group, filterName(name, handler), handler);
        addLast0(newCtx);
        // If the registered is false it means that the channel was not registered on an eventLoop yet.
        // In this case we add the context to the pipeline and add a task that will call
        // ChannelHandler.handlerAdded(...) once the channel is registered.
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }
        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}

addList方法是將Handler包裝成一個 AbstractChannelHandlerContext(鏈表結構)然後添加到處理鏈之中,其中線程分配是在newContext()方法中實現的。下面重點來了,

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}

private EventExecutor childExecutor(EventExecutorGroup group) {
    if (group == null) {
        return null;
    }
    Boolean pinEventExecutor = channel.config().getOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP);
    //是否每個事件分組一個單線程的事件執行器  
    if (pinEventExecutor != null && !pinEventExecutor) {
        return group.next();
    }
    Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
    if (childExecutors == null) {
        // Use size of 4 as most people only use one extra EventExecutor.
        childExecutors = this.childExecutors = new IdentityHashMap<EventExecutorGroup, EventExecutor>(4);
    }
    // Pin one of the child executors once and remember it so that the same child executor
    // is used to fire events for the same channel.
    EventExecutor childExecutor = childExecutors.get(group);
    if (childExecutor == null) {
        childExecutor = group.next();
        childExecutors.put(group, childExecutor);
    }
    return childExecutor;
}

上面的childExecutor(group)表示從group分配一個EventExecutor給這個Handler來處理業務,group就是在初始化傳進來的businessGroup,childExecutor()先會判斷是否需要爲每個事件處理器handler分配一個執行器,一般默認爲true,false表示如果兩個處理器(Handler)使用同一個group那麼可能會被分配同一個EventExecuto。然後會爲這個group分配一個子的執行器集合。然後從group中拿一個執行器放到這個集合中。其中group.next表示從EventExecutorGroup隨機拿一個執行器childExecutor。接下來看EventExecutor如何處理任務的。

上面說的EventExecutor一般是DefaultEventLoop extends SingleThreadEventLoop,在DefaultEventLoop有如下:

 

@Override
protected void run() {
    for (;;) {
        Runnable task = takeTask();
        if (task != null) {
            task.run();
            updateLastExecutionTime();
        }

        if (confirmShutdown()) {
            break;
        }
    }
}

上面可以看出DefaultEventLoop起了一個循環任務,一直都獲取任務執行,這個taskTask()方法在其父類中定義:

protected Runnable takeTask() {
    assert inEventLoop();
    if (!(taskQueue instanceof BlockingQueue)) {
        throw new UnsupportedOperationException();
    }

    BlockingQueue<Runnable> taskQueue = (BlockingQueue<Runnable>) this.taskQueue;
    for (;;) {
        ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
        if (scheduledTask == null) {
            Runnable task = null;
            try {
                task = taskQueue.take();
                if (task == WAKEUP_TASK) {
                    task = null;
                }
            } catch (InterruptedException e) {
                // Ignore
            }
            return task;
        } else {
            long delayNanos = scheduledTask.delayNanos();
            Runnable task = null;
            if (delayNanos > 0) {
                try {
                    task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS);
                } catch (InterruptedException e) {
                    // Waken up.
                    return null;
                }
            }
            if (task == null) {
                // We need to fetch the scheduled tasks now as otherwise there may be a chance that
                // scheduled tasks are never executed if there is always one task in the taskQueue.
                // This is for example true for the read task of OIO Transport
                // See https://github.com/netty/netty/issues/1614
                fetchFromScheduledTaskQueue();
                task = taskQueue.poll();
            }

            if (task != null) {
                return task;
            }
        }
    }
}

 

上面代碼表示從一個隊列中的獲取任務。當channel中觸發一個ServerHandler事件時,會將這個事件封裝成一個task放到BlockingQueue這個阻塞隊列中,等待這個執行器去執行。

 

總結:

1、在使用業務線程池的時候同一個Channel的同一個處理器Handler使用的是同一個EventExecutor,也可理解是單線程在執行同一個處理器的任務。

2、Handler任務是通過BlockingQueue來解藕且只有一個線程在處理同一個Handler的任務,所以同一個Channel的同一個處理器的任務執行是有序的,從而可以兼容Netty3中的OrderedMemoryAwareThreadPoolExecutor的有序性

3、在處理業務邏輯的儘量不要使用I/O線程,這樣會影響服務有吞吐量。(之前用Netty實現http接口,沒有定義線程池然後表現是應用內部處理很快,但是調用方就是超時。就是I/O線程線時間被佔用,導致請求一直在等待連接,從而調用方超時)

4、業務線程池要用EventExecutorGroup,EventLoopGroup這個是給I/O線程使用,裏面有一些處理網絡的方法。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章