併發編程:能夠充分利用多核CPU的處理能力,提高系統的處理效率和併發性能。react
基於事件驅動(異步非阻塞IO),特別適合處理海量的I/O事件。算法
全部的IO操做都在同一個NIO線程上面完成。數據庫
缺點:編程
a、一個NIO線程同時處理成百上千的鏈路,性能上沒法支撐,即使NIO線程的CPU負荷達到100%,也沒法知足海量消息的編碼、解碼、讀取和發送;後端
b、當NIO線程負載太重以後,處理速度將變慢,這會致使大量客戶端鏈接超時,超時以後每每會進行重發,這更加劇了NIO線程的負載,最終會致使大量消息積壓和處理超時,成爲系統的性能瓶頸;設計模式
有專門一個NIO線程-Acceptor線程用於監聽服務端,接收客戶端的TCP鏈接請求,網絡IO操做-讀、寫等由一個NIO線程池負責,線程池能夠採用標準的JDK線程池實現,它包含一個任務隊列和N個可用的線程,由這些NIO線程負責消息的讀取、解碼、編碼和發送,1個NIO線程能夠同時處理N條鏈路,可是1個鏈路只對應1個NIO線程,防止發生併發操做問題。數組
缺點:緩存
a、一個NIO線程負責監聽和處理全部的客戶端鏈接可能會存在性能問題。例如併發百萬客戶端鏈接,或者服務端須要對客戶端握手進行安全認證,可是認證自己很是損耗性能。安全
服務端用於接收客戶端鏈接的再也不是個1個單獨的NIO線程,而是一個獨立的NIO線程池。Acceptor接收到客戶端TCP鏈接請求處理完成後(可能包含接入認證等),將新建立的SocketChannel註冊到IO線程池(sub reactor線程池)的某個IO線程上,由它負責SocketChannel的讀寫和編解碼工做。Acceptor線程池僅僅只用於客戶端的登錄、握手和安全認證,一旦鏈路創建成功,就將鏈路註冊到後端subReactor線程池的IO線程上,由IO線程負責後續的IO操做。性能優化
避免線程上下文切換和數據併發控制。
優勢:
a、從消息的讀取、編碼以及後續Handler的執行,始終都由IO線程NioEventLoop負責,這就意外着整個流程不會進行線程上下文的切換,數據也不會面臨被併發修改的風險。
b、一個NioEventLoop聚合了一個多路複用器Selector,所以能夠處理成百上千的客戶端鏈接,Netty的處理策略是每當有一個新的客戶端接入,則從NioEventLoop線程組中順序獲取一個可用的NioEventLoop,當到達數組上限以後,從新返回到0,經過這種方式,能夠基本保證各個NioEventLoop的負載均衡。一個客戶端鏈接只註冊到一個NioEventLoop上,這樣就避免了多個IO線程去併發操做它。
總結:Netty經過串行化設計理念下降了用戶的開發難度,提高了處理性能。利用線程組實現了多個串行化線程水平並行執行,線程之間並無交集,這樣既能夠充分利用多核提高並行處理能力,同時避免了線程上下文的切換和併發保護帶來的額外性能損耗。
場景:
一、客戶端鏈接超時控制;
二、鏈路空閒檢測。
一種比較經常使用的設計理念是在NioEventLoop中聚合JDK的定時任務線程池ScheduledExecutorService,經過它來執行定時任務。這樣作單純從性能角度看不是最優,緣由有以下三點:
a、在IO線程中聚合了一個獨立的定時任務線程池,這樣在處理過程當中會存在線程上下文切換問題,這就打破了Netty的串行化設計理念;
b、存在多線程併發操做問題,由於定時任務Task和IO線程NioEventLoop可能同時訪問並修改同一份數據;
c、JDK的ScheduledExecutorService從性能角度看,存在性能優化空間。
背景:
最先面臨上述問題的是操做系統和協議棧,例如TCP協議棧,其可靠傳輸依賴超時重傳機制,所以每一個經過TCP傳輸的 packet 都須要一個 timer來調度 timeout 事件。這類超時多是海量的,若是爲每一個超時都建立一個定時器,從性能和資源消耗角度看都是不合理的。
根據George Varghese和Tony Lauck 1996年的論文《Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility》提出了一種定時輪的方式來管理和維護大量的timer調度。Netty的定時任務調度就是基於時間輪算法調度,下面咱們一塊兒來看下Netty的實現。
Netty是個異步高性能的NIO框架,它並非個業務運行容器,所以它不須要也不該該提供業務容器和業務線程。合理的設計模式是Netty只負責提供和管理NIO線程,其它的業務層線程模型由用戶本身集成,Netty不該該提供此類功能,只要將分層劃分清楚,就會更有利於用戶集成和擴展。
一、時間可控的簡單業務直接在IO線程上處理
二、複雜和時間不可控業務建議投遞到後端業務線程池統一處理
後端的業務線程池處理各類類型的業務消息,有些是I/O密集型、有些是CPU密集型、有些是純內存計算型,不一樣的業務處理時延,以及發生故障的機率都是不一樣的。若是把業務線程和I/O線程合併,就會存在以下問題:
1.一、某類業務處理較慢,阻塞I/O線程,致使其它處理較快的業務消息的響應沒法及時發送出去。
1.二、即使是同類業務,若是使用同一個I/O線程同時處理業務邏輯和I/O讀寫,若是請求消息的業務邏輯處理較慢,一樣會致使響應消息沒法及時發送出去。
I/O線程和業務線程分離以後,雙方職責單一,有利於代碼維護和問題定位。若是合設在一塊兒,當RPC調用時延增大以後,究竟是網絡問題、仍是I/O線程問題、仍是業務邏輯問題致使的時延大,糾纏在一塊兒,問題定位難度很是大。例如業務線程中訪問緩存或者數據庫偶爾時延增大,就會致使I/O線程被阻塞,時延出現毛刺,這些時延毛刺的定位,難度很是大。
NioEventLoopGroup的建立並非廉價的,它會聚合Selector,Selector自己就會消耗句柄資源。
Netty的NioEventLoop設計理念就是經過有限的I/O線程,經過多路複用和非阻塞的方式,一個線程同時處理成百上千個鏈路,來解決傳統一鏈接一線程的同步阻塞模型。所以,它的建立成本也較高,一個進程中不宜建立過多NioEventLoop。線程切換的代價:若是不是追求極致的性能,線程切換隻要不過於頻繁,它的代價仍是能夠接受的。在一個複雜的系統中,當你集成第三方SDK時,例如Redis Client,一般都包含着隱式的線程切換。
缺點:
a、性能問題:JDK線程池默認採用一個阻塞隊列,N個work線程的模式,隨着work線程數的增長、隊列的爭用會很是激烈,進而致使性能降低。
建議採用N組線程池,每一個線程池線程數儘可能少的方式增長並行處理能力,減小鎖爭用。
b、故障隔離問題:若是後端只有一個線程池,某個服務故障將會致使整個進程不可用。採用分組處理業務服務的方式,能夠下降故障的影響範圍
Ref:
https://mp.weixin.qq.com/s/saKZIqugR_2KZYmSQMPPog