SwiftNIO —— Swift 版的 Netty

SwiftNIO is a cross-platform asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.git

It's like Netty, but written for Swift.github

SwfitNIO 是一款基於事件驅動的跨平臺網絡應用程序開發框架,其目標是幫助開發者快速開發出高性能且易於維護的服務器端和客戶端應用協議。編程

對於喜歡探究本源的咱們能夠先了解 Netty 的一些概念。swift

本文是篇整理性文章,主要來自:設計模式

Netty 高性能設計

Netty 做爲異步事件驅動的網絡,高性能之處主要來自於其 I/O 模型線程處理模型,前者決定如何 收發數據,後者決定如何 處理數據api

I/O 模型

用什麼樣的通道將數據發送給對方?Java 中比較流行的 3 種 I/O 模型:安全

  • BIO: 同步並阻塞,服務器實現模式爲 一個鏈接一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,若是這個鏈接不作任何事情會形成沒必要要的線程開銷,固然能夠經過線程池機制改善。BIO 方式適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,程序直觀簡單易理解。
  • NIO: 同步非阻塞,服務器實現模式爲 一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有 I/O 請求時才啓動一個線程進行處理。NIO 方式適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜。。
  • AIO: 異步非阻塞,服務器實現模式爲 一個有效請求一個線程,客戶端的I/O 請求都是由 OS 先完成了再通知服務器應用去啓動線程進行處理。AIO方式使用於鏈接數目多且鏈接比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜。

Netty 的非阻塞 I/O 的實現關鍵是基於 I/O 複用模型服務器

15680790878888

Netty 的 IO 線程 NioEventLoop 因爲聚合了多路複用器 Selector,能夠同時併發處理成百上千個客戶端鏈接。 當線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程能夠進行其餘任務。 線程一般將非阻塞 IO 的空閒時間用於在其餘通道上執行 IO 操做,因此單獨的線程能夠管理多個輸入和輸出通道。 因爲讀寫操做都是非阻塞的,這就能夠充分提高 IO 線程的運行效率,避免因爲頻繁 I/O 阻塞致使的線程掛起。 一個 I/O 線程能夠併發處理 N 個客戶端鏈接和讀寫操做,這從根本上解決了傳統同步阻塞 I/O 一鏈接一線程模型,架構的性能、彈性伸縮能力和可靠性都獲得了極大的提高。網絡

基於 Buffer

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

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

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

線程模型

事件驅動模型

設計一個事件處理模型的程序有兩種思路。

  1. 輪詢方式:線程不斷輪詢訪問相關事件發生源有沒有發生事件,有發生事件就調用事件處理邏輯;

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

    主要包括 4 個基本組件:

    1. 事件隊列(event queue):接收事件的入口,存儲待處理事件;

    2. 分發器(event mediator):將不一樣的事件分發到不一樣的業務邏輯單元;

    3. 事件通道(event channel):分發器與處理器之間的聯繫渠道;

    4. 事件處理器(event processor):實現業務邏輯,處理完成後會發出事件,觸發下一步操做。

    能夠看出,相對傳統輪詢模式,事件驅動有以下優勢:

    1. 可擴展性好:分佈式的異步架構,事件處理器之間高度解耦,能夠方便擴展事件處理邏輯;

    2. 高性能:基於隊列暫存事件,能方便並行異步處理事件。

Reactor 線程模型

Reactor 是反應堆的意思,Reactor 模型是指經過一個或多個輸入同時傳遞給服務處理器的服務請求的事件驅動處理模式。

服務端程序處理傳入多路請求,並將它們同步分派給請求對應的處理線程,Reactor 模式也叫 Dispatcher 模式,即 I/O 多了複用統一監聽事件,收到事件後分發(Dispatch 給某進程),是編寫高性能網絡服務器的必備技術之一。

Reactor 模型中有 2 個關鍵組成:

  1. Reactor:Reactor 在一個單獨的線程中運行,負責監聽和分發事件,分發給適當的處理程序來對 IO 事件作出反應。它就像公司的電話接線員,它接聽來自客戶的電話並將線路轉移到適當的聯繫人;

  2. Handlers:處理程序執行 I/O 事件要完成的實際事件,相似於客戶想要與之交談的公司中的實際官員。Reactor 經過調度適當的處理程序來響應 I/O 事件,處理程序執行非阻塞操做。

取決於 Reactor 的數量和 Hanndler 線程數量的不一樣,Reactor 模型有 3 個變種:

  1. 單 Reactor 單線程;

  2. 單 Reactor 多線程;

  3. 主從 Reactor 多線程。

能夠這樣理解,Reactor 就是一個執行

while (true) { selector.select(); …} 
複製代碼

循環的線程,會源源不斷的產生新的事件,稱做反應堆很貼切。

Netty 線程模型

Netty 主要基於主從 Reactors 多線程模型(以下圖)作了必定的修改,其中主從 Reactor 多線程模型有多個 Reactor:

  1. MainReactor 負責客戶端的鏈接請求,並將請求轉交給 SubReactor;

  2. SubReactor 負責相應通道的 IO 讀寫請求;

  3. 非 IO 請求(具體邏輯處理)的任務則會直接寫入隊列,等待 worker threads 進行處理。

異步處理

異步的概念和同步相對。當一個異步過程調用發出後,調用者不能馬上獲得結果。實際處理這個調用的部件在完成後,經過狀態、通知和回調來通知調用者。

Netty 中的 I/O 操做是異步的,包括 Bind、Write、Connect 等操做會簡單的返回一個 ChannelFuture。

調用者並不能馬上得到結果,而是經過 Future-Listener 機制,用戶能夠方便的主動獲取或者經過通知機制得到 IO 操做結果。

當 Future 對象剛剛建立時,處於非完成狀態,調用者能夠經過返回的 ChannelFuture 來獲取操做執行的狀態,註冊監聽函數來執行完成後的操做。

常見有以下操做:

  1. 經過 isDone 方法來判斷當前操做是否完成;

  2. 經過 isSuccess 方法來判斷已完成的當前操做是否成功;

  3. 經過 getCause 方法來獲取已完成的當前操做失敗的緣由;

  4. 經過 isCancelled 方法來判斷已完成的當前操做是否被取消;

  5. 經過 addListener 方法來註冊監聽器,當操做已完成(isDone 方法返回完成),將會通知指定的監聽器;若是 Future 對象已完成,則理解通知指定的監聽器。

相比傳統阻塞 I/O,執行 I/O 操做後線程會被阻塞住, 直到操做完成;異步處理的好處是不會形成線程阻塞,線程在 I/O 操做期間能夠執行別的程序,在高併發情形下會更穩定和更高的吞吐量。

Netty 架構

Server 端包含 1 個 Boss NioEventLoopGroup 和 1 個 Worker NioEventLoopGroup。

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。

SwiftNIO 體系結構

nio-01

  • EventLoopGroup 接口
  • EventLoop 接口
  • Channel 接口
  • ChannelHandler 接口
  • Bootstrap 多種數據結構
  • ByteBuffer 結構體
  • EventLoopFuture 通用類
  • EventLoopPromise 通用結構體

EventLoops and EventLoopGroups

nio-02

EventLoop 是 SwfitNIO 最基本的 IO 原語,它等待事件的發生,在發生事件時觸發某種回調操做。在大部分 SwfitNIO 應用程序中,EventLoop 對象的數量並很少,一般每一個 CPU 核數對應一到兩個 EventLoop 對象。通常來講,EventLoop 會在應用程序的整個生命週期中存在,進行無限的事件分發。

EventLoop 能夠組合成 EventLoopGroup,EventLoopGroup 提供了一種機制用於在各個 EventLoop 間分發工做負載。例如,服務器在監聽外部鏈接時,用於監聽鏈接的 socket 會被註冊到一個 EventLoop 上。但咱們不但願這個 EventLoop 承擔全部的鏈接負載,那麼就能夠經過 EventLoopGroup 在多個 EventLoop 間分攤鏈接負載。

目前,SwiftNIO 提供了一個 EventLoopGroup 實現(MultiThreadedEventLoopGroup)和兩個 EventLoop 實現(SelectableEventLoop 和 EmbeddedEventLoop)。

  • MultiThreadedEventLoopGroup 會建立多個線程(使用 POSIX 的 pthreads 庫),併爲每一個線程分配一個 SelectableEventLoop 對象。
  • SelectableEventLoop 使用選擇器(基於 kqueue 或 epoll)來管理來自文件和網絡 IO 事件。
  • EmbeddedEventLoop 是一個空的 EventLoop,什麼事也不作,主要用於測試。

Channels、ChannelHandler、ChannelPipeline 和 ChannelHandlerContext

儘管 EventLoop 很是重要,但大部分開發者並不會與它有太多的交互,最多就是用它建立 EventLoopPromise 和調度做業。開發者常常用到的是 Channel 和 ChannelHandler。

每一個文件描述符對應一個 Channel,Channel 負責管理文件描述符的生命週期,並處理髮生在文件描述符上的事件:每當 EventLoop 檢測到一個與相應的文件描述符相關的事件,就會通知 Channel。

nio-03

ChannelPipeline 由一系列 ChannelHandler 組成,ChannelHandler 負責按順序處理 Channel 中的事件。ChannelPipeline 就像數據處理管道同樣,因此纔有了這個名字。

ChannelHandler 要麼是 Inbound,要麼是 Outbound,要麼二者兼有。Inbound 的 ChannelHandler 負責處理「inbound」事件,例如從 socket 讀取數據、關閉 socket 或者其餘由遠程發起的事件。Outbound 的 ChannelHandler 負責處理「outbound」事件,例如寫數據、發起鏈接以及關閉本地 socket。

nio-04

nio-07

ChannelHandler 按照必定順序處理事件,例如,讀取事件從管道的前面傳到後面,而寫入事件則從管道的後面傳到前面。每一個 ChannelHandler 都會在處理完一個事件後生成一個新的事件給下一個 ChannelHandler。

ChannelHandler 是高度可重用的組件,因此儘量設計得輕量級,每一個 ChannelHandler 只處理一種數據轉換,這樣就能夠靈活組合各類 ChannelHandler,提高代碼的可重用性和封裝性。

咱們能夠經過 ChannelHandlerContext 來跟蹤 ChannelHandler 在 ChannelPipeline 中的位置。ChannelHandlerContext 包含了當前 ChannelHandler 到上一個和下一個 ChannelHandler 的引用,所以,在任什麼時候候,只要 ChannelHandler 還在管道當中,就能觸發新事件。

nio-05

SwiftNIO 內置了多種 ChannelHandler,包括 HTTP 解析器。另外,SwiftNIO 還提供了一些 Channel 實現,好比 ServerSocketChannel(用於接收鏈接)、SocketChannel(用於 TCP 鏈接)、DatagramChannel(用於 UDP socket)和 EmbeddedChannel(用於測試)。

很重要的一點是:ChannelPipeline 是線程安全的,這就意味不用單獨作同步處理。全部 ChannelPipeline 中的 Handlers 都是放到同一個線程經過 EventLoop 處理的,同時也說明,全部的 Handlers 都不能是阻塞的或者說必須是 none blocking 的。若是阻塞,pipeline 中的其餘 Handlers 就會一直等待當前 Handler 處理結束。所以,最好將可能會有阻塞,或者可能併發量高的處理放到其餘子線程去處理。

Bootstrap

nio-06

SwiftNIO 提供了一些 Bootstrap 對象,用於簡化 Channel 的建立。有些 Bootstrap 對象還提供了其餘的一些功能,好比支持 Happy Eyeballs。

目前 SwiftNIO 提供了三種 Bootstrap:ServerBootstrap(用於監聽 Channel),ClientBootstrap(用於 TCP Channel)和 DatagramBootstrap(用於 UDP Channel)。

ByteBuffer

SwiftNIO 提供了 ByteBuffer,一種快速的 Copy-On-Write 字節緩衝器,是大部分 SwiftNIO 應用程序的關鍵構建塊。

ByteBuffer 提供了不少有用的特性以及一些「鉤子」,經過這些鉤子,咱們能夠在「unsafe」的模式下使用 ByteBuffer。這種方式能夠得到更好的性能,代價是應用程序有可能出現內存問題。在通常狀況下,仍是建議在安全模式下使用 ByteBuffer。

EventLoopPromise 和 EventLoopFuture

併發代碼和同步代碼之間最主要的區別在於並不是全部的動做都可以當即完成。例如,在向一個 Channel 寫入數據時,EventLoop 有可能不會當即將數據沖刷到網絡上。爲此,SwiftNIO 提供了 EventLoopPromise 和 EventLoopFuture,用於管理異步操做。

EventLoopFuture 其實是一個容器,用於存放函數在將來某個時刻的返回值。每一個 EventLoopFuture 對象都有一個對應的 EventLoopPromise,用於存放實際的結果。只要 EventLoopPromise 執行成功,EventLoopFuture 也就完成了。

經過輪詢的方式檢查 EventLoopFuture 是否完成是一種很是低效的方式,因此 EventLoopFuture 被設計成能夠接收回調函數。也就是說,在有結果的時候回調函數會被執行。

EventLoopFuture 負責處理調度工做,確保回調函數是在最初建立 EventLoopPromise 的那個 EventLoop 上執行,因此就沒有必要再針對回調函數作任何同步操做。

相關文章
相關標籤/搜索