Netty學習筆記(Reactor線程模型)

NIO selector 多路複用Reactor線程模型

在文章《Scalable IO in Java》中,介紹了使用NIO與Reactor模式相結合來實現高伸縮性網絡的模型。在這個模型中,mianReactor負責處理client的請求;subReactor可以有多個,每個subReactor都會在一個獨立線程中執行。
多路複用reactor線程模型

模型實現部分代碼講解

創建mainReactor和subReactor

	/**
     * 初始化線程組
     */
    private void newGroup() throws IOException {
        // 創建IO線程,負責處理客戶端連接以後socketChannel的IO讀寫
        for (int i = 0; i < subReactorThreads.length; i++) {
            subReactorThreads[i] = new ReactorThread() {
                @Override
                public void handler(SelectableChannel channel) throws IOException {
                    // work線程只負責處理IO處理,不處理accept事件
                    SocketChannel ch = (SocketChannel) channel;
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                    while (ch.isOpen() && ch.read(requestBuffer) != -1) {
                        // 長連接情況下,需要手動判斷數據有沒有讀取結束 (此處做一個簡單的判斷: 超過0字節就認爲請求結束了)
                        if (requestBuffer.position() > 0) break;
                    }
                    if (requestBuffer.position() == 0) return; // 如果沒數據了, 則不繼續後面的處理
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    System.out.println(new String(content));
                    System.out.println(Thread.currentThread().getName() + "收到數據,來自:" + ch.getRemoteAddress());

                    // TODO 業務操作 數據庫、接口...
                    workPool.submit(() -> {
                    });

                    // 響應結果 200
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "Hello World";
                    ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
                    while (buffer.hasRemaining()) {
                        ch.write(buffer);
                    }
                }
            };
        }

        // 創建mainReactor線程, 只負責處理serverSocketChannel
        for (int i = 0; i < mainReactorThreads.length; i++) {
            mainReactorThreads[i] = new ReactorThread() {
                AtomicInteger incr = new AtomicInteger(0);

                @Override
                public void handler(SelectableChannel channel) throws Exception {
                    // 只做請求分發,不做具體的數據讀取
                    ServerSocketChannel ch = (ServerSocketChannel) channel;
                    SocketChannel socketChannel = ch.accept();
                    socketChannel.configureBlocking(false);
                    // 收到連接建立的通知之後,分發給I/O線程繼續去讀取數據
                    int index = incr.getAndIncrement() % subReactorThreads.length;
                    ReactorThread workEventLoop = subReactorThreads[index];
                    workEventLoop.doStart();
                    SelectionKey selectionKey = workEventLoop.register(socketChannel);
                    selectionKey.interestOps(SelectionKey.OP_READ);
                    System.out.println(Thread.currentThread().getName() + "收到新連接 : " + socketChannel.getRemoteAddress());
                }
            };
        }


    }

ReactorThread類中封裝了selector.select()等事件輪詢的代碼,上面的代碼中創建了mainReactor線程和subReactor線程, mainReactor線程只負責處理serverSocketChannel,subReactor線程負責IO處理。當mainReactor收到連接建立的通知之後,分發給I/O線程繼續去讀取數據。

創建serverSocketChannel,註冊到mainReactor線程上的selector上

	/**
     * 初始化channel,並且綁定一個eventLoop線程
     *
     * @throws IOException IO異常
     */
    private void initAndRegister() throws Exception {
        // 1、 創建ServerSocketChannel
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        // 2、 將serverSocketChannel註冊到selector
        int index = new Random().nextInt(mainReactorThreads.length);
        mainReactorThreads[index].doStart();
        SelectionKey selectionKey = mainReactorThreads[index].register(serverSocketChannel);
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
    }

Netty線程模型

爲了讓NIO處理更好地利用多線程特性,Netty實現了Reactor線程模型。

Netty模塊組件

NioEventLoopGroup

NioEventLoopGroup,主要管理 EventLoop 的生命週期,可以理解爲一個線程池,內部維護了一組線程,每個線程(NioEventLoop)負責處理多個 Channel 上的事件,而一個 Channel 只對應於一個線程。

NioEventLoop

NioEventLoop 中維護了一個線程和任務隊列,支持異步提交執行任務,線程啓動時會調用 NioEventLoop 的 run 方法,執行 I/O 任務和非 I/O 任務:
I/O 任務,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法觸發。
非 IO 任務,添加到 taskQueue 中的任務,如 register0、bind0 等任務,由 runAllTasks 方法觸發。

Selector

Netty 基於 Selector 對象實現 I/O 多路複用,通過 Selector 一個線程可以監聽多個連接的 Channel 事件。
當向一個 Selector 中註冊 Channel 後,Selector 內部的機制就可以自動不斷地查詢(Select) 這些註冊的 Channel 是否有已就緒的 I/O 事件(例如可讀,可寫,網絡連接完成等),這樣程序就可以很簡單地使用一個線程高效地管理多個 Channel 。

Channel

Netty的Channel可以理解爲對NIO中Channel的增強和拓展,增加了很多屬性和方法。

Netty工作原理

Netty服務端典型代碼如下

public static void main(String[] args) throws Exception {
        // Configure the server.
        // 創建EventLoopGroup   accept線程組 NioEventLoop
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 創建EventLoopGroup   I/O線程組
        EventLoopGroup workerGroup = new NioEventLoopGroup(1);
        try {
            // 服務端啓動引導工具類
            ServerBootstrap b = new ServerBootstrap();
            // 配置服務端處理的reactor線程組以及服務端的其他配置
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline p = ch.pipeline();
                    p.addLast(new EchoServerHandler());
                }
            });
            // 通過bind啓動服務
            ChannelFuture f = b.bind(PORT).sync();
            // 阻塞主線程,知道網絡服務被關閉
            f.channel().closeFuture().sync();
        } finally {
            // 關閉線程組
            bossGroup.shutdownGracefully();
            workerGroup2.shutdownGracefully();
        }
    }

服務端代碼基本過程描述如下:
1)初始化創建 2 個 NioEventLoopGroup:其中 boosGroup 用於 accept 連接建立事件並分發請求,workerGroup 用於處理 I/O 讀寫事件和業務邏輯。
2)基於 ServerBootstrap(服務端啓動引導類):配置 EventLoopGroup、Channel 類型,連接參數、配置入站、出站事件 handler。
3)綁定端口:開始工作。

結合上面介紹的 Netty Reactor 模型,介紹服務端 Netty 的工作架構圖如下:服務端 Netty 的工作架構圖
服務端包含 1 個 Boss NioEventLoopGroup(bossGroup) 和 1 個 Worker NioEventLoopGroup(workGroup)。
NioEventLoopGroup 相當於 1 個事件循環組,這個組裏包含多個事件循環 NioEventLoop,每個 NioEventLoop 包含 1 個 Selector 和 1 個事件循環線程。
每個 Boss NioEventLoop 循環執行的任務包含 3 步:
1)輪詢 Accept 事件;
2)處理 Accept I/O 事件,與 Client 建立連接,生成 NioSocketChannel,並將 NioSocketChannel 註冊到某個 Worker NioEventLoop 的 Selector 上;
3)處理任務隊列中的任務,runAllTasks。任務隊列中的任務包括用戶調用 eventloop.execute 或 schedule 執行的任務,或者其他線程提交到該 eventloop 的任務。
每個 Worker NioEventLoop 循環執行的任務包含 3 步:
1)輪詢 Read、Write 事件;
2)處理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可讀、可寫事件發生時進行處理;
3)處理任務隊列中的任務,runAllTasks。

個人小結

Netty實現了Reactor線程模型,Netty服務端包含 1 個 bossGroup 和 1 個 workGroup,其中 boosGroup 中的 NioEventLoop 負責 accept 連接建立事件並分發請求,workerGroup 中的多個 NioEventLoop 用於處理 I/O 讀寫事件和業務邏輯。

參考資料

[1].Scalable IO in Java http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
[2].Netty源碼解讀(四)Netty與Reactor模式 https://yq.aliyun.com/articles/25425?spm=a2c4e.11153940.0.0.34ee5e71hGNfnN
[3].Netty 4.x學習(三):線程模型詳解 http://www.52im.net/thread-98-1-1.html

發佈了6 篇原創文章 · 獲贊 0 · 訪問量 532
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章