聲明:此文章非本人所 原創,是別人分享所得,若有知道原做者是誰能夠聯繫本人,若有轉載請加上此段話 java
1.BIO、NIO 和 AIO 的區別?python
BIO:一個鏈接一個線程,客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理。線程開銷大。算法
僞異步 IO:將請求鏈接放入線程池,一對多,但線程仍是很寶貴的資源。編程
NIO:一個請求一個線程,但客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有 I/O 請求時才啓動一個線程進行處理。json
AIO:一個有效請求一個線程,客戶端的 I/O 請求都是由 OS 先完成了再通知服務器應用去啓動線程進行處理。後端
BIO 是面向流的,NIO 是面向緩衝區的;BIO 的各類流是阻塞的。而 NIO 是非阻塞的;BIO的 Stream 是單向的,而 NIO 的 channel 是雙向的。數組
NIO 的特色:事件驅動模型、單線程處理多任務、非阻塞 I/O,I/O 讀寫再也不阻塞,而是返回 0、基於 block 的傳輸比基於流的傳輸更高效、更高級的 IO 函數 zero-copy、IO 多路複用大大提升了 Java 網絡應用的可伸縮性和實用性。基於 Reactor 線程模型。安全
在 Reactor 模式中,事件分發器等待某個事件或者可應用或個操做的狀態發生,事件分發器就把這個事件傳給事先註冊的事件處理函數或者回調函數,由後者來作實際的讀寫操做。如在 Reactor 中實現讀:註冊讀就緒事件和相應的事件處理器、事件分發器等待事件、事件到來,激活分發器,分發器調用事件對應的處理器、事件處理器完成實際的讀操做,處理讀到的數據,註冊新的事件,而後返還控制權。服務器
flip 方法 : 反轉此緩衝區,將 position 給 limit,而後將 position 置爲 0,其實就是切換讀寫模式網絡
clear 方法 :清除此緩衝區,將 position 置爲 0,把 capacity 的值給 limit。
rewind 方法 : 重繞此緩衝區,將 position 置爲 0
DirectByteBuffer :可減小一次系統空間到用戶空間的拷貝。但 Buffer 建立和銷燬的成本更高,不可控,一般會用內存池來提升性能。直接緩衝區主要分配給那些易受基礎系統的本機 I/O 操做影響的大型、持久的緩衝區。若是數據量比較小的中小應用狀況下,能夠考慮使用 heapBuffer,由 JVM 進行管理。
Channel:表示 IO 源與目標打開的鏈接,是雙向的,但不能直接訪問數據,只能與 Buffer進行交互。經過源碼可知,FileChannel 的 read 方法和 write 方法都致使數據複製了兩次!
Selector:可以使一個單獨的線程管理多個 Channel,open 方法可建立 Selector,register 方法向多路複用器器註冊通道,能夠監聽的事件類型:讀、寫、鏈接、accept。註冊事件後會產生一個SelectionKey:它表示 SelectableChannel 和 Selector 之間的註冊關係,wakeup 方法:使還沒有返回的第一個選擇操做當即返回,喚醒的緣由是:註冊了新的 channel 或者事件;channel 關閉,取消註冊;優先級更高的事件觸發(如定時器事件),但願及時處理。
Selector 在 Linux 的實現類是 EPollSelectorImpl,委託給 EPollArrayWrapper 實現,其中三個native 方法是對 epoll 的封裝,而 EPollSelectorImpl. implRegister 方法,經過調用 epoll_ctl向 epoll 實例中註冊事件,還將註冊的文件描述符(fd)與 SelectionKey 的對應關係添加到fdToKey 中,這個 map 維護了文件描述符與 SelectionKey 的映射。
fdToKey 有時會變得很是大,由於註冊到 Selector 上的 Channel 很是多(百萬鏈接);過時或失效的 Channel 沒有及時關閉。fdToKey 老是串行讀取的,而讀取是在 select 方法中進行的,該方法是非線程安全的。
Pipe:兩個線程之間的單向數據鏈接,數據會被寫到 sink 通道,從 source 通道讀取
NIO 的服務端創建過程:Selector.open():打開一個 Selector; ServerSocketChannel.open():
建立服務端的 Channel; bind():綁定到某個端口上。並配置非阻塞模式; register():註冊Channel 和關注的事件到 Selector 上; select():輪詢拿到已經就緒的事件
3.Netty 的特色?
一個高性能、異步事件驅動的 NIO 框架,它提供了對 TCP、UDP 和文件傳輸的支持使用更高效的 socket 底層,對 epoll 空輪詢引發的 cpu 佔用飆升在內部進行了處理,避免了直接使用 NIO 的陷阱,簡化了 NIO 的處理方式。
採用多種 decoder/encoder 支持,對 TCP 粘包/分包進行自動化處理可以使用接受/處理線程池,提升鏈接效率,對重連、心跳檢測的簡單支持可配置 IO 線程數、TCP 參數, TCP 接收和發送緩衝區使用直接內存代替堆內存,經過內存池的方式循環利用 ByteBuf經過引用計數器及時申請釋放再也不引用的對象,下降了 GC 頻率使用單線程串行化的方式,高效的 Reactor 線程模型大量使用了 volitale、使用了 CAS 和原子類、線程安全類的使用、讀寫鎖的使用。
4.Netty 的線程模型?
Netty 經過 Reactor 模型基於多路複用器接收並處理用戶請求,內部實現了兩個線程池,boss 線程池和 work 線程池,其中 boss 線程池的線程負責處理請求的 accept 事件,當接收到 accept 事件的請求時,把對應的 socket 封裝到一個 NioSocketChannel 中,並交給 work線程池,其中 work 線程池負責請求的 read 和 write 事件,由對應的 Handler 處理。
單線程模型:全部 I/O 操做都由一個線程完成,即多路複用、事件分發和處理都是在一個Reactor 線程上完成的。既要接收客戶端的鏈接請求,向服務端發起鏈接,又要發送/讀取請求或應答/響應消息。一個 NIO線程同時處理成百上千的鏈路,性能上沒法支撐,速度慢,若線程進入死循環,整個程序不可用,對於高負載、大併發的應用場景不合適。
多線程模型:有一個 NIO 線程(Acceptor) 只負責監聽服務端,接收客戶端的 TCP 鏈接請求;NIO 線程池負責網絡 IO 的操做,即消息的讀取、解碼、編碼和發送;1 個 NIO 線程能夠同時處理 N 條鏈路,可是 1 個鏈路只對應 1 個 NIO 線程,這是爲了防止發生併發操做問題。但在併發百萬客戶端鏈接或須要安全認證時,一個 Acceptor 線程可能會存在性能不足問題。
主從多線程模型:Acceptor線程用於綁定監聽端口,接收客戶端鏈接,將 SocketChannel從主線程池的 Reactor 線程的多路複用器上移除,從新註冊到 Sub 線程池的線程上,用於處理 I/O 的讀寫等操做,從而保證 mainReactor 只負責接入認證、握手等操做;TCP 是以流的方式來處理數據,一個完整的包可能會被 TCP 拆分紅多個包進行發送,也可能把小的封裝成一個大的數據包發送。
TCP 粘包/分包的緣由:應用程序寫入的字節大小大於套接字發送緩衝區的大小,會發生拆包現象,而應用程序寫入數據小於套接字緩衝區大小,網卡將應用屢次寫入的數據發送到網絡上,這將會發生粘包現象;
進行 MSS 大小的 TCP 分段,當 TCP 報文長度-TCP 頭部長度>MSS 的時候將發生拆包以太網幀的 payload(淨荷)大於 MTU(1500 字節)進行 ip 分片。
解決方法
消息定長:FixedLengthFrameDecoder 類包尾增長特殊字符分割:行分隔符類:LineBasedFrameDecoder 或自定義分隔符類
DelimiterBasedFrameDecoder:將消息分爲消息頭和消息體:LengthFieldBasedFrameDecoder 類。分爲有頭部的拆包與粘包、長度字段在前且有頭部的拆包與粘包、多擴展頭部的拆包與粘包。
6.瞭解哪幾種序列化協議?
序列化(編碼)是將對象序列化爲二進制形式(字節數組),主要用於網絡傳輸、數據持久化等;而反序列化(解碼)則是將從網絡、磁盤等讀取的字節數組還原成原始對象,主要用於網絡傳輸對象的解碼,以便完成遠程調用。
影響序列化性能的關鍵因素:序列化後的碼流大小(網絡帶寬的佔用)、序列化的性能(CPU 資源佔用);是否支持跨語言(異構系統的對接和開發語言切換)。XML,優勢:人機可讀性好,可指定元素或特性的名稱。
缺點:序列化數據只包含數據本身以及類的結構,不包括類型標識和程序集信息;只能序列化公共屬性和字段;不能序列化方法;文件龐大,文件格式複雜,傳輸佔帶寬。
適用場景:當作配置文件存儲數據,實時數據轉換。
JSON,是一種輕量級的數據交換格式,
優勢:兼容性高、數據格式比較簡單,易於讀寫、序列化後數據較小,可擴展性好,兼容性好、與 XML 相比,其協議比較簡單,解析速度比較快。
缺點:數據的描述性比 XML 差、不適合性能要求爲 ms 級別的狀況、額外空間開銷比較大。
適用場景(可替代XML):跨防火牆訪問、可調式性要求高、基於 Webbrowser 的 Ajax 請求、傳輸數據量相對小,實時性要求相對低(例如秒級別)的服務。
Fastjson,採用一種「假定有序快速匹配」的算法。
優勢:接口簡單易用、目前 java 語言中最快的 json 庫。
缺點:過於注重快,而偏離了「標準」及功能性、代碼質量不高,文檔不全。
適用場景:協議交互、Web 輸出、Android 客戶端
Thrift,不只是序列化協議,仍是一個 RPC 框架。
優勢:序列化後的體積小, 速度快、支持多種語言和豐富的數據類型、對於數據字段的增刪具備較強的兼容性、支持二進制壓縮編碼。
缺點:使用者較少、跨防火牆訪問時,不安全、不具備可讀性,調試代碼時相對困難、不能與其餘傳輸層協議共同使用(例如 HTTP)、沒法支持向持久層直接讀寫數據,即不適合作數據持久化序列化協議。
適用場景:分佈式系統的 RPC 解決方案
Jboss marshaling 能夠直接序列化 java 類, 無須實 java.io.Serializable 接口
Message pack 一個高效的二進制序列化格式
Hessian 採用二進制協議的輕量級 remoting onhttp 工具7.如何選擇序列化協議?
具體場景
對於公司間的系統調用,若是性能要求在 100ms 以上的服務,基於 XML 的 SOAP 協議是一個值得考慮的方案。
基於 Web browser 的 Ajax,以及 Mobile app 與服務端之間的通信,JSON 協議是首選。對於性能要求不過高,或者以動態類型語言爲主,或者傳輸數據載荷很小的的運用場景,JSON也是很是不錯的選擇。
對於調試環境比較惡劣的場景,採用 JSON 或 XML 可以極大的提升調試效率,下降系統開發成本。
當對性能和簡潔性有極高要求的場景,Protobuf,Thrift,Avro 之間具備必定的競爭關係。
對於 T 級別的數據的持久化應用場景,Protobuf 和 Avro 是首要選擇。若是持久化後的數據存儲在 hadoop 子項目裏,Avro 會是更好的選擇。
對於持久層非 Hadoop 項目,以靜態類型語言爲主的應用場景,Protobuf 會更符合靜態類型語言工程師的開發習慣。因爲 Avro 的設計理念偏向於動態類型語言,對於動態語言爲主的應用場景,Avro 是更好的選擇。
若是須要提供一個完整的 RPC 解決方案,Thrift 是一個好的選擇。
若是序列化以後須要支持不一樣的傳輸層協議,或者須要跨防火牆訪問的高性能場景,Protobuf 能夠優先考慮。
protobuf 的數據類型有多種:bool、double、float、int3二、int6四、string、bytes、enum、message。protobuf 的限定符:required: 必須賦值,不能爲空、optional:字段能夠賦值,也能夠不賦值、repeated: 該字段能夠重複任意次數(包括 0 次)、枚舉;只能用指定的常量集中的一個值做爲其值;
protobuf 的基本規則:每一個消息中必須至少留有一個 required 類型的字段、包含 0 個或多個 optional 類型的字段;repeated 表示的字段能夠包含 0 個或多個數據;[1,15]以內的標識號在編碼的時候會佔用一個字節(經常使用),[16,2047]以內的標識號則佔用 2 個字節,標識號必定不能重複、使用消息類型,也能夠將消息嵌套任意多層,可用嵌套消息類型來代替組。
protobuf 的消息升級原則:不要更改任何已有的字段的數值標識;不能移除已經存在的required 字段,optional 和 repeated 類型的字段能夠被移除,但要保留標號不能被重用。
新添加的字段必須是 optional 或 repeated。由於舊版本程序沒法讀取或寫入新增的required 限定符的字段。
ProtobufDecoder(UserProto.User.getDefaultInstance())這是建立的 UserProto.java 文件中的解碼類;ProtobufVarint32LengthFieldPrepender 對 protobuf 協議的消息頭上加上一個長度爲32 的整形字段,用於標誌這個消息的長度的類;ProtobufEncoder 是編碼類將 StringBuilder 轉換爲 ByteBuf 類型:copiedBuffer()方法
經過 FileRegion 包裝的 FileChannel.tranferTo 方法 實現文件傳輸, 能夠直接將文件緩衝區的數據發送到目標 Channel,避免了傳統經過循環 write 方式致使的內存拷貝問題。
經過 wrap 方法, 咱們能夠將 byte[] 數組、ByteBuf、ByteBuffer 等包裝成一個 NettyByteBuf 對象, 進而避免了拷貝操做。
Selector BUG:若 Selector 的輪詢結果爲空,也沒有 wakeup 或新消息處理,則發生空輪詢,CPU 使用率 100%,Netty 的解決辦法:對 Selector 的 select 操做週期進行統計,每完成一次空的 select 操做進行一次計數,若在某個週期內連續發生 N 次空輪詢,則觸發了 epoll 死循環 bug。重建Selector,判斷是不是其餘線程發起的重建請求,若不是則將原 SocketChannel 從舊的Selector 上去除註冊,從新註冊到新的 Selector 上,並將原來的 Selector 關閉。
9.Netty 的高性能表如今哪些方面?
可靠性,鏈路有效性檢測:鏈路空閒檢測機制,讀/寫空閒超時機制;內存保護機制:經過內存池重用 ByteBuf;ByteBuf 的解碼保護;優雅停機:再也不接收新消息、退出前的預處理操做、資源的釋放操做。
Netty 安全性:支持的安全協議:SSL V2 和 V3,TLS,SSL 單向認證、雙向認證和第三方 CA證。高效併發編程的體現:volatile 的大量、正確使用;CAS 和原子類的普遍使用;線程安全容器的使用;經過讀寫鎖提高併發性能。IO 通訊性能三原則:傳輸(AIO)、協議(Http)、線程(主從多線程)
流量整型的做用(變壓器):防止因爲上下游網元性能不均衡致使下游網元被壓垮,業務流中斷;防止因爲通訊模塊接受消息過快,後端業務線程處理不及時致使撐死問題。
TCP 參數配置:SO_RCVBUF 和 SO_SNDBUF:一般建議值爲 128K 或者 256K;
SO_TCPNODELAY:NAGLE 算法經過將緩衝區內的小封包自動相連,組成較大的封包,阻止大量小封包的發送阻塞網絡,從而提升網絡應用效率。可是對於時延敏感的應用場景須要關閉該優化算法;
10.NIOEventLoopGroup 源碼?
線程啓動時調用 SingleThreadEventExecutor 的構造方法,執行 NioEventLoop 類的 run 方法,首先會調用 hasTasks()方法判斷當前 taskQueue 是否有元素。若是 taskQueue 中有元素,執行selectNow() 方法,最終執行 selector.selectNow(),該方法會當即返回。若是taskQueue 沒有元素,執行 select(oldWakenUp) 方法
select ( oldWakenUp) 方法解決了 Nio 中的 bug,selectCnt 用來記錄 selector.select 方法的執行次數和標識是否執行過 selector.selectNow(),若觸發了 epoll 的空輪詢 bug,則會反覆執行 selector.select(timeoutMillis),變量 selectCnt 會逐漸變大,當 selectCnt 達到閾值(默認 512),則執行 rebuildSelector 方法,進行 selector 重建,解決 cpu 佔用 100%的 bug。
rebuildSelector 方法先經過 openSelector 方法建立一個新的 selector。而後將 old selector 的selectionKey 執行 cancel。最後將 old selector 的 channel 從新註冊到新的 selector 中。rebuild 後,須要從新執行方法 selectNow,檢查是否有已 ready 的 selectionKey。
接下來調用 processSelectedKeys 方法(處理 I/O 任務),當 selectedKeys != null 時,調用processSelectedKeysOptimized 方法,迭代 selectedKeys 獲取就緒的 IO 事件的 selectkey 存
放在數組 selectedKeys 中,而後爲每一個事件都調用processSelectedKey來處理它,processSelectedKey 中分別處理 OP_READ;OP_WRITE;OP_CONNECT 事件。最後調用 runAllTasks 方法(非 IO 任務),該方法首先會調用 fetchFromScheduledTaskQueue方法,把 scheduledTaskQueue 中已經超過延遲執行時間的任務移到 taskQueue 中等待被執行,而後依次從 taskQueue 中取任務執行,每執行 64 個任務,進行耗時檢查,若是已執行時間超過預先設定的執行時間,則中止執行非 IO 任務,避免非 IO 任務太多,影響 IO 任務的執行。
每一個 NioEventLoop 對應一個線程和一個 Selector,NioServerSocketChannel 會主動註冊到某一個 NioEventLoop 的 Selector 上,NioEventLoop 負責事件輪詢。
Outbound 事件都是請求事件, 發起者是 Channel,處理者是 unsafe,經過 Outbound 事件進行通知,傳播方向是 tail 到 head。Inbound 事件發起者是 unsafe,事件的處理者是Channel, 是通知事件,傳播方向是從頭至尾。
內存管理機制,首先會預申請一大塊內存 Arena,Arena 由許多 Chunk 組成,而每一個 Chunk默認由 2048 個 page 組成。Chunk 經過 AVL 樹的形式組織 Page,每一個葉子節點表示一個Page,而中間節點表示內存區域,節點本身記錄它在整個 Arena 中的偏移地址。當區域被分配出去後,中間節點上的標記位會被標記,這樣就表示這個中間節點如下的全部節點都已被分配了。大於 8k 的內存分配在 poolChunkList 中,而 PoolSubpage 用於分配小於 8k 的內存,它會把一個 page 分割成多段,進行內存分配。
ByteBuf 的特色:支持自動擴容(4M),保證 put 方法不會拋出異常、經過內置的複合緩衝類型,實現零拷貝(zero-copy);不須要調用 flip()來切換讀/寫模式,讀取和寫入索引分開;方法鏈;引用計數基於 AtomicIntegerFieldUpdater 用於內存回收;PooledByteBuf 採用二叉樹來實現一個內存池,集中管理內存的分配和釋放,不用每次使用都新建一個緩衝區對象。UnpooledHeapByteBuf 每次都會新建一個緩衝區對象。