前沿拓展:
MSXML 4.0 Servi
MSXML全名是:「或更前版本。安裝Nero 8時(shí)要安裝最新版本MSXML 4.0,按正常的安裝就可以了對(duì)你的電腦沒(méi)有多大的影響
1.BIO、NIO和AIO的區(qū)別?BIO:一個(gè)連接一個(gè)線程,客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理。線程開(kāi)銷大。偽異步IO:將請(qǐng)求連接放入線程池,一對(duì)多,但線程還是很寶貴的資源。NIO:一個(gè)請(qǐng)求一個(gè)線程,但客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有I/O請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。AIO:一個(gè)有效請(qǐng)求一個(gè)線程,客戶端的I/O請(qǐng)求都是由OS先完成了再通知服務(wù)器應(yīng)用去啟動(dòng)線程進(jìn)行處理,BIO是面向流的,NIO是面向緩沖區(qū)的;BIO的各種流是阻塞的。而NIO是非阻塞的;BIO的Stream是單向的,而NIO的channel是雙向的。NIO的特點(diǎn):**驅(qū)動(dòng)模型、單線程處理多任務(wù)、非阻塞I/O,I/O讀寫(xiě)不再阻塞,而是返回0、基于block的傳輸比基于流的傳輸更高效、更高級(jí)的IO函數(shù)zero-copy、IO多路復(fù)用大大提高了Java網(wǎng)絡(luò)應(yīng)用的可伸縮性和實(shí)用性?;赗eactor線程模型。在Reactor模式中,**分發(fā)器等待某個(gè)**或者可應(yīng)用或個(gè)**作的狀態(tài)發(fā)生,**分發(fā)器就把這個(gè)**傳給事先注冊(cè)的**處理函數(shù)或者回調(diào)函數(shù),由后者來(lái)做實(shí)際的讀寫(xiě)**作。如在Reactor中實(shí)現(xiàn)讀:注冊(cè)讀就緒**和相應(yīng)的**處理器、**分發(fā)器等待**、**到來(lái),激活分發(fā)器,分發(fā)器調(diào)用**對(duì)應(yīng)的處理器、**處理器完成實(shí)際的讀**作,處理讀到的數(shù)據(jù),注冊(cè)新的**,第二返還控制權(quán)。2.NIO的組成?
Buffer:與Channel進(jìn)行交互,數(shù)據(jù)是從Channel讀入緩沖區(qū),從緩沖區(qū)寫(xiě)入Channel中的
flip方法 : 反轉(zhuǎn)此緩沖區(qū),將position給limit,第二將position置為0,其實(shí)就是切換讀寫(xiě)模式
clear方法 :清除此緩沖區(qū),將position置為0,把capacity的值給limit。
rewind方法 : 重繞此緩沖區(qū),將position置為0
DirectByteBuffer可減少一次系統(tǒng)空間到用戶空間的拷貝。但Buffer創(chuàng)建和銷毀的成本更高,不可控,通常會(huì)用內(nèi)存池來(lái)提高性能。直接緩沖區(qū)主要分配給那些易受基礎(chǔ)系統(tǒng)的本機(jī)I/O **作影響的大型、持久的緩沖區(qū)。如果數(shù)據(jù)量比較小的中小應(yīng)用情況下,可以考慮使用heapBuffer,由JVM進(jìn)行管理。
Channel:表示 IO 源與目標(biāo)打開(kāi)的連接,是雙向的,但不能直接訪問(wèn)數(shù)據(jù),只能與Buffer 進(jìn)行交互。通過(guò)源碼可知,F(xiàn)ileChannel的read方法和write方法都導(dǎo)致數(shù)據(jù)**了兩次!
Selector可使一個(gè)單獨(dú)的線程管理多個(gè)Channel,open方法可創(chuàng)建Selector,register方法向多路復(fù)用器器注冊(cè)通道,可以**的**類型:讀、寫(xiě)、連接、accept。注冊(cè)**后會(huì)產(chǎn)生一個(gè)SelectionKey:它表示SelectableChannel 和Selector 之間的注冊(cè)關(guān)系,wakeup方法:使尚未返回的第一個(gè)選擇**作立即返回,喚醒的原因是:注冊(cè)了新的channel或者**;channel關(guān)閉,取消注冊(cè);優(yōu)先級(jí)更高的**觸發(fā)(如定時(shí)器**),希望及時(shí)處理。
Selector在Linux的實(shí)現(xiàn)類是EPollSelectorImpl,委托給EPollArrayWrapper實(shí)現(xiàn),其中三個(gè)native方法是對(duì)epoll的封裝,而EPollSelectorImpl. implRegister方法,通過(guò)調(diào)用epoll_ctl向epoll實(shí)例中注冊(cè)**,還將注冊(cè)的文件描述符(fd)與SelectionKey的對(duì)應(yīng)關(guān)系添加到fdToKey中,這個(gè)map維護(hù)了文件描述符與SelectionKey的映射。
fdToKey有時(shí)會(huì)變得非常大,因?yàn)樽?cè)到Selector上的Channel非常多(百萬(wàn)連接);過(guò)期或失效的Channel沒(méi)有及時(shí)關(guān)閉。fdToKey總是串行讀取的,而讀取是在select方法中進(jìn)行的,該方法是非線程安全的。
Pipe:兩個(gè)線程之間的單向數(shù)據(jù)連接,數(shù)據(jù)會(huì)被寫(xiě)到sink通道,從source通道讀取
NIO的服務(wù)端建立過(guò)程:Selector.open():打開(kāi)一個(gè)Selector;ServerSocketChannel.open():創(chuàng)建服務(wù)端的Channel;bind():綁定到某個(gè)端口上。并配置非阻塞模式;register():注冊(cè)Channel和關(guān)注的**到Selector上;select()輪詢拿到已經(jīng)就緒的**
3.Netty的特
一個(gè)高性能、異步**驅(qū)動(dòng)的NIO框架,它提供了對(duì)TCP、UDP和文件傳輸?shù)闹С?/p>
使用更高效的socket底層,對(duì)epoll空輪詢引起的cpu占用飆升在內(nèi)部進(jìn)行了處理,避免了直接使用NIO的陷阱,簡(jiǎn)化了NIO的處理方式。
采用多種decoder/encoder 支持,對(duì)TCP粘包/分包進(jìn)行自動(dòng)化處理
可使用接受/處理線程池,提高連接效率,對(duì)重連、心跳檢測(cè)的簡(jiǎn)單支持
可配置IO線程數(shù)、TCP參數(shù), TCP接收和發(fā)送緩沖區(qū)使用直接內(nèi)存代替堆內(nèi)存,通過(guò)內(nèi)存池的方式循環(huán)利用ByteBuf
通過(guò)引用計(jì)數(shù)器及時(shí)申請(qǐng)釋放不再引用的對(duì)象,降低了GC頻率
使用單線程串行化的方式,高效的Reactor線程模型
大量使用了volitale、使用了CAS和原子類、線程安全類的使用、讀寫(xiě)鎖的使用
使用
4.Netty的線程模型?
Netty通過(guò)Reactor模型基于多路復(fù)用器接收并處理用戶請(qǐng)求,內(nèi)部實(shí)現(xiàn)了兩個(gè)線程池,boss線程池和work線程池,其中boss線程池的線程負(fù)責(zé)處理請(qǐng)求的accept**,當(dāng)接收到accept**的請(qǐng)求時(shí),把對(duì)應(yīng)的socket封裝到一個(gè)NioSocketChannel中,并交給work線程池,其中work線程池負(fù)責(zé)請(qǐng)求的read和write**,由對(duì)應(yīng)的Handler處理。
單線程模型:所有I/O**作都由一個(gè)線程完成,即多路復(fù)用、**分發(fā)和處理都是在一個(gè)Reactor線程上完成的。既要接收客戶端的連接請(qǐng)求,向服務(wù)端發(fā)起連接,又要發(fā)送/讀取請(qǐng)求或應(yīng)答/響應(yīng)消息。一個(gè)NIO 線程同時(shí)處理成百上千的鏈路,性能上無(wú)法支撐,速度慢,若線程進(jìn)入**循環(huán),整個(gè)程序不可用,對(duì)于高負(fù)載、大并發(fā)的應(yīng)用場(chǎng)景不合適。
多線程模型:有一個(gè)NIO 線程(Acceptor) 只負(fù)責(zé)**服務(wù)端,接收客戶端的TCP 連接請(qǐng)求;NIO 線程池負(fù)責(zé)網(wǎng)絡(luò)IO 的**作,即消息的讀取、解碼、編碼和發(fā)送;1 個(gè)NIO 線程可以同時(shí)處理N 條鏈路,但是1 個(gè)鏈路只對(duì)應(yīng)1 個(gè)NIO 線程,這是為了防止發(fā)生并發(fā)**作問(wèn)題。但在并發(fā)百萬(wàn)客戶端連接或需要安全認(rèn)證時(shí),一個(gè)Acceptor 線程可能會(huì)存在性能不足問(wèn)題。
主從多線程模型:Acceptor 線程用于綁定**端口,接收客戶端連接,將SocketChannel 從主線程池的Reactor 線程的多路復(fù)用器上移除,重新注冊(cè)到Sub 線程池的線程上,用于處理I/O 的讀寫(xiě)等**作,從而保證mainReactor只負(fù)責(zé)接入認(rèn)證、握手等**作;
5.TCP 粘包/拆包的原因及解決方法?
TCP是以流的方式來(lái)處理數(shù)據(jù),一個(gè)完整的包可能會(huì)被TCP拆分成多個(gè)包進(jìn)行發(fā)送,也可能把小的封裝成一個(gè)大的數(shù)據(jù)包發(fā)送。
TCP粘包/分包的原因:
應(yīng)用程序?qū)懭氲淖止?jié)大小大于套接字發(fā)送緩沖區(qū)的大小,會(huì)發(fā)生拆包現(xiàn)象,而應(yīng)用程序?qū)懭霐?shù)據(jù)小于套接字緩沖區(qū)大小,網(wǎng)卡將應(yīng)用多次寫(xiě)入的數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上,這將會(huì)發(fā)生粘包現(xiàn)象;進(jìn)行MSS大小的TCP分段,當(dāng)TCP報(bào)文長(zhǎng)度-TCP頭部長(zhǎng)度>MSS的時(shí)候?qū)l(fā)生拆包以太網(wǎng)幀的payload(凈荷)大于MTU(1500字節(jié))進(jìn)行ip分片。
解決方法
消息定長(zhǎng):FixedLengthFrameDecoder類包尾增加特殊字符分割:行分隔符類:LineBasedFrameDecoder或自定義分隔符類 :DelimiterBasedFrameDecoder將消息分為消息頭和消息體:LengthFieldBasedFrameDecoder類。分為有頭部的拆包與粘包、長(zhǎng)度字段在前且有頭部的拆包與粘包、多擴(kuò)展頭部的拆包與粘包。6.了解哪幾種序列化協(xié)議?序列化(編碼)是將對(duì)象序列化為二進(jìn)制形式(字節(jié)數(shù)組),主要用于網(wǎng)絡(luò)傳輸、數(shù)據(jù)持久化等;而反序列化(解碼)則是將從網(wǎng)絡(luò)、磁盤(pán)等讀取的字節(jié)數(shù)組還原成原始對(duì)象,主要用于網(wǎng)絡(luò)傳輸對(duì)象的解碼,以便完成遠(yuǎn)程調(diào)用。影響序列化性能的關(guān)鍵因素:序列化后的碼流大?。ňW(wǎng)絡(luò)帶寬的占用)、序列化的性能(CPU資源占用);是否支持跨語(yǔ)言(異構(gòu)系統(tǒng)的對(duì)接和開(kāi)發(fā)語(yǔ)言切換)。Java默認(rèn)提供的序列化:無(wú)法跨語(yǔ)言、序列化后的碼流太大、序列化的性能差XML,優(yōu)點(diǎn):人機(jī)可讀性好,可指定元素或特性的名稱。缺點(diǎn):序列化數(shù)據(jù)只包含數(shù)據(jù)本身以及類的結(jié)構(gòu),不包括類型標(biāo)識(shí)和程序集信息;只能序列化公共屬性和字段;不能序列化方法;文件龐大,文件格式復(fù)雜,傳輸占帶寬。適用場(chǎng)景:當(dāng)做配置文件存儲(chǔ)數(shù)據(jù),實(shí)時(shí)數(shù)據(jù)轉(zhuǎn)換。JSON,是一種輕量級(jí)的數(shù)據(jù)交換格式,優(yōu)點(diǎn):兼容性高、數(shù)據(jù)格式比較簡(jiǎn)單,易于讀寫(xiě)、序列化后數(shù)據(jù)較小,可擴(kuò)展性好,兼容性好、與XML相比,其協(xié)議比較簡(jiǎn)單,解析速度比較快。缺點(diǎn):數(shù)據(jù)的描述性比XML差、不適合性能要求為ms級(jí)別的情況、額外空間開(kāi)銷比較大。適用場(chǎng)景(可替代XML):跨防火墻訪問(wèn)、可調(diào)式性要求高、基于Web browser的Ajax請(qǐng)求、傳輸數(shù)據(jù)量相對(duì)小,實(shí)時(shí)性要求相對(duì)低(例如秒級(jí)別)的服務(wù)。Fastjson,采用一種“假定有序快速匹配”的算法。優(yōu)點(diǎn):接口簡(jiǎn)單易用、目前java語(yǔ)言中最快的json庫(kù)。缺點(diǎn):過(guò)于注重快,而偏離了“標(biāo)準(zhǔn)”及功能性、代碼質(zhì)量不高,文檔不全。適用場(chǎng)景:協(xié)議交互、Web輸出、Android客戶端Thrift,不僅是序列化協(xié)議,還是一個(gè)RPC框架。優(yōu)點(diǎn):序列化后的體積小, 速度快、支持多種語(yǔ)言和豐富的數(shù)據(jù)類型、對(duì)于數(shù)據(jù)字段的增刪具有較強(qiáng)的兼容性、支持二進(jìn)制壓縮編碼。缺點(diǎn):使用者較少、跨防火墻訪問(wèn)時(shí),不安全、不具有可讀性,調(diào)試代碼時(shí)相對(duì)困難、不能與其他傳輸層協(xié)議共同使用(例如HTTP)、無(wú)法支持向持久層直接讀寫(xiě)數(shù)據(jù),即不適合做數(shù)據(jù)持久化序列化協(xié)議。適用場(chǎng)景:分布式系統(tǒng)的RPC解決方案Avro,Hadoop的一個(gè)子項(xiàng)目,解決了JSON的冗長(zhǎng)和沒(méi)有IDL的問(wèn)題。優(yōu)點(diǎn):支持豐富的數(shù)據(jù)類型、簡(jiǎn)單的動(dòng)態(tài)語(yǔ)言結(jié)合功能、具有自我描述屬性、提高了數(shù)據(jù)解析速度、快速可壓縮的二進(jìn)制數(shù)據(jù)形式、可以實(shí)現(xiàn)遠(yuǎn)程過(guò)程調(diào)用RPC、支持跨編程語(yǔ)言實(shí)現(xiàn)。缺點(diǎn):對(duì)于習(xí)慣于靜態(tài)類型語(yǔ)言的用戶不直觀。適用場(chǎng)景:在Hadoop中做Hive、Pig和MapReduce的持久化數(shù)據(jù)格式。Protobuf,將數(shù)據(jù)結(jié)構(gòu)以.proto文件進(jìn)行描述,通過(guò)代碼生成工具可以生成對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)的POJO對(duì)象和Protobuf相關(guān)的方法和屬性。優(yōu)點(diǎn):序列化后碼流小,性能高、結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式(XML JSON等)、通過(guò)標(biāo)識(shí)字段的順序,可以實(shí)現(xiàn)協(xié)議的前向兼容、結(jié)構(gòu)化的文檔更容易管理和維護(hù)。缺點(diǎn):需要依賴于工具生成代碼、支持的語(yǔ)言相對(duì)較少,官方只支持Java 、C++ 、python。適用場(chǎng)景:對(duì)性能要求高的RPC調(diào)用、具有良好的跨防火墻的訪問(wèn)屬性、適合應(yīng)用層對(duì)象的持久化其它protostuff 基于protobuf協(xié)議,但不需要配置proto文件,直接導(dǎo)包即可**oss marshaling 可以直接序列化java類, 無(wú)須實(shí)java.io.Serializable接口Message pack 一個(gè)高效的二進(jìn)制序列化格式Hessian 采用二進(jìn)制協(xié)議的輕量級(jí)remoting onhttp工具kryo 基于protobuf協(xié)議,只支持java語(yǔ)言,需要注冊(cè)(Registration),第二序列化(Output),反序列化(Input)7.如何選擇序列化協(xié)議?具體場(chǎng)景對(duì)于公司間的系統(tǒng)調(diào)用,如果性能要求在100ms以上的服務(wù),基于XML的SOAP協(xié)議是一個(gè)值得考慮的方案?;赪eb browser的Ajax,以及Mobile app與服務(wù)端之間的通訊,JSON協(xié)議是首選。對(duì)于性能要求不太高,或者以動(dòng)態(tài)類型語(yǔ)言為主,或者傳輸數(shù)據(jù)載荷很小的的運(yùn)用場(chǎng)景,JSON也是非常不錯(cuò)的選擇。對(duì)于調(diào)試環(huán)境比較惡劣的場(chǎng)景,采用JSON或XML能夠極大的提高調(diào)試效率,降低系統(tǒng)開(kāi)發(fā)成本。當(dāng)對(duì)性能和簡(jiǎn)潔性有極高要求的場(chǎng)景,Protobuf,Thrift,Avro之間具有一定的競(jìng)爭(zhēng)關(guān)系。對(duì)于T級(jí)別的數(shù)據(jù)的持久化應(yīng)用場(chǎng)景,Protobuf和Avro是首要選擇。如果持久化后的數(shù)據(jù)存儲(chǔ)在hadoop子項(xiàng)目里,Avro會(huì)是更好的選擇。對(duì)于持久層非Hadoop項(xiàng)目,以靜態(tài)類型語(yǔ)言為主的應(yīng)用場(chǎng)景,Protobuf會(huì)更符合靜態(tài)類型語(yǔ)言工程師的開(kāi)發(fā)習(xí)慣。由于Avro的設(shè)計(jì)理念偏向于動(dòng)態(tài)類型語(yǔ)言,對(duì)于動(dòng)態(tài)語(yǔ)言為主的應(yīng)用場(chǎng)景,Avro是更好的選擇。如果需要提供一個(gè)完整的RPC解決方案,Thrift是一個(gè)好的選擇。如果序列化之后需要支持不同的傳輸層協(xié)議,或者需要跨防火墻訪問(wèn)的高性能場(chǎng)景,Protobuf可以優(yōu)先考慮。protobuf的數(shù)據(jù)類型有多種:bool、double、float、int32、int64、string、bytes、enum、message。protobuf的限定符:required: 必須賦值,不能為空、optional:字段可以賦值,也可以不賦值、repeated: 該字段可以重復(fù)任意次數(shù)(包括0次)、枚舉;只能用指定的常量集中的一個(gè)值作為其值;protobuf的基本規(guī)則:每個(gè)消息中必須至少留有一個(gè)required類型的字段、包含0個(gè)或多個(gè)optional類型的字段;repeated表示的字段可以包含0個(gè)或多個(gè)數(shù)據(jù);[1,15]之內(nèi)的標(biāo)識(shí)號(hào)在編碼的時(shí)候會(huì)占用一個(gè)字節(jié)(常用),[16,2047]之內(nèi)的標(biāo)識(shí)號(hào)則占用2個(gè)字節(jié),標(biāo)識(shí)號(hào)一定不能重復(fù)、使用消息類型,也可以將消息嵌套任意多層,可用嵌套消息類型來(lái)代替組。protobuf的消息升級(jí)原則:不要更改任何已有的字段的數(shù)值標(biāo)識(shí);不能移除已經(jīng)存在的required字段,optional和repeated類型的字段可以被移除,但要保留標(biāo)號(hào)不能被重用。新添加的字段必須是optional或repeated。因?yàn)榕f版本程序無(wú)法讀取或?qū)懭胄略龅膔equired限定符的字段。編譯器為每一個(gè)消息類型生成了一個(gè).java文件,以及一個(gè)特殊的Builder類(該類是用來(lái)創(chuàng)建消息類接口的)。如:UserProto.User.Builder builder = UserProto.User.newBuilder();builder.build();Netty中的使用:ProtobufVarint32FrameDecoder 是用于處理半包消息的解碼類;ProtobufDecoder(UserProto.User.getDefaultInstance())這是創(chuàng)建的UserProto.java文件中的解碼類;ProtobufVarint32LengthFieldPrepender 對(duì)protobuf協(xié)議的消息頭上加上一個(gè)長(zhǎng)度為32的×××字段,用于標(biāo)志這個(gè)消息的長(zhǎng)度的類;ProtobufEncoder 是編碼類將StringBuilder轉(zhuǎn)換為ByteBuf類型:copiedBuffer()方法8.Netty的零拷貝實(shí)現(xiàn)Netty的接收和發(fā)送ByteBuffer采用DIRECT BUFFERS,使用堆外直接內(nèi)存進(jìn)行Socket讀寫(xiě),不需要進(jìn)行字節(jié)緩沖區(qū)的二次拷貝。堆內(nèi)存多了一次內(nèi)存拷貝,JVM會(huì)將堆內(nèi)存Buffer拷貝一份到直接內(nèi)存中,第二才寫(xiě)入Socket中。ByteBuffer由ChannelConfig分配,而ChannelConfig創(chuàng)建ByteBufAllocator默認(rèn)使用Direct BufferCompositeByteBuf 類可以將多個(gè) ByteBuf 合并為一個(gè)邏輯上的 ByteBuf, 避免了傳統(tǒng)通過(guò)內(nèi)存拷貝的方式將幾個(gè)**uffer合并成一個(gè)大的Buffer。addComponents方法將 header 與 body 合并為一個(gè)邏輯上的 ByteBuf, 這兩個(gè) ByteBuf 在CompositeByteBuf 內(nèi)部都是單獨(dú)存在的, CompositeByteBuf 只是邏輯上是一個(gè)整體通過(guò) FileRegion 包裝的FileChannel.tranferTo方法 實(shí)現(xiàn)文件傳輸, 可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo) Channel,避免了傳統(tǒng)通過(guò)循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問(wèn)題。通過(guò) wrap方法, 我們可以將 byte[] 數(shù)組、ByteBuf、ByteBuffer等包裝成一個(gè) Netty ByteBuf 對(duì)象, 進(jìn)而避免了拷貝**作。Selector BUG:若Selector的輪詢結(jié)果為空,也沒(méi)有wakeup或新消息處理,則發(fā)生空輪詢,CPU使用率100%,Netty的解決辦法:對(duì)Selector的select**作周期進(jìn)行統(tǒng)計(jì),每完成一次空的select**作進(jìn)行一次計(jì)數(shù),若在某個(gè)周期內(nèi)連續(xù)發(fā)生N次空輪詢,則觸發(fā)了epoll**循環(huán)bug。重建Selector,判斷是否是其他線程發(fā)起的重建請(qǐng)求,若不是則將原SocketChannel從舊的Selector上去除注冊(cè),重新注冊(cè)到新的Selector上,并將原來(lái)的Selector關(guān)閉。9.Netty的高性能表現(xiàn)在哪些方面?心跳,對(duì)服務(wù)端:會(huì)定時(shí)清除閑置會(huì)話inactive(netty5),對(duì)客戶端:用來(lái)檢測(cè)會(huì)話是否斷開(kāi),是否重來(lái),檢測(cè)網(wǎng)絡(luò)延遲,其中idleStateHandler類 用來(lái)檢測(cè)會(huì)話狀態(tài)串行無(wú)鎖化設(shè)計(jì),即消息的處理盡可能在同一個(gè)線程內(nèi)完成,期間不進(jìn)行線程切換,這樣就避免了多線程競(jìng)爭(zhēng)和同步鎖。表面上看,串行化設(shè)計(jì)似乎CPU利用率不高,并發(fā)程度不夠。但是,通過(guò)調(diào)整NIO線程池的線程參數(shù),可以同時(shí)啟動(dòng)多個(gè)串行化的線程并行運(yùn)行,這種局部無(wú)鎖化的串行線程設(shè)計(jì)相比一個(gè)隊(duì)列-多個(gè)工作線程模型性能更優(yōu)??煽啃?,鏈路有效性檢測(cè):鏈路空閑檢測(cè)機(jī)制,讀/寫(xiě)空閑超時(shí)機(jī)制;內(nèi)存保護(hù)機(jī)制:通過(guò)內(nèi)存池重用ByteBuf;ByteBuf的解碼保護(hù);優(yōu)雅停機(jī):不再接收新消息、退出前的預(yù)處理**作、資源的釋放**作。Netty安全性:支持的安全協(xié)議:SSL V2和V3,TLS,SSL單向認(rèn)證、雙向認(rèn)證和第三方CA認(rèn)證。高效并發(fā)編程的體現(xiàn):volatile的大量、正確使用;CAS和原子類的廣泛使用;線程安全容器的使用;通過(guò)讀寫(xiě)鎖提升并發(fā)性能。IO通信性能三原則:傳輸(AIO)、協(xié)議(Http)、線程(主從多線程)流量整型的作用(變壓器):防止由于上下游網(wǎng)元性能不均衡導(dǎo)致下游網(wǎng)元被壓垮,業(yè)務(wù)流中斷;防止由于通信模塊接受消息過(guò)快,后端業(yè)務(wù)線程處理不及時(shí)導(dǎo)致?lián)?*問(wèn)題。TCP參數(shù)配置:SO_RCVBUF和SO_SNDBUF:通常建議值為128K或者256K;SO_TCPNODELAY:NAGLE算法通過(guò)將緩沖區(qū)內(nèi)的小封包自動(dòng)相連,組成較大的封包,阻止大量小封包的發(fā)送阻塞網(wǎng)絡(luò),從而提高網(wǎng)絡(luò)應(yīng)用效率。但是對(duì)于時(shí)延敏感的應(yīng)用場(chǎng)景需要關(guān)閉該優(yōu)化算法;10.NIOEventLoopGroup源碼
NioEventLoopGroup(其實(shí)是MultithreadEventExecutorGroup) 內(nèi)部維護(hù)一個(gè)類型為 EventExecutor children [], 默認(rèn)大小是處理器核數(shù) * 2, 這樣就構(gòu)成了一個(gè)線程池,初始化EventExecutor時(shí)NioEventLoopGroup重載newChild方法,所以children元素的實(shí)際類型為NioEventLoop。線程啟動(dòng)時(shí)調(diào)用SingleThreadEventExecutor的構(gòu)造方法,執(zhí)行NioEventLoop類的run方法,第一會(huì)調(diào)用hasTasks()方法判斷當(dāng)前taskQueue是否有元素。如果taskQueue中有元素,執(zhí)行 selectNow() 方法,最終執(zhí)行selector.selectNow(),該方**立即返回。如果taskQueue沒(méi)有元素,執(zhí)行 select(oldWakenUp) 方法select ( oldWakenUp) 方法解決了 Nio 中的 bug,selectCnt 用來(lái)記錄selector.select方法的執(zhí)行次數(shù)和標(biāo)識(shí)是否執(zhí)行過(guò)selector.selectNow(),若觸發(fā)了epoll的空輪詢bug,則會(huì)反復(fù)執(zhí)行selector.select(timeoutMillis),變量selectCnt 會(huì)逐漸變大,當(dāng)selectCnt 達(dá)到閾值(默認(rèn)512),則執(zhí)行rebuildSelector方法,進(jìn)行selector重建,解決cpu占用100%的bug。rebuildSelector方法先通過(guò)openSelector方法創(chuàng)建一個(gè)新的selector。第二將old selector的selectionKey執(zhí)行cancel。最后將old selector的channel重新注冊(cè)到新的selector中。rebuild后,需要重新執(zhí)行方法selectNow,檢查是否有已ready的selectionKey。接下來(lái)調(diào)用processSelectedKeys 方法(處理I/O任務(wù)),當(dāng)selectedKeys != null時(shí),調(diào)用processSelectedKeysOptimized方法,迭代 selectedKeys 獲取就緒的 IO **的selectkey存放在數(shù)組selectedKeys中, 第二為每個(gè)**都調(diào)用 processSelectedKey 來(lái)處理它,processSelectedKey 中分別處理OP_READ;OP_WRITE;OP_CONNECT**。最后調(diào)用runAllTasks方法(非IO任務(wù)),該方法第一會(huì)調(diào)用fetchFromScheduledTaskQueue方法,把scheduledTaskQueue中已經(jīng)超過(guò)延遲執(zhí)行時(shí)間的任務(wù)移到taskQueue中等待被執(zhí)行,第二依次從taskQueue中取任務(wù)執(zhí)行,每執(zhí)行64個(gè)任務(wù),進(jìn)行耗時(shí)檢查,如果已執(zhí)行時(shí)間超過(guò)預(yù)先設(shè)定的執(zhí)行時(shí)間,則停止執(zhí)行非IO任務(wù),避免非IO任務(wù)太多,影響IO任務(wù)的執(zhí)行。每個(gè)NioEventLoop對(duì)應(yīng)一個(gè)線程和一個(gè)Selector,NioServerSocketChannel會(huì)主動(dòng)注冊(cè)到某一個(gè)NioEventLoop的Selector上,NioEventLoop負(fù)責(zé)**輪詢。Outbound **都是請(qǐng)求**, 發(fā)起者是 Channel,處理者是 unsafe,通過(guò) Outbound **進(jìn)行通知,傳播方向是 tail到head。Inbound **發(fā)起者是 unsafe,**的處理者是 Channel, 是通知**,傳播方向是從頭到尾。內(nèi)存管理機(jī)制,第一會(huì)預(yù)申請(qǐng)一大塊內(nèi)存Arena,Arena由許多Chunk組成,而每個(gè)Chunk默認(rèn)由2048個(gè)page組成。Chunk通過(guò)AVL樹(shù)的形式組織Page,每個(gè)葉子節(jié)點(diǎn)表示一個(gè)Page,而中間節(jié)點(diǎn)表示內(nèi)存區(qū)域,節(jié)點(diǎn)自己記錄它在整個(gè)Arena中的偏移地址。當(dāng)區(qū)域被分配出去后,中間節(jié)點(diǎn)上的標(biāo)記位會(huì)被標(biāo)記,這樣就表示這個(gè)中間節(jié)點(diǎn)以下的所有節(jié)點(diǎn)都已被分配了。大于8k的內(nèi)存分配在poolChunkList中,而PoolSubpage用于分配小于8k的內(nèi)存,它會(huì)把一個(gè)page分割成多段,進(jìn)行內(nèi)存分配。
ByteBuf的特點(diǎn):支持自動(dòng)擴(kuò)容(4M),保證put方法不會(huì)拋出異常、通過(guò)內(nèi)置的復(fù)合緩沖類型,實(shí)現(xiàn)零拷貝(zero-copy);不需要調(diào)用flip()來(lái)切換讀/寫(xiě)模式,讀取和寫(xiě)入索引分開(kāi);方法鏈;引用計(jì)數(shù)基于AtomicIntegerFieldUpdater用于內(nèi)存回收;PooledByteBuf采用二叉樹(shù)來(lái)實(shí)現(xiàn)一個(gè)內(nèi)存池,集中管理內(nèi)存的分配和釋放,不用每次使用都新建一個(gè)緩沖區(qū)對(duì)象。UnpooledHeapByteBuf每次都會(huì)新建一個(gè)緩沖區(qū)對(duì)象。
本文首次分布在https://blog.51cto.com/9928699/2354140
拓展知識(shí):
前沿拓展:
MSXML 4.0 Servi
MSXML全名是:「或更前版本。安裝Nero 8時(shí)要安裝最新版本MSXML 4.0,按正常的安裝就可以了對(duì)你的電腦沒(méi)有多大的影響
1.BIO、NIO和AIO的區(qū)別?BIO:一個(gè)連接一個(gè)線程,客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理。線程開(kāi)銷大。偽異步IO:將請(qǐng)求連接放入線程池,一對(duì)多,但線程還是很寶貴的資源。NIO:一個(gè)請(qǐng)求一個(gè)線程,但客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有I/O請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。AIO:一個(gè)有效請(qǐng)求一個(gè)線程,客戶端的I/O請(qǐng)求都是由OS先完成了再通知服務(wù)器應(yīng)用去啟動(dòng)線程進(jìn)行處理,BIO是面向流的,NIO是面向緩沖區(qū)的;BIO的各種流是阻塞的。而NIO是非阻塞的;BIO的Stream是單向的,而NIO的channel是雙向的。NIO的特點(diǎn):**驅(qū)動(dòng)模型、單線程處理多任務(wù)、非阻塞I/O,I/O讀寫(xiě)不再阻塞,而是返回0、基于block的傳輸比基于流的傳輸更高效、更高級(jí)的IO函數(shù)zero-copy、IO多路復(fù)用大大提高了Java網(wǎng)絡(luò)應(yīng)用的可伸縮性和實(shí)用性?;赗eactor線程模型。在Reactor模式中,**分發(fā)器等待某個(gè)**或者可應(yīng)用或個(gè)**作的狀態(tài)發(fā)生,**分發(fā)器就把這個(gè)**傳給事先注冊(cè)的**處理函數(shù)或者回調(diào)函數(shù),由后者來(lái)做實(shí)際的讀寫(xiě)**作。如在Reactor中實(shí)現(xiàn)讀:注冊(cè)讀就緒**和相應(yīng)的**處理器、**分發(fā)器等待**、**到來(lái),激活分發(fā)器,分發(fā)器調(diào)用**對(duì)應(yīng)的處理器、**處理器完成實(shí)際的讀**作,處理讀到的數(shù)據(jù),注冊(cè)新的**,第二返還控制權(quán)。2.NIO的組成?
Buffer:與Channel進(jìn)行交互,數(shù)據(jù)是從Channel讀入緩沖區(qū),從緩沖區(qū)寫(xiě)入Channel中的
flip方法 : 反轉(zhuǎn)此緩沖區(qū),將position給limit,第二將position置為0,其實(shí)就是切換讀寫(xiě)模式
clear方法 :清除此緩沖區(qū),將position置為0,把capacity的值給limit。
rewind方法 : 重繞此緩沖區(qū),將position置為0
DirectByteBuffer可減少一次系統(tǒng)空間到用戶空間的拷貝。但Buffer創(chuàng)建和銷毀的成本更高,不可控,通常會(huì)用內(nèi)存池來(lái)提高性能。直接緩沖區(qū)主要分配給那些易受基礎(chǔ)系統(tǒng)的本機(jī)I/O **作影響的大型、持久的緩沖區(qū)。如果數(shù)據(jù)量比較小的中小應(yīng)用情況下,可以考慮使用heapBuffer,由JVM進(jìn)行管理。
Channel:表示 IO 源與目標(biāo)打開(kāi)的連接,是雙向的,但不能直接訪問(wèn)數(shù)據(jù),只能與Buffer 進(jìn)行交互。通過(guò)源碼可知,F(xiàn)ileChannel的read方法和write方法都導(dǎo)致數(shù)據(jù)**了兩次!
Selector可使一個(gè)單獨(dú)的線程管理多個(gè)Channel,open方法可創(chuàng)建Selector,register方法向多路復(fù)用器器注冊(cè)通道,可以**的**類型:讀、寫(xiě)、連接、accept。注冊(cè)**后會(huì)產(chǎn)生一個(gè)SelectionKey:它表示SelectableChannel 和Selector 之間的注冊(cè)關(guān)系,wakeup方法:使尚未返回的第一個(gè)選擇**作立即返回,喚醒的原因是:注冊(cè)了新的channel或者**;channel關(guān)閉,取消注冊(cè);優(yōu)先級(jí)更高的**觸發(fā)(如定時(shí)器**),希望及時(shí)處理。
Selector在Linux的實(shí)現(xiàn)類是EPollSelectorImpl,委托給EPollArrayWrapper實(shí)現(xiàn),其中三個(gè)native方法是對(duì)epoll的封裝,而EPollSelectorImpl. implRegister方法,通過(guò)調(diào)用epoll_ctl向epoll實(shí)例中注冊(cè)**,還將注冊(cè)的文件描述符(fd)與SelectionKey的對(duì)應(yīng)關(guān)系添加到fdToKey中,這個(gè)map維護(hù)了文件描述符與SelectionKey的映射。
fdToKey有時(shí)會(huì)變得非常大,因?yàn)樽?cè)到Selector上的Channel非常多(百萬(wàn)連接);過(guò)期或失效的Channel沒(méi)有及時(shí)關(guān)閉。fdToKey總是串行讀取的,而讀取是在select方法中進(jìn)行的,該方法是非線程安全的。
Pipe:兩個(gè)線程之間的單向數(shù)據(jù)連接,數(shù)據(jù)會(huì)被寫(xiě)到sink通道,從source通道讀取
NIO的服務(wù)端建立過(guò)程:Selector.open():打開(kāi)一個(gè)Selector;ServerSocketChannel.open():創(chuàng)建服務(wù)端的Channel;bind():綁定到某個(gè)端口上。并配置非阻塞模式;register():注冊(cè)Channel和關(guān)注的**到Selector上;select()輪詢拿到已經(jīng)就緒的**
3.Netty的特
一個(gè)高性能、異步**驅(qū)動(dòng)的NIO框架,它提供了對(duì)TCP、UDP和文件傳輸?shù)闹С?/p>
使用更高效的socket底層,對(duì)epoll空輪詢引起的cpu占用飆升在內(nèi)部進(jìn)行了處理,避免了直接使用NIO的陷阱,簡(jiǎn)化了NIO的處理方式。
采用多種decoder/encoder 支持,對(duì)TCP粘包/分包進(jìn)行自動(dòng)化處理
可使用接受/處理線程池,提高連接效率,對(duì)重連、心跳檢測(cè)的簡(jiǎn)單支持
可配置IO線程數(shù)、TCP參數(shù), TCP接收和發(fā)送緩沖區(qū)使用直接內(nèi)存代替堆內(nèi)存,通過(guò)內(nèi)存池的方式循環(huán)利用ByteBuf
通過(guò)引用計(jì)數(shù)器及時(shí)申請(qǐng)釋放不再引用的對(duì)象,降低了GC頻率
使用單線程串行化的方式,高效的Reactor線程模型
大量使用了volitale、使用了CAS和原子類、線程安全類的使用、讀寫(xiě)鎖的使用
使用
4.Netty的線程模型?
Netty通過(guò)Reactor模型基于多路復(fù)用器接收并處理用戶請(qǐng)求,內(nèi)部實(shí)現(xiàn)了兩個(gè)線程池,boss線程池和work線程池,其中boss線程池的線程負(fù)責(zé)處理請(qǐng)求的accept**,當(dāng)接收到accept**的請(qǐng)求時(shí),把對(duì)應(yīng)的socket封裝到一個(gè)NioSocketChannel中,并交給work線程池,其中work線程池負(fù)責(zé)請(qǐng)求的read和write**,由對(duì)應(yīng)的Handler處理。
單線程模型:所有I/O**作都由一個(gè)線程完成,即多路復(fù)用、**分發(fā)和處理都是在一個(gè)Reactor線程上完成的。既要接收客戶端的連接請(qǐng)求,向服務(wù)端發(fā)起連接,又要發(fā)送/讀取請(qǐng)求或應(yīng)答/響應(yīng)消息。一個(gè)NIO 線程同時(shí)處理成百上千的鏈路,性能上無(wú)法支撐,速度慢,若線程進(jìn)入**循環(huán),整個(gè)程序不可用,對(duì)于高負(fù)載、大并發(fā)的應(yīng)用場(chǎng)景不合適。
多線程模型:有一個(gè)NIO 線程(Acceptor) 只負(fù)責(zé)**服務(wù)端,接收客戶端的TCP 連接請(qǐng)求;NIO 線程池負(fù)責(zé)網(wǎng)絡(luò)IO 的**作,即消息的讀取、解碼、編碼和發(fā)送;1 個(gè)NIO 線程可以同時(shí)處理N 條鏈路,但是1 個(gè)鏈路只對(duì)應(yīng)1 個(gè)NIO 線程,這是為了防止發(fā)生并發(fā)**作問(wèn)題。但在并發(fā)百萬(wàn)客戶端連接或需要安全認(rèn)證時(shí),一個(gè)Acceptor 線程可能會(huì)存在性能不足問(wèn)題。
主從多線程模型:Acceptor 線程用于綁定**端口,接收客戶端連接,將SocketChannel 從主線程池的Reactor 線程的多路復(fù)用器上移除,重新注冊(cè)到Sub 線程池的線程上,用于處理I/O 的讀寫(xiě)等**作,從而保證mainReactor只負(fù)責(zé)接入認(rèn)證、握手等**作;
5.TCP 粘包/拆包的原因及解決方法?
TCP是以流的方式來(lái)處理數(shù)據(jù),一個(gè)完整的包可能會(huì)被TCP拆分成多個(gè)包進(jìn)行發(fā)送,也可能把小的封裝成一個(gè)大的數(shù)據(jù)包發(fā)送。
TCP粘包/分包的原因:
應(yīng)用程序?qū)懭氲淖止?jié)大小大于套接字發(fā)送緩沖區(qū)的大小,會(huì)發(fā)生拆包現(xiàn)象,而應(yīng)用程序?qū)懭霐?shù)據(jù)小于套接字緩沖區(qū)大小,網(wǎng)卡將應(yīng)用多次寫(xiě)入的數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上,這將會(huì)發(fā)生粘包現(xiàn)象;進(jìn)行MSS大小的TCP分段,當(dāng)TCP報(bào)文長(zhǎng)度-TCP頭部長(zhǎng)度>MSS的時(shí)候?qū)l(fā)生拆包以太網(wǎng)幀的payload(凈荷)大于MTU(1500字節(jié))進(jìn)行ip分片。
解決方法
消息定長(zhǎng):FixedLengthFrameDecoder類包尾增加特殊字符分割:行分隔符類:LineBasedFrameDecoder或自定義分隔符類 :DelimiterBasedFrameDecoder將消息分為消息頭和消息體:LengthFieldBasedFrameDecoder類。分為有頭部的拆包與粘包、長(zhǎng)度字段在前且有頭部的拆包與粘包、多擴(kuò)展頭部的拆包與粘包。6.了解哪幾種序列化協(xié)議?序列化(編碼)是將對(duì)象序列化為二進(jìn)制形式(字節(jié)數(shù)組),主要用于網(wǎng)絡(luò)傳輸、數(shù)據(jù)持久化等;而反序列化(解碼)則是將從網(wǎng)絡(luò)、磁盤(pán)等讀取的字節(jié)數(shù)組還原成原始對(duì)象,主要用于網(wǎng)絡(luò)傳輸對(duì)象的解碼,以便完成遠(yuǎn)程調(diào)用。影響序列化性能的關(guān)鍵因素:序列化后的碼流大小(網(wǎng)絡(luò)帶寬的占用)、序列化的性能(CPU資源占用);是否支持跨語(yǔ)言(異構(gòu)系統(tǒng)的對(duì)接和開(kāi)發(fā)語(yǔ)言切換)。Java默認(rèn)提供的序列化:無(wú)法跨語(yǔ)言、序列化后的碼流太大、序列化的性能差XML,優(yōu)點(diǎn):人機(jī)可讀性好,可指定元素或特性的名稱。缺點(diǎn):序列化數(shù)據(jù)只包含數(shù)據(jù)本身以及類的結(jié)構(gòu),不包括類型標(biāo)識(shí)和程序集信息;只能序列化公共屬性和字段;不能序列化方法;文件龐大,文件格式復(fù)雜,傳輸占帶寬。適用場(chǎng)景:當(dāng)做配置文件存儲(chǔ)數(shù)據(jù),實(shí)時(shí)數(shù)據(jù)轉(zhuǎn)換。JSON,是一種輕量級(jí)的數(shù)據(jù)交換格式,優(yōu)點(diǎn):兼容性高、數(shù)據(jù)格式比較簡(jiǎn)單,易于讀寫(xiě)、序列化后數(shù)據(jù)較小,可擴(kuò)展性好,兼容性好、與XML相比,其協(xié)議比較簡(jiǎn)單,解析速度比較快。缺點(diǎn):數(shù)據(jù)的描述性比XML差、不適合性能要求為ms級(jí)別的情況、額外空間開(kāi)銷比較大。適用場(chǎng)景(可替代XML):跨防火墻訪問(wèn)、可調(diào)式性要求高、基于Web browser的Ajax請(qǐng)求、傳輸數(shù)據(jù)量相對(duì)小,實(shí)時(shí)性要求相對(duì)低(例如秒級(jí)別)的服務(wù)。Fastjson,采用一種“假定有序快速匹配”的算法。優(yōu)點(diǎn):接口簡(jiǎn)單易用、目前java語(yǔ)言中最快的json庫(kù)。缺點(diǎn):過(guò)于注重快,而偏離了“標(biāo)準(zhǔn)”及功能性、代碼質(zhì)量不高,文檔不全。適用場(chǎng)景:協(xié)議交互、Web輸出、Android客戶端Thrift,不僅是序列化協(xié)議,還是一個(gè)RPC框架。優(yōu)點(diǎn):序列化后的體積小, 速度快、支持多種語(yǔ)言和豐富的數(shù)據(jù)類型、對(duì)于數(shù)據(jù)字段的增刪具有較強(qiáng)的兼容性、支持二進(jìn)制壓縮編碼。缺點(diǎn):使用者較少、跨防火墻訪問(wèn)時(shí),不安全、不具有可讀性,調(diào)試代碼時(shí)相對(duì)困難、不能與其他傳輸層協(xié)議共同使用(例如HTTP)、無(wú)法支持向持久層直接讀寫(xiě)數(shù)據(jù),即不適合做數(shù)據(jù)持久化序列化協(xié)議。適用場(chǎng)景:分布式系統(tǒng)的RPC解決方案Avro,Hadoop的一個(gè)子項(xiàng)目,解決了JSON的冗長(zhǎng)和沒(méi)有IDL的問(wèn)題。優(yōu)點(diǎn):支持豐富的數(shù)據(jù)類型、簡(jiǎn)單的動(dòng)態(tài)語(yǔ)言結(jié)合功能、具有自我描述屬性、提高了數(shù)據(jù)解析速度、快速可壓縮的二進(jìn)制數(shù)據(jù)形式、可以實(shí)現(xiàn)遠(yuǎn)程過(guò)程調(diào)用RPC、支持跨編程語(yǔ)言實(shí)現(xiàn)。缺點(diǎn):對(duì)于習(xí)慣于靜態(tài)類型語(yǔ)言的用戶不直觀。適用場(chǎng)景:在Hadoop中做Hive、Pig和MapReduce的持久化數(shù)據(jù)格式。Protobuf,將數(shù)據(jù)結(jié)構(gòu)以.proto文件進(jìn)行描述,通過(guò)代碼生成工具可以生成對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)的POJO對(duì)象和Protobuf相關(guān)的方法和屬性。優(yōu)點(diǎn):序列化后碼流小,性能高、結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式(XML JSON等)、通過(guò)標(biāo)識(shí)字段的順序,可以實(shí)現(xiàn)協(xié)議的前向兼容、結(jié)構(gòu)化的文檔更容易管理和維護(hù)。缺點(diǎn):需要依賴于工具生成代碼、支持的語(yǔ)言相對(duì)較少,官方只支持Java 、C++ 、python。適用場(chǎng)景:對(duì)性能要求高的RPC調(diào)用、具有良好的跨防火墻的訪問(wèn)屬性、適合應(yīng)用層對(duì)象的持久化其它protostuff 基于protobuf協(xié)議,但不需要配置proto文件,直接導(dǎo)包即可**oss marshaling 可以直接序列化java類, 無(wú)須實(shí)java.io.Serializable接口Message pack 一個(gè)高效的二進(jìn)制序列化格式Hessian 采用二進(jìn)制協(xié)議的輕量級(jí)remoting onhttp工具kryo 基于protobuf協(xié)議,只支持java語(yǔ)言,需要注冊(cè)(Registration),第二序列化(Output),反序列化(Input)7.如何選擇序列化協(xié)議?具體場(chǎng)景對(duì)于公司間的系統(tǒng)調(diào)用,如果性能要求在100ms以上的服務(wù),基于XML的SOAP協(xié)議是一個(gè)值得考慮的方案?;赪eb browser的Ajax,以及Mobile app與服務(wù)端之間的通訊,JSON協(xié)議是首選。對(duì)于性能要求不太高,或者以動(dòng)態(tài)類型語(yǔ)言為主,或者傳輸數(shù)據(jù)載荷很小的的運(yùn)用場(chǎng)景,JSON也是非常不錯(cuò)的選擇。對(duì)于調(diào)試環(huán)境比較惡劣的場(chǎng)景,采用JSON或XML能夠極大的提高調(diào)試效率,降低系統(tǒng)開(kāi)發(fā)成本。當(dāng)對(duì)性能和簡(jiǎn)潔性有極高要求的場(chǎng)景,Protobuf,Thrift,Avro之間具有一定的競(jìng)爭(zhēng)關(guān)系。對(duì)于T級(jí)別的數(shù)據(jù)的持久化應(yīng)用場(chǎng)景,Protobuf和Avro是首要選擇。如果持久化后的數(shù)據(jù)存儲(chǔ)在hadoop子項(xiàng)目里,Avro會(huì)是更好的選擇。對(duì)于持久層非Hadoop項(xiàng)目,以靜態(tài)類型語(yǔ)言為主的應(yīng)用場(chǎng)景,Protobuf會(huì)更符合靜態(tài)類型語(yǔ)言工程師的開(kāi)發(fā)習(xí)慣。由于Avro的設(shè)計(jì)理念偏向于動(dòng)態(tài)類型語(yǔ)言,對(duì)于動(dòng)態(tài)語(yǔ)言為主的應(yīng)用場(chǎng)景,Avro是更好的選擇。如果需要提供一個(gè)完整的RPC解決方案,Thrift是一個(gè)好的選擇。如果序列化之后需要支持不同的傳輸層協(xié)議,或者需要跨防火墻訪問(wèn)的高性能場(chǎng)景,Protobuf可以優(yōu)先考慮。protobuf的數(shù)據(jù)類型有多種:bool、double、float、int32、int64、string、bytes、enum、message。protobuf的限定符:required: 必須賦值,不能為空、optional:字段可以賦值,也可以不賦值、repeated: 該字段可以重復(fù)任意次數(shù)(包括0次)、枚舉;只能用指定的常量集中的一個(gè)值作為其值;protobuf的基本規(guī)則:每個(gè)消息中必須至少留有一個(gè)required類型的字段、包含0個(gè)或多個(gè)optional類型的字段;repeated表示的字段可以包含0個(gè)或多個(gè)數(shù)據(jù);[1,15]之內(nèi)的標(biāo)識(shí)號(hào)在編碼的時(shí)候會(huì)占用一個(gè)字節(jié)(常用),[16,2047]之內(nèi)的標(biāo)識(shí)號(hào)則占用2個(gè)字節(jié),標(biāo)識(shí)號(hào)一定不能重復(fù)、使用消息類型,也可以將消息嵌套任意多層,可用嵌套消息類型來(lái)代替組。protobuf的消息升級(jí)原則:不要更改任何已有的字段的數(shù)值標(biāo)識(shí);不能移除已經(jīng)存在的required字段,optional和repeated類型的字段可以被移除,但要保留標(biāo)號(hào)不能被重用。新添加的字段必須是optional或repeated。因?yàn)榕f版本程序無(wú)法讀取或?qū)懭胄略龅膔equired限定符的字段。編譯器為每一個(gè)消息類型生成了一個(gè).java文件,以及一個(gè)特殊的Builder類(該類是用來(lái)創(chuàng)建消息類接口的)。如:UserProto.User.Builder builder = UserProto.User.newBuilder();builder.build();Netty中的使用:ProtobufVarint32FrameDecoder 是用于處理半包消息的解碼類;ProtobufDecoder(UserProto.User.getDefaultInstance())這是創(chuàng)建的UserProto.java文件中的解碼類;ProtobufVarint32LengthFieldPrepender 對(duì)protobuf協(xié)議的消息頭上加上一個(gè)長(zhǎng)度為32的×××字段,用于標(biāo)志這個(gè)消息的長(zhǎng)度的類;ProtobufEncoder 是編碼類將StringBuilder轉(zhuǎn)換為ByteBuf類型:copiedBuffer()方法8.Netty的零拷貝實(shí)現(xiàn)Netty的接收和發(fā)送ByteBuffer采用DIRECT BUFFERS,使用堆外直接內(nèi)存進(jìn)行Socket讀寫(xiě),不需要進(jìn)行字節(jié)緩沖區(qū)的二次拷貝。堆內(nèi)存多了一次內(nèi)存拷貝,JVM會(huì)將堆內(nèi)存Buffer拷貝一份到直接內(nèi)存中,第二才寫(xiě)入Socket中。ByteBuffer由ChannelConfig分配,而ChannelConfig創(chuàng)建ByteBufAllocator默認(rèn)使用Direct BufferCompositeByteBuf 類可以將多個(gè) ByteBuf 合并為一個(gè)邏輯上的 ByteBuf, 避免了傳統(tǒng)通過(guò)內(nèi)存拷貝的方式將幾個(gè)**uffer合并成一個(gè)大的Buffer。addComponents方法將 header 與 body 合并為一個(gè)邏輯上的 ByteBuf, 這兩個(gè) ByteBuf 在CompositeByteBuf 內(nèi)部都是單獨(dú)存在的, CompositeByteBuf 只是邏輯上是一個(gè)整體通過(guò) FileRegion 包裝的FileChannel.tranferTo方法 實(shí)現(xiàn)文件傳輸, 可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo) Channel,避免了傳統(tǒng)通過(guò)循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問(wèn)題。通過(guò) wrap方法, 我們可以將 byte[] 數(shù)組、ByteBuf、ByteBuffer等包裝成一個(gè) Netty ByteBuf 對(duì)象, 進(jìn)而避免了拷貝**作。Selector BUG:若Selector的輪詢結(jié)果為空,也沒(méi)有wakeup或新消息處理,則發(fā)生空輪詢,CPU使用率100%,Netty的解決辦法:對(duì)Selector的select**作周期進(jìn)行統(tǒng)計(jì),每完成一次空的select**作進(jìn)行一次計(jì)數(shù),若在某個(gè)周期內(nèi)連續(xù)發(fā)生N次空輪詢,則觸發(fā)了epoll**循環(huán)bug。重建Selector,判斷是否是其他線程發(fā)起的重建請(qǐng)求,若不是則將原SocketChannel從舊的Selector上去除注冊(cè),重新注冊(cè)到新的Selector上,并將原來(lái)的Selector關(guān)閉。9.Netty的高性能表現(xiàn)在哪些方面?心跳,對(duì)服務(wù)端:會(huì)定時(shí)清除閑置會(huì)話inactive(netty5),對(duì)客戶端:用來(lái)檢測(cè)會(huì)話是否斷開(kāi),是否重來(lái),檢測(cè)網(wǎng)絡(luò)延遲,其中idleStateHandler類 用來(lái)檢測(cè)會(huì)話狀態(tài)串行無(wú)鎖化設(shè)計(jì),即消息的處理盡可能在同一個(gè)線程內(nèi)完成,期間不進(jìn)行線程切換,這樣就避免了多線程競(jìng)爭(zhēng)和同步鎖。表面上看,串行化設(shè)計(jì)似乎CPU利用率不高,并發(fā)程度不夠。但是,通過(guò)調(diào)整NIO線程池的線程參數(shù),可以同時(shí)啟動(dòng)多個(gè)串行化的線程并行運(yùn)行,這種局部無(wú)鎖化的串行線程設(shè)計(jì)相比一個(gè)隊(duì)列-多個(gè)工作線程模型性能更優(yōu)。可靠性,鏈路有效性檢測(cè):鏈路空閑檢測(cè)機(jī)制,讀/寫(xiě)空閑超時(shí)機(jī)制;內(nèi)存保護(hù)機(jī)制:通過(guò)內(nèi)存池重用ByteBuf;ByteBuf的解碼保護(hù);優(yōu)雅停機(jī):不再接收新消息、退出前的預(yù)處理**作、資源的釋放**作。Netty安全性:支持的安全協(xié)議:SSL V2和V3,TLS,SSL單向認(rèn)證、雙向認(rèn)證和第三方CA認(rèn)證。高效并發(fā)編程的體現(xiàn):volatile的大量、正確使用;CAS和原子類的廣泛使用;線程安全容器的使用;通過(guò)讀寫(xiě)鎖提升并發(fā)性能。IO通信性能三原則:傳輸(AIO)、協(xié)議(Http)、線程(主從多線程)流量整型的作用(變壓器):防止由于上下游網(wǎng)元性能不均衡導(dǎo)致下游網(wǎng)元被壓垮,業(yè)務(wù)流中斷;防止由于通信模塊接受消息過(guò)快,后端業(yè)務(wù)線程處理不及時(shí)導(dǎo)致?lián)?*問(wèn)題。TCP參數(shù)配置:SO_RCVBUF和SO_SNDBUF:通常建議值為128K或者256K;SO_TCPNODELAY:NAGLE算法通過(guò)將緩沖區(qū)內(nèi)的小封包自動(dòng)相連,組成較大的封包,阻止大量小封包的發(fā)送阻塞網(wǎng)絡(luò),從而提高網(wǎng)絡(luò)應(yīng)用效率。但是對(duì)于時(shí)延敏感的應(yīng)用場(chǎng)景需要關(guān)閉該優(yōu)化算法;10.NIOEventLoopGroup源碼
NioEventLoopGroup(其實(shí)是MultithreadEventExecutorGroup) 內(nèi)部維護(hù)一個(gè)類型為 EventExecutor children [], 默認(rèn)大小是處理器核數(shù) * 2, 這樣就構(gòu)成了一個(gè)線程池,初始化EventExecutor時(shí)NioEventLoopGroup重載newChild方法,所以children元素的實(shí)際類型為NioEventLoop。線程啟動(dòng)時(shí)調(diào)用SingleThreadEventExecutor的構(gòu)造方法,執(zhí)行NioEventLoop類的run方法,第一會(huì)調(diào)用hasTasks()方法判斷當(dāng)前taskQueue是否有元素。如果taskQueue中有元素,執(zhí)行 selectNow() 方法,最終執(zhí)行selector.selectNow(),該方**立即返回。如果taskQueue沒(méi)有元素,執(zhí)行 select(oldWakenUp) 方法select ( oldWakenUp) 方法解決了 Nio 中的 bug,selectCnt 用來(lái)記錄selector.select方法的執(zhí)行次數(shù)和標(biāo)識(shí)是否執(zhí)行過(guò)selector.selectNow(),若觸發(fā)了epoll的空輪詢bug,則會(huì)反復(fù)執(zhí)行selector.select(timeoutMillis),變量selectCnt 會(huì)逐漸變大,當(dāng)selectCnt 達(dá)到閾值(默認(rèn)512),則執(zhí)行rebuildSelector方法,進(jìn)行selector重建,解決cpu占用100%的bug。rebuildSelector方法先通過(guò)openSelector方法創(chuàng)建一個(gè)新的selector。第二將old selector的selectionKey執(zhí)行cancel。最后將old selector的channel重新注冊(cè)到新的selector中。rebuild后,需要重新執(zhí)行方法selectNow,檢查是否有已ready的selectionKey。接下來(lái)調(diào)用processSelectedKeys 方法(處理I/O任務(wù)),當(dāng)selectedKeys != null時(shí),調(diào)用processSelectedKeysOptimized方法,迭代 selectedKeys 獲取就緒的 IO **的selectkey存放在數(shù)組selectedKeys中, 第二為每個(gè)**都調(diào)用 processSelectedKey 來(lái)處理它,processSelectedKey 中分別處理OP_READ;OP_WRITE;OP_CONNECT**。最后調(diào)用runAllTasks方法(非IO任務(wù)),該方法第一會(huì)調(diào)用fetchFromScheduledTaskQueue方法,把scheduledTaskQueue中已經(jīng)超過(guò)延遲執(zhí)行時(shí)間的任務(wù)移到taskQueue中等待被執(zhí)行,第二依次從taskQueue中取任務(wù)執(zhí)行,每執(zhí)行64個(gè)任務(wù),進(jìn)行耗時(shí)檢查,如果已執(zhí)行時(shí)間超過(guò)預(yù)先設(shè)定的執(zhí)行時(shí)間,則停止執(zhí)行非IO任務(wù),避免非IO任務(wù)太多,影響IO任務(wù)的執(zhí)行。每個(gè)NioEventLoop對(duì)應(yīng)一個(gè)線程和一個(gè)Selector,NioServerSocketChannel會(huì)主動(dòng)注冊(cè)到某一個(gè)NioEventLoop的Selector上,NioEventLoop負(fù)責(zé)**輪詢。Outbound **都是請(qǐng)求**, 發(fā)起者是 Channel,處理者是 unsafe,通過(guò) Outbound **進(jìn)行通知,傳播方向是 tail到head。Inbound **發(fā)起者是 unsafe,**的處理者是 Channel, 是通知**,傳播方向是從頭到尾。內(nèi)存管理機(jī)制,第一會(huì)預(yù)申請(qǐng)一大塊內(nèi)存Arena,Arena由許多Chunk組成,而每個(gè)Chunk默認(rèn)由2048個(gè)page組成。Chunk通過(guò)AVL樹(shù)的形式組織Page,每個(gè)葉子節(jié)點(diǎn)表示一個(gè)Page,而中間節(jié)點(diǎn)表示內(nèi)存區(qū)域,節(jié)點(diǎn)自己記錄它在整個(gè)Arena中的偏移地址。當(dāng)區(qū)域被分配出去后,中間節(jié)點(diǎn)上的標(biāo)記位會(huì)被標(biāo)記,這樣就表示這個(gè)中間節(jié)點(diǎn)以下的所有節(jié)點(diǎn)都已被分配了。大于8k的內(nèi)存分配在poolChunkList中,而PoolSubpage用于分配小于8k的內(nèi)存,它會(huì)把一個(gè)page分割成多段,進(jìn)行內(nèi)存分配。
ByteBuf的特點(diǎn):支持自動(dòng)擴(kuò)容(4M),保證put方法不會(huì)拋出異常、通過(guò)內(nèi)置的復(fù)合緩沖類型,實(shí)現(xiàn)零拷貝(zero-copy);不需要調(diào)用flip()來(lái)切換讀/寫(xiě)模式,讀取和寫(xiě)入索引分開(kāi);方法鏈;引用計(jì)數(shù)基于AtomicIntegerFieldUpdater用于內(nèi)存回收;PooledByteBuf采用二叉樹(shù)來(lái)實(shí)現(xiàn)一個(gè)內(nèi)存池,集中管理內(nèi)存的分配和釋放,不用每次使用都新建一個(gè)緩沖區(qū)對(duì)象。UnpooledHeapByteBuf每次都會(huì)新建一個(gè)緩沖區(qū)對(duì)象。
本文首次分布在https://blog.51cto.com/9928699/2354140
拓展知識(shí):
原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請(qǐng)注明出處:http:///32397.html