Netty學習要點

Netty學習要點

1、UNIX的5種I/O模型
2、epoll與select的對比
3、私有協議棧可靠性設計
4、ByteBuf和ByteBuffer對比
5、Netty的線程模型

UNIX的5種I/O模型:

  Linux的內核將所有外部設備都看做一個文件來操作,對一個文件的讀寫操作會調用內核提供的系統命令,返回一個file descriptor(fd,文件描述符)。而對一個socket的讀寫也會有相應的描述符,稱爲socketfd(socket描述符),描述符就是一個數字,它指向內核中的一個結構體(文件路徑,數據區等一些屬性)。

  阻塞I/O模型: 最常用的I/O模型就是阻塞I/O模型,缺省情形下,所有文件操作都是阻塞的。在進程空間中調用recvfrom,其系統調用直到數據包到達且被複制到應用進程的緩衝區或者發生錯誤時才返回,在此期間一直會等待,進程在從調用recvfrom開始到它返回到整段時間內都是被阻塞的。

在這裏插入圖片描述
  非阻塞I/O模型: recvfrom從應用層到內核的時候,如果該緩衝區沒有數據的話,就直接返回一個EWOULDBLOCK錯位,一般都會對非阻塞I/O模型進行輪詢檢查這個狀態,看內核是不是有數據到來。
在這裏插入圖片描述
  I/O複用模型: Linux提供select/poll,進程通過將一個或多個fd傳遞給select或poll系統調用,阻塞在select操作上,這樣select/poll可以幫我門偵測多個fd是否處於就緒狀態。select/poll是順序掃描fd是否就緒,而且支持的fd數量有限,因此它的使用受到來一些制約。Linux還提供了一個epoll系統調用,epoll使用基於事件驅動方式替代順序掃描,性能更高。當有fd就緒時,立即回調函數rollback。
在這裏插入圖片描述
  信號驅動I/O模型: 首先開啓套接口信號驅動I/O功能,並通過系統調用sigaction執行一個信號處理函數(此係統調用立即返回,進程繼續工作,它是非阻塞的)。當數據準備就緒時,就爲該進程生成一個SIGIO信號,通過信號回調通知應用程序調用recvfrom來讀取數據,並通知主循環函數處理數據。
在這裏插入圖片描述
  異步I/O: 告知內核啓動某個操作,並讓內核在整個操作完成後(包括將數據從內核複製到用戶自己的緩衝區)通知我門。這種模型與信號驅動模型的主要區別是:信號驅動I/O由內核通知我們何時可以開始一個I/O操作,異步I/O模型由內核通知我們I/O操作何時已經完成。
在這裏插入圖片描述

epoll與select的對比:

  (1)select最大的缺陷就是單個進程打開的FD是有一定的限制,由FD_SETSIZE設置,默認1024。epoll沒有這個限制,所支持的FD上限是操作系統的最大文件句柄數,通過cat /proc/sys/fs/file -max查看,這個值跟系統的內存關係比較大。
  (2)select每次調用都會線性掃描全部大集合,導致效率呈現線性下降。epoll是根據fd上面的callback函數實現的,只有活躍的socket纔會主動調用callback函數,其他idle狀態的socket則不會。
  (3)內存複製這塊,epoll是通過內核和用戶空間mmap同一塊內存來實現減少複製的。

私有協議棧可靠性設計:

(1)心跳機制設計:
  當網絡處於空閒狀態持續時間達到T(連續週期T沒有讀寫消息)時,客戶端主動發送Ping心跳消息給服務端。
  如果在下一個週期T到來時客戶端沒有收到對方發送到Pong心跳應答消息或者讀取到服務端發送到其他業務消息,則心跳失敗計數器加1.
  每當客戶端接收到服務的業務消息或者Pong應答消息時,將心跳失敗計數器清零,連續N此沒有接收到服務端的Pong消息或者業務消息,則關閉鏈路,間隔INTERVAL時間後發起重連操作。
  服務端網絡空閒狀態持續時間達到T後,服務端將心跳失敗計數器加1,只要接收到客戶端發送到Ping消息或者其他業務消息,計數器請零。
  服務端連續N次沒有接收到客戶端的Ping消息或者其他業務消息,則關閉鏈路,釋放自由,等待客戶端重連。
(2)重連機制:
  如果鏈路中斷,等待INTERVAL時間後,由客戶端發起重連操作,如果重連失敗,間隔週期INTERVAL後再次發起重連,直到連接成功。重連失敗,客戶端必須及時釋放自身的資源並打印異常堆棧信息,方便後續的問題定位。
(3)重複登錄保護
  當客戶端握手成功之後,在鏈路處於正常狀態下,不允許客戶端重複登錄,以防止客戶端在異常狀態下反覆重連導致句柄資源被耗盡。服務端接收到客戶端的握手請求消息之後,首先對IP地址進行合法性檢驗,如果校驗成功,在緩存的地址表中查看客戶端是否已經登錄,如果已經登錄,則拒絕重複登錄,返回錯誤碼-1,同時關閉TCP鏈路,並在服務端的日誌中打印握手失敗的原因。服務端主動關閉鏈路時,清空客戶端的地址緩存信息。
(4)消息緩存重發:
  無論客戶端還是服務端,當發生鏈路中斷之後,在鏈路恢復之前,緩存在消息隊列中待發送的消息不能丟失,等鏈路恢復之後,重新發送這些消息,保證鏈路中斷期間消息不丟失。考慮到內存溢出的風險,消息緩存隊列設置上限,當達到上限之後,應該拒絕繼續向該隊列添加新的消息。
(5)安全性設計:
  爲了保證整個集羣環境的安全,內部長連接採用基於IP地址的安全認證機制,服務端對握手請求消息的IP地址進行合法性校驗,如果在白名單之內,則校驗通過,否則,拒絕對方連接。還可以基於密鑰和AES加密的用戶名+密碼認證機制,還可以採用SSL/TSL安全傳輸。
(6)可擴展性
  Netty協議棧具備一定的擴展能力,統一的消息攔截、接口日誌、安全、加解密等可以被方便地添加和刪除,不需要修改之前的邏輯代碼,類型Servlet的FilterChain和AOP。

ByteBuf和ByteBuffer對比:

JDK中ByteBuffer的缺點:
  (1)ByteBuffer長度固定,一旦分配完成,它的容量不能動態擴展和收縮,當需要編碼的POJO對象大於ByteBuffer的容量時,會發生索引越界異常。
  (2)ByteBuffer只有一個標識位置的指針position,讀寫的時候需要手工調用flip()和rewind()等,使用者必須小心謹慎地處理這些API,否則很容易導致呈現處理失敗。
  (3)ByteBuffer的API功能有限,一些高級和實用的特性它不支持,需要使用者自己編程實現。
Netty中ByteBuf的優點:
  (1)支持容量動態擴展和收縮
  (2)API更豐富一些,使用起來更簡單
  (3)緩衝區使用的是直接內存,非堆內存,它在堆外進行內存分配,相比於堆內存,它的分配和回收速度會慢一些,但是將它寫入或者從Socket Channel中讀取時,由於少了一次內存複製,速度比堆內存快。堆內存的分配和回收速度快,可以被JVM自動回收,缺點就是如果進行Socket的I/O讀寫,需要額外做一次內存複製,將堆內存對應的緩衝區複製到內核Channel中,性能會有一定程度的下降。
  (4)還可選擇內存池來提供性能。從內存回收角度看,ByteBuf分爲兩類,基於對象池的ByteBuf和普通ByteBUf。兩者的主要區別就是基於對象池的ByteBuf可以重用ByteBuf對象,它自己維護了一個內存池,可以循環利用創建的ByteBuf,提升內存的使用效率,降低由於高負載導致的頻繁GC。

Netty的線程模型

  (1)Reactor單線程模型:一個NIO線程處理所有的工作,不支持高負載,高併發場景

EventLoopGroup reactorGroup = new NioEventLoopGroup(1);
	try {
		ServerBootstrap b =new ServerBootstrap();
		b.group(reactorGroup, reactorGroup)
			.channel(NioServerSocketChannel.class)

  (2)Reactor多線程模型:一個專門的NIO線程–Acceptor線程用於監聽服務端,接收客戶端的TCP連接請求。網絡I/O操作–讀、寫等由一個NIO線程池負責,線程池可以採用標準等JDK線程池實現,它包含一個任務隊列和N個可用線程,由這些NIO線程負責消息的讀取、解碼、編碼和發送。1個NIO線程可以同時處理N個鏈路,但是1個鏈路只對應1個NIO線程,防止發生併發操作問題。可以滿足性能需求,但是不穩定,因爲一個NIO線程負責監聽和處理所有的客戶端連接可能會存在性能問題。比如服務端需要對客戶端的握手消息進行安全認證,認證本身非常損耗性能。單獨一個Acceptor線程可能會存在性能不足問題。

EventLoopGroup acceptorGroup = new NioEventLoopGroup(1);
EventLoopGroup IOGroup = new NioEventLoopGroup();
	try {
		ServerBootstrap b =new ServerBootstrap();
		b.group(acceptorGroup, IOGroup)
			.channel(NioServerSocketChannel.class)

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

EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup IOGroup = new NioEventLoopGroup();
	try {
		ServerBootstrap b =new ServerBootstrap();
		b.group(acceptorGroup, IOGroup)
			.channel(NioServerSocketChannel.class)
發佈了69 篇原創文章 · 獲贊 9 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章