Netty(DotNetty)原理解析

1、背景介紹

DotNetty是微軟的Azure團隊,使用C#實現的Netty的版本發佈。不但使用了C#和.Net平臺的技術特色,而且保留了Netty原來絕大部分的編程接口。讓咱們在使用時,徹底能夠依照Netty官方的教程來學習和使用DotNetty應用程序。html

Netty 是一個異步事件驅動的網絡應用程序框架,用於快速開發可維護的高性能協議服務器和客戶端。java

2、NIO

他並非 Java 獨有的概念,NIO表明的一個詞彙叫着IO多路複用。它是由操做系統提供的系統調用,早期這個操做系統調用的名字是select,可是性能低下,後來漸漸演化成了 Linux 下的epoll和Mac裏的kqueue。咱們通常就說是epoll,由於沒有人拿蘋果電腦做爲服務器使用對外提供服務。而Netty就是基於Java NIO技術封裝的一套框架。爲何要封裝,由於原生的Java NIO使用起來沒那麼方便,並且還有臭名昭著的bug,Netty把它封裝以後,提供了一個易於操做的使用模式和接口,用戶使用起來也就便捷多了。react

 

說NIO以前先說一下BIO(Blocking IO),如何理解這個Blocking呢?linux

1.客戶端監聽(Listen)時,Accept是阻塞的,只有新鏈接來了,Accept纔會返回,主線程才能繼讀寫socket時,Read是阻塞的,只有請求消息來了,Read才能返回,子線程才能繼續處理。編程

2.讀寫socket時,Write是阻塞的,只有客戶端把消息收了,Write才能返回,子線程才能繼續讀取下一個請求。後端

3.傳統的BIO模式下,從頭至尾的全部線程都是阻塞的,這些線程就乾等着,佔用系統的資源,什麼事也不幹。設計模式

Netty 的非阻塞 I/O 的實現關鍵是基於 I/O 複用模型,這裏用 Selector 對象表示:安全

 

1.Netty 的 IO 線程 NioEventLoop 因爲聚合了多路複用器 Selector,能夠同時併發處理成百上千個客戶端鏈接。服務器

2.當線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程能夠進行其餘任務。網絡

3.線程一般將非阻塞 IO 的空閒時間用於在其餘通道上執行 IO 操做,因此單獨的線程能夠管理多個輸入和輸出通道。

4.因爲讀寫操做都是非阻塞的,這就能夠充分提高 IO 線程的運行效率,避免因爲頻繁 I/O 阻塞致使的線程掛起。

5.一個 I/O 線程能夠併發處理 N 個客戶端鏈接和讀寫操做,這從根本上解決了傳統同步阻塞 I/O 一鏈接一線程模型,架構的性能、彈性伸縮能力和可靠性都獲得了極大的提高。

基於Buffer

傳統的 I/O 是面向字節流或字符流的,以流式的方式順序地從一個 Stream 中讀取一個或多個字節, 所以也就不能隨意改變讀取指針的位置。

在 NIO 中,拋棄了傳統的 I/O 流,而是引入了 Channel 和 Buffer 的概念。在 NIO 中,只能從 Channel 中讀取數據到 Buffer 中或將數據從 Buffer 中寫入到 Channel。

基於 Buffer 操做不像傳統 IO 的順序操做,NIO 中能夠隨意地讀取任意位置的數據。

事件驅動模型

一般,咱們設計一個事件處理模型的程序有兩種思路:

  • 1.輪詢方式,線程不斷輪詢訪問相關事件發生源有沒有發生事件,有發生事件就調用事件處理邏輯。
  • 2.事件驅動方式,發生事件,主線程把事件放入事件隊列,在另外線程不斷循環消費事件列表中的事件,調用事件對應的處理邏輯處理事件。事件驅動方式也被稱爲消息通知方式,實際上是設計模式中觀察者模式的思路。

事件機制,它能夠用一個線程把Accept,讀寫操做,請求處理的邏輯全乾了。若是什麼事都沒得作,它也不會死循環,它會將線程休眠起來,直到下一個事件來了再繼續幹活,這樣的一個線程稱之爲NIO線程。用僞代碼表示:

while true {
    events = takeEvents(fds)  // 獲取事件,若是沒有事件,線程就休眠
    for event in events {
        if event.isAcceptable {
            doAccept() // 新連接來了
        } elif event.isReadable {
            request = doRead() // 讀消息
            if request.isComplete() {
                doProcess()
            }
        } elif event.isWriteable {
            doWrite()  // 寫消息
        }
    }
}複製代碼

  

Reactor線程模型

Reactor單線程模型

一個NIO線程+一個accept線程:

 

因爲Reactor模式使用的是異步非阻塞IO,全部的IO操做都不會致使阻塞,理論上一個線程能夠獨立處理全部IO相關的操做。從架構層面看,一個NIO線程確實能夠完成其承擔的職責。例如,經過Acceptor類接收客戶端的TCP鏈接請求消息,鏈路創建成功以後,經過Dispatch將對應的ByteBuffer派發到指定的Handler上進行消息解碼。用戶線程能夠經過消息編碼經過NIO線程將消息發送給客戶端。

對於一些小容量應用場景,可使用單線程模型。可是對於高負載、大併發的應用場景卻不合適,主要緣由以下:

1)一個NIO線程同時處理成百上千的鏈路,性能上沒法支撐,即使NIO線程的CPU負荷達到100%,也沒法知足海量消息的編碼、解碼、讀取和發送;

2)當NIO線程負載太重以後,處理速度將變慢,這會致使大量客戶端鏈接超時,超時以後每每會進行重發,這更加劇了NIO線程的負載,最終會致使大量消息積壓和處理超時,成爲系統的性能瓶頸;

3)可靠性問題:一旦NIO線程意外跑飛,或者進入死循環,會致使整個系統通訊模塊不可用,不能接收和處理外部消息,形成節點故障。

 

Reactor多線程模型

 

Reactor多線程模型的特色:

1)有專門一個NIO線程-Acceptor線程用於監聽服務端,接收客戶端的TCP鏈接請求;

2)網絡IO操做-讀、寫等由一個NIO線程池負責,線程池能夠採用標準的JDK線程池實現,它包含一個任務隊列和N個可用的線程,由這些NIO線程負責消息的讀取、解碼、編碼和發送;

3)1個NIO線程能夠同時處理N條鏈路,可是1個鏈路只對應1個NIO線程,防止發生併發操做問題。

在絕大多數場景下,Reactor多線程模型均可以知足性能需求;可是,在極個別特殊場景中,一個NIO線程負責監聽和處理全部的客戶端鏈接可能會存在性能問題。例如併發百萬客戶端鏈接,或者服務端須要對客戶端握手進行安全認證,可是認證自己很是損耗性能。在這類場景下,單獨一個Acceptor線程可能會存在性能不足問題,爲了解決性能問題,產生了第三種Reactor線程模型-主從Reactor多線程模型。

 

Reactor主從模型

主從Reactor線程模型的特色是:服務端用於接收客戶端鏈接的再也不是個1個單獨的NIO線程,而是一個獨立的NIO線程池。Acceptor接收到客戶端TCP鏈接請求處理完成後(可能包含接入認證等),將新建立的SocketChannel註冊到IO線程池(sub reactor線程池)的某個IO線程上,由它負責SocketChannel的讀寫和編解碼工做。Acceptor線程池僅僅只用於客戶端的登錄、握手和安全認證,一旦鏈路創建成功,就將鏈路註冊到後端subReactor線程池的IO線程上,由IO線程負責後續的IO操做。

利用主從NIO線程模型,能夠解決1個服務端監聽線程沒法有效處理全部客戶端鏈接的性能不足問題。

它的工做流程總結以下:

  1. 從主線程池中隨機選擇一個Reactor線程做爲Acceptor線程,用於綁定監聽端口,接收客戶端鏈接;
  2. Acceptor線程接收客戶端鏈接請求以後建立新的SocketChannel,將其註冊到主線程池的其它Reactor線程上,由其負責接入認證、IP黑白名單過濾、握手等操做;
  3. 步驟2完成以後,業務層的鏈路正式創建,將SocketChannel從主線程池的Reactor線程的多路複用器上摘除,從新註冊到Sub線程池的線程上,用於處理I/O的讀寫操做.

 

Netty能夠基於如上三種模型進行靈活的配置。

總結

Netty是創建在NIO基礎之上,Netty在NIO之上又提供了更高層次的抽象。

在Netty裏面,Accept鏈接可使用單獨的線程池去處理,讀寫操做又是另外的線程池來處理。

Accept鏈接和讀寫操做也可使用同一個線程池來進行處理。而請求處理邏輯既可使用單獨的線程池進行處理,也能夠跟放在讀寫線程一塊處理。線程池中的每個線程都是NIO線程。用戶能夠根據實際狀況進行組裝,構造出知足系統需求的高性能併發模型。

原文出處:https://www.cnblogs.com/hahahayang/p/11231474.html

相關文章
相關標籤/搜索