第1章 Java的I/O演進之路
1.1 Linux網絡I/O模型
fd:file descriptor,文件描述符。linux內核將全部外部設備都看做一個文件來操做,對文件的讀寫會調用內核提供的命令,返回一個文件描述符。對一個socket的讀寫也會有相應的socket fd。描述符就是一個指向內核中結構體的數字。
Unix I/O模型分爲5類:
①阻塞IO模型
文件操做的默認模型。進程空間調用recvfrom函數,直到數據包到達且被複制到應用進程的緩衝區中或者發生錯誤時才返回,在此期間,進程會被阻塞一直等待。
②非阻塞IO模型
recvfrom函數當發現內核緩衝區沒有數據時,直接返回一個EWOULDBLOCK錯誤,通常經過輪詢檢查這個狀態,看是否有數據到來。
③IO複用模型
linux提供select/poll,進程經過將一個或多個fd傳遞給select或poll系統調用,阻塞在select操做上,這樣select/poll能夠經過順序掃描多個fd幫咱們偵測是否處於就緒狀態。但支持的fd數量有限。
linux還提供了epoll系統調用,基於事件驅動方式代替順序掃描,性能更高。當有fd就緒時,當即回掉函數rollback。
④信號驅動IO模型
首先開啓套接口信號驅動IO功能,並經過系統調用sigaction執行一個非阻塞信號處理函數。當數據準備就緒時,就爲該進程生成一個SIGIO信號,經過信號回調通知應用程序調用recvfrom來讀取數據,並通知主循環函數處理數據。
⑤異步IO模型
告知內核啓動某個操做,並讓內核在整個操做完成後(包括將數據從內核複製到用戶本身的緩衝區)通知咱們。
這種模型與信號驅動模型的主要區別是:信號驅動IO由內核通知咱們什麼時候能夠開始一個IO操做;而異步IO模型由內核通知咱們IO操做什麼時候已經完成。
1.2 I/O多路複用技術
java NIO 的核心類庫多路複用器Selector就是基於epoll的多路複用技術實現。
當須要同時處理多個客戶端接入請求時,能夠利用多線程或IO多路複用技術實現。
IO多路複用:經過把多個IO的阻塞複用到同一個select的阻塞上,從而使得系統在單線程的狀況下也能夠同時處理多個客戶端請求。
IO多路複用最大優點:系統開銷小。即系統不須要建立新的額外進程或線程,也不須要維護這些這些進程和線程的運行,下降了系統的維護工做量,節省了系統資源。
IO多路複用主要應用場景:①服務器須要同時處理多個處於監聽或鏈接狀態的套接字。②服務器須要同時處理多種網絡協議的套接字。
支持IO多路複用的系統調用有select、pselect、poll、epoll。在linux網絡編程中,很長時間使用select,但最終選擇epoll。用來克服select/poll缺點的方法不僅有epoll,epoll只是一種linux實現方案,在freeBSD下有kqueue。
epoll改進的select缺點以下:
①支持一個進程打開的socket描述符(FD)不受限制(僅受限於操做系統的最大文件句柄數)。
select最大缺陷是單個進程打開的FD是有限制的,默認值1024個,太多網絡效率會降低。而epoll在1G內存大概支持10萬個。
②IO效率不會隨着FD數目的增長而線性降低。
由於select/poll每次調用都會線性掃描所有的集合,致使效率呈現線性降低。而epoll只有活躍的socket纔會主動調用callback函數,epoll實現了一個僞AIO。極端狀況當所有socket都活躍時,epoll和select性能差很少。
③使用mmap加速內核與用戶空間的消息傳遞。
內核須要把FD消息通知給用戶空間,epoll經過內核和用戶空間mmap同一塊內存來避免沒必要要的內存複製。
④epoll的API更加簡單。
包括建立一個epoll描述符、添加監聽事件、阻塞等待所監聽的事件發生、關閉epoll描述符等。
1.3 Java的I/O演進
在JDK 1.4推出Java NIO以前,java都是使用同步阻塞模式(BIO),而這一時期C和C++語言的大型應用都直接使用操做系統提供的異步IO或AIO能力。
JDK 1.4新增了個java.nio包,提供了進行異步IO開發的API和類庫,主要類和接口以下:進行異步IO操做的緩衝區ByteBuffer、管道Pipe、各類異步或同步的通道Channel、實現非阻塞IO操做的多路複用器Selector等。但NIO 1.0版仍然存在不足,主要問題爲:沒有統一的文件屬性、API能力比較弱、底層存儲系統的一些高級API沒法使用、全部文件操做都是同步阻塞調用,不支持異步文件讀寫操做。
JDK1.7 對NIO作了升級,被稱爲NIO 2.0版,主要改進三個方面以下:
①提供批量獲取文件屬性API。
②提供AIO功能,支持基於文件的異步IO操做和針對網絡套接字的異步操做。
③完善通道功能,包括對配置和多播數據報的支持等。
第2章 NIO入門
2.1 傳統的BIO編程
ServerSocket負責綁定IP地址,啓動監聽端口;Socket負責發起鏈接操做。通過三次握手,鏈接成功以後,Client和Server雙方經過輸入流和輸出流進行同步阻塞式通訊。
同步阻塞IO服務端通訊模型:稱作一個客戶端鏈接一個線程。一般由一個獨立的Acceptor線程負責監聽客戶端的鏈接,它接收到鏈接請求後爲每一個客戶端建立一個新的線程進行鏈路處理,處理完成以後,經過輸出流返回應答給客戶端,線程銷燬。
該模型最大問題:缺少彈性伸縮能力,服務端線程數和客戶端訪問數呈1:1的正比關係,當線程數膨脹,性能急劇降低,致使宕機和僵死。
2.2 僞異步IO編程
是對傳統BIO一個鏈接一個線程的簡單優化,服務端經過一個線程池來處理多個客戶端的請求接入,造成客戶端個數M:線程池最大線程數N的比例關係,M能夠遠遠大於N。因爲底層依然使用同步阻塞IO,因此被稱爲「僞異步」。
優勢:資源佔用是可控的,不會致使耗盡和宕機。
缺點:只是簡單優化,沒法從根本上解決同步IO致使的通訊線程阻塞問題。
2.3 NIO編程
官方叫法是New I/O。而被大多數人接受的更準確叫法是非阻塞IO(Non-block I/O)。
(1)NIO類庫簡介
①緩衝區Buffer
緩衝區實質是一個數組,提供對數據的結構化訪問以及維護讀寫位置等。全部數據都是用緩衝區處理,任什麼時候候訪問NIO中的數據,都是經過緩衝區進行操做。常見緩衝區類有7個:ByteBuffer(最經常使用)、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。而java bio是面向流操做的。
②通道Channel
Channel是一個全雙工的雙向通道,能夠讀寫操做同時進行,能更好的映射底層操做系統的API,由於Unix底層操做系統通道都是全雙工的。而java bio 流是單向的,一個流必須是InputStream或OutputString的子類。
Channel能夠分爲兩大類:用於網絡多寫的SelectableChannel和用於文件操做的FileChannel。ServerSocketChannel和SocketChannel都是SelectableChannel的子類。
③多路複用器Selector
多路複用器提供選擇以及就緒的任務的能力。Selector會不斷地輪詢註冊在其上地Channel,若是某個Channel上面發生讀或寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,而後經過SelectionKey能夠獲取就緒Channel的集合,進行後續的IO操做。JDK使用epoll實現。
(2)NIO編程優缺點
缺點:NIO編程難度比BIO大很大,編碼複雜。
優勢:①客戶端發起的鏈接操做都是異步的,經過在多路複用器註冊OP_CONNECT等待後續結果。
②SocketChannel的讀寫操做都是異步的。
③線程模型的優化,一個Selector線程能夠同時處理成千上萬個客戶端鏈接。
2.4 AIO編程
JDK1.7(NIO 2.0)引入了新的異步通道的概念,並提供了異步文件通道和異步套接字通道的實現,是真正的異步IO(所以NIO2.0也稱做異步非阻塞IO,而NIO 1.0稱做非阻塞IO)。其中異步套接字通道是真正的異步非阻塞IO,對應於Unix網絡編程中的事件驅動IO(AIO)。它不須要經過多路複用器Selector對註冊的通道進行輪詢操做便可實現異步讀寫,從而簡化了NIO的編程模型。主要類有AsynchronousSocketChannel和CompletionHandler(異步操做回調通知接口)。
2.5 四種IO模型對比
第3章 TCP粘包和拆包
3.1 TCP粘包/拆包
TCP是個「流」協議,所謂流,就是沒有界限的一串數據。就像河裏的流水,它們連成一片,其間並無分界線。
粘包:上層業務的多個小包被封裝成一個大的數據包發送。
拆包:上層業務的一個完整的包被拆分紅多個數據包發送。
3.2 TCP粘包/拆包解決方案
主要有四種解決方案:
①消息定長。例如每一個報文的大小爲固定長度200字節,若是不夠,空位補空格。
②在包尾增長回車換行符進行分割。例如FTP協議。
③將消息分爲消息頭和消息體,消息頭中包含表示消息總長度的字段。
④更復雜的應用層協議。
爲了解決TCP粘包/拆包致使的半包讀寫問題,Netty默認提供了多種編解碼器用於處理半包。
第4章 編解碼技術
4.1 Java序列化缺點
java序列化缺點以下:
①沒法跨語言
②序列化後的碼流太大
③序列化性能過低
4.2 業界主流編解碼框架
Protobuf:google開源,高效的編解碼性能,語言無關和平臺無關,文本化的數據結構描述語言。
Thrift:facebook開源給apache,支持壓縮和可選優化等二進制編解碼。
第5章 HTTP協議
5.1 HTTP請求消息
http請求由三部分組成:
①HTTP請求行
請求行以一個方法夫開頭,以空格分開,後面跟着URI和協議版本,格式爲:Method Request-URI HTTP-Version CRLF(回車和換行)。
②HTTP消息頭
③HTTP請求正文
5.2 HTTP響應消息
http響應由三部分組成:
①狀態行
狀態行格式:HTTP-Version Status-Code(狀態碼) Reason-Phrase CRLF。
②消息報頭
③響應正文
5.3 HTTP協議缺點
http協議缺點以下:
①HTTP協議爲半雙工協議。
半雙工協議指數據能夠在客戶端和服務器兩個方向傳輸,可是不能同時傳輸。即同一時刻,只有一個方向上的數據傳送。
②HTTP消息冗長而繁瑣。
採用文本方式傳輸,比二進制通訊協議冗長繁瑣。
③開銷大,不適用於低延遲應用。
容易針對服務器長鏈接推送的黑客攻擊。例如長時間輪詢,消耗大量服務器帶寬。
第6章 WebSocket協議
6.1 WebSocket入門
WebSocket是HTML5開始提供的一種瀏覽器與服務器間進行全雙工通訊的網路技術,瀏覽器和服務器只須要作一個握手動做,而後,瀏覽器和服務器之間就造成了一條快速通道,二者就能夠直接互相傳送數據了。
WebSocket特色以下:
①單一的TCP鏈接,基於TCP的全雙工模式通訊。
②對代理、防火牆和路由器透明。
③無頭部信息、Cookie和身份驗證。
④無安全開銷。
⑤經過「ping/pong」幀保持鏈路激活。
⑥服務器能夠主動傳遞消息給客戶端,再也不須要客戶端輪詢。
第7章 私有協議棧開發
7.1 私有協議介紹
私有協議也稱非標準協議,就是未經國際和國家標準化組織採納和批准,由某個企業本身制定。
7.2 Netty協議棧開發
Netty私有協議棧開發主要有以下步驟:
①數據結構定義
②消息編解碼
③握手和安全認證
④心跳檢測機制
⑤斷連重連
⑥客戶端代碼
⑦服務端代碼
第8章 Reactor線程模型
8.1 Reactor單線程模型
是指全部的IO操做都在同一個NIO線程上完成,這個惟一NIO線程的職責以下:
①作爲NIO服務器,接收客戶端的TCP鏈接。
②作爲NIO客戶端,向服務器發起TCP鏈接。
③讀取通訊對端的請求或者應答消息。
④向通訊對端發送消息或者應答消息。
經過Acceptor類接收客戶端的TCP鏈接請求消息,當鏈路創建成功以後,經過Dispatch將對應的ByteBuffer派發到指定的Handler上,進行消息解碼,用戶線程消息編碼後經過NIO線程將消息發送給客戶端。
單線程模型只適用於小容量應用場景,對於高負載、大併發應用場景不適用,緣由以下:
①一個NIO線程同時處理成百上千的鏈路,性能上沒法支撐,沒法知足海量消息的編碼、解碼、讀取和發送。
②當NIO線程負載太重時,處理速度變慢,會致使大量客戶端鏈接超時,而超時重發會進一步加劇負載。
③可靠性問題,一旦NIO線程崩潰或死循環,會致使整個系統通訊模塊不可用。
8.2 Reactor多線程模型
用一組NIO線程來處理IO操做。多線程模型特色以下:
①有專門一個NIO線程Acceptor線程用於監聽服務端,接收客戶端的TCP鏈接請求。
②網路IO操做讀寫等由一個NIO線程池負責。
不足:單獨一個Acceptor線程可能會存在性能不足問題。
8.3 主從Reactor多線程模型
用一個Acceptor線程池來處理客戶端TCP鏈接請求,握手,安全認證等。一旦鏈路創建成功,就將鏈路註冊到後端Reactor線程池的IO線程上,由IO線程負責後續IO操做。
第9章 高性能之道
9.1 傳統RPC調用性能分析
傳統RPC調用性能差的三宗罪:
①網絡傳輸方式採用同步阻塞I/O。
②序列化性能差。
③線程模型問題。即1:1佔用服務器線程資源。
9.2 IO通訊性能三原則
從架構層面,影響IO通訊性能主要有三個要素:傳輸方式、通訊協議、線程模型。
9.3 Netty高性能之道
netty高性能之道以下:
①異步非阻塞通訊。
②高效的Reactor線程模型。
③無鎖化的串行設計。
避免鎖競爭帶來的性能損耗。
④高效的併發編程。
⑤高性能的序列框架。
netty默認提供了對google Protobuf的支持。
⑥零拷貝。
netty的接收和發送byteBuffer採用Direct Buffer,使用堆外直接內存進行socket讀寫,不須要進行緩衝區二次拷貝。
⑦內存池。
netty提供了基於內存池的緩衝區重用機制,來進行對象的分配和回收。
⑧靈活的TCP參數配置能力。