前言
在了解一個事物之前,最好能對它的基本屬性和相關(guān)概念有個基本的認知偷卧,所以學(xué)習(xí)Netty之前,也有必要了解與Netty相關(guān)的基礎(chǔ)概念知識;本篇將對Netty做一個基礎(chǔ)性的介紹侠鳄,主要包括Netty的適用場景,特色以及基礎(chǔ)的IO知識死宣,如果你已經(jīng)了解這些知識伟恶,也可以跳過本篇,直接進入下一篇:Netty剖析 - 2. 實現(xiàn)
Netty是什么毅该?
首先我們來看Netty是什么博秫,關(guān)于這個問題,其官網(wǎng)有一段闡述:
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients
這段翻譯過來意思就是:
Netty是一個基于NIO的異步網(wǎng)絡(luò)編程框架眶掌,基于Netty能快速的搭建高性能易擴展的網(wǎng)絡(luò)應(yīng)用(包括客戶端與服務(wù)端)
具體來說Netty就是對于Java NIO的封裝挡育,NIO又是什么呢?NIO是Java 1.4后引入的基于事件模型的非阻塞IO框架朴爬,在NIO之前即寒,對于數(shù)據(jù)的處理都是基于BIO(blocking IO),從名字上就知道BIO是以阻塞的形式對數(shù)據(jù)進行處理召噩,這種處理形式比較簡單母赵,但是既然阻塞的,那么就不可避免會涉及到線程的操作具滴,熟悉并發(fā)的小伙伴應(yīng)該都知道凹嘲,線程是一種昂貴的資源,無論是創(chuàng)建构韵,銷毀周蹭,還是切換趋艘,這就導(dǎo)致BIO在面對一些特定場景如高并發(fā)等束手無策,而這些場景在互聯(lián)網(wǎng)應(yīng)用中卻又很常見谷醉;對應(yīng)的致稀,NIO能較好的應(yīng)對這些場景,遺憾的是俱尼,Java在剛推出NIO時抖单,由于各種原因,致使其使用復(fù)雜遇八,且經(jīng)常會出現(xiàn)Bug矛绘,結(jié)果就是:廣大開發(fā)者有需求,但解決需求的工具就是不好用這樣尷尬的局面刃永,怎么辦呢货矮? -- 自己動手,豐衣食足斯够!大不了再造個"輪子"囚玫,所以就出現(xiàn)了一系列解決NIO問題的框架,而Netty就是其中最著名的那一個(當(dāng)然Java發(fā)展到現(xiàn)在读规,其NIO庫原本的很多問題都得到了解決抓督,不過很多解決方案借鑒的也是Netty的思想)
另外,Netty并不止于解決NIO的問題束亏,它更進一步铃在,還提供了一系列特色功能
Netty的特色
自己的孩子自己最了解,下面試Netty官網(wǎng)對于Netty特色的說明:
It greatly simplifies and streamlines network programming such as TCP and UDP socket server
'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise
這段話大概的意思就是:
首先碍遍,Netty能極大的簡化你的網(wǎng)絡(luò)編程定铜;并且,簡單好用還不需要以復(fù)雜的管理和低效的性能為代價怕敬,Netty通過優(yōu)秀的設(shè)計揣炕,在易部署,高性能赖捌,穩(wěn)定性祝沸,擴展性之間找到了一個較好的平衡點
我們把這句話提煉出來,大概就可以得到Netty的幾大特色:
- 針對基本的需求提供了簡單易用的接口越庇,直接上手!
- 針對復(fù)雜的場景提供了很強的擴展性奉狈,輕松應(yīng)對業(yè)務(wù)發(fā)展卤唉!
- 在上面兩點的基礎(chǔ)上,性能不打折仁期!
而如果對這些特點進行細化桑驱,則可以得出:
- 基于事件機制(Pipeline - Handler)達成關(guān)注點分離(消息編解碼竭恬,協(xié)議編解碼,業(yè)務(wù)處理)
- 可定制的線程處理模型熬的,單線程痊硕,多線程池等
- 屏蔽NIO本身的bug
- 性能上的優(yōu)化
- 相較于NIO接口功能更豐富
- 對外提供統(tǒng)一的接口,底層支持BIO與NIO兩種方式自由切換
這些特性將在本系列第二篇里做詳細分析押框;既然Netty的本質(zhì)還是一個基于NIO的網(wǎng)絡(luò)框架岔绸,那么想要掌握Netty的精髓,對于NIO的了解就不可或缺
NIO處理模型介紹
在介紹NIO之前橡伞,最好了解一下BIO盒揉,還沒有學(xué)習(xí)過的小伙伴可以閱讀我另外一篇介紹BIO的文章:Java IO使用入門 -- IO其實很簡單
NIO是Java 1.4引入的一種同步非阻塞的I/O模型,也是I/O多路復(fù)用的基礎(chǔ)兑徘;相對于Java BIO(OIO)提供的基于面向流的阻塞式編程模型刚盈,NIO提供的是面向緩沖區(qū)的響應(yīng)式事件編程模型
讀到這里可能有些人會覺得迷糊,什么阻塞挂脑?非阻塞藕漱?基于流?基于緩沖區(qū)崭闲?這里有必要介紹一下Linux下的5中IO模型:
- 阻塞I/O模型:
最常用的I/O模型就是阻塞I/O模型肋联,當(dāng)用戶進程調(diào)用了recvfrom這個系統(tǒng)調(diào)用,kernel就開始了IO的第一個階段:準備數(shù)據(jù)(對于網(wǎng)絡(luò)IO來說镀脂,很多時候數(shù)據(jù)在一開始還沒有到達牺蹄。比如,還沒有收到一個完整的UDP包薄翅。這個時候kernel就要等待足夠的數(shù)據(jù)到來)沙兰。這個過程需要等待,也就是說數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個過程的翘魄。而在用戶進程這邊鼎天,整個進程會被阻塞。當(dāng)kernel一直等到數(shù)據(jù)準備好了暑竟,它就會將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存斋射,然后kernel返回結(jié)果,用戶進程才解除block的狀態(tài)但荤,重新運行起來罗岖。所以,blocking IO的特點就是在IO執(zhí)行的兩個階段都被block了
image.png
-
非阻塞IO模型:
linux下腹躁,可以通過設(shè)置socket使其變?yōu)閚on-blocking桑包。當(dāng)對一個non-blocking socket執(zhí)行讀操作時,流程是這個樣子:
image.png
當(dāng)用戶進程發(fā)出read操作時纺非,如果kernel中的數(shù)據(jù)還沒有準備好哑了,那么它并不會block用戶進程赘方,而是立刻返回一個error。從用戶進程角度講 弱左,它發(fā)起一個read操作后窄陡,并不需要等待,而是馬上就得到了一個結(jié)果拆火。用戶進程判斷結(jié)果是一個error時跳夭,它就知道數(shù)據(jù)還沒有準備好,于是它可以再次發(fā)送read操作榜掌。一旦kernel中的數(shù)據(jù)準備好了优妙,并且又再次收到了用戶進程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存憎账,然后返回套硼;所以,nonblocking IO的特點是用戶進程需要不斷的主動詢問kernel數(shù)據(jù)好了沒有胞皱。 -
IO復(fù)用模型:
IO multiplexing就是我們說的select邪意,poll,epoll反砌,有些地方也稱這種IO方式為event driven IO雾鬼。select/epoll的好處就在于單個process就可以同時處理多個網(wǎng)絡(luò)連接的IO。它的基本原理就是select宴树,poll策菜,epoll這個function會不斷的輪詢所負責(zé)的所有socket,當(dāng)某個socket有數(shù)據(jù)到達了酒贬,就通知用戶進程
image.png
當(dāng)用戶進程調(diào)用了select又憨,那么整個進程會被block,而同時锭吨,kernel會監(jiān)視所有select負責(zé)的socket蠢莺,當(dāng)任何一個socket中的數(shù)據(jù)準備好了,select就會返回零如。這個時候用戶進程再調(diào)用read操作躏将,內(nèi)核負責(zé)將數(shù)據(jù)從kernel拷貝到用戶進程;所以考蕾,I/O 多路復(fù)用的特點是通過一種機制使得一個進程能同時等待多個文件描述符祸憋,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態(tài),select()函數(shù)就可以返回肖卧,所以說它最大的優(yōu)勢是系統(tǒng)開銷小夺衍,系統(tǒng)不需要創(chuàng)建或維護新的進程/線程。另外喜命,從上面比較IO復(fù)用流程圖和阻塞IO的圖可以發(fā)現(xiàn)沟沙,多路復(fù)用本身也是阻塞的,事實上壁榕,其效率可能還更差一些矛紫。因為這里需要使用兩個system call (select 和 recvfrom),而阻塞IO只調(diào)用了一個system call (recvfrom)牌里。但是颊咬,用select的優(yōu)勢在于它可以同時處理多個connection。所以牡辽,如果處理的連接數(shù)不是很高的話喳篇,使用select/epoll的web server不一定比使用阻塞IO的web server性能更好,可能延遲還更大态辛。select/epoll的優(yōu)勢并不是對于單個連接能處理得更快麸澜,而是在于能處理更多的連接。)在IO復(fù)用模型中奏黑,對于每一個socket炊邦,一般都設(shè)置成為non-blocking,但是熟史,如上圖所示馁害,整個用戶的process其實是一直被block的。只不過process是被select這個函數(shù)block蹂匹,而不是被socket IO給block 信號驅(qū)動IO模型:
首先開啟套接口信號驅(qū)動I/O功能,并通過系統(tǒng)調(diào)用sigaction執(zhí)行一個信號處理函數(shù)(此系統(tǒng)調(diào)用立即返回碘菜,進程繼續(xù)工作,它是非阻塞的)限寞。當(dāng)數(shù)據(jù)準備就緒時忍啸,就為該進程生成一個SIGIO信號。隨即可以在信號處理程序中調(diào)用recvfrom來讀數(shù)據(jù)昆烁,井通知主循環(huán)函數(shù)處理數(shù)據(jù)吊骤;一般用的較少-
異步IO:
在異步IO模型下,用戶進程發(fā)起read操作之后静尼,立刻就可以開始去做其它的事白粉。而另一方面,從kernel的角度鼠渺,當(dāng)它收到一個asynchronous read之后鸭巴,首先它會立刻返回,所以不會對用戶進程產(chǎn)生任何block拦盹。然后鹃祖,kernel會等待數(shù)據(jù)準備完成,然后依然由它將數(shù)據(jù)拷貝到用戶內(nèi)存普舆,當(dāng)這一切都完成之后恬口,kernel會給用戶進程發(fā)送一個signal校读,告訴它read操作完成了
image.png
介紹完這5種IO模型后,我們回到NIO祖能,NIO基于的是IO復(fù)用模型(就是上面的第三種IO模型)歉秫,正如在介紹IO復(fù)用模型時已提到,而在linux下养铸,有三種針對該模型的實現(xiàn)雁芙,分別為:select,poll钞螟,epoll兔甘;select和poll的實現(xiàn)機制類似,主要區(qū)別在于描述fd集合的方式不同鳞滨,poll使用pollfd結(jié)構(gòu)而不是select的fd_set結(jié)構(gòu)洞焙;epoll是linux 2.6后才有的,它主要是對select和poll的缺陷做了一些改進太援,這兩種實現(xiàn)方式有幾個比較大的缺點:
1) 每次調(diào)用select闽晦,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大
2) 每次調(diào)用select都需要在內(nèi)核遍歷傳遞進來的所有fd提岔,這個開銷在fd很多時也很大
3) select支持的文件描述符數(shù)量太小了仙蛉,默認是1024(當(dāng)然可以手動改,但改大了不一定效果好碱蒙,以為前面的1,2兩點)
對于第一個缺點荠瘪,epoll在每次注冊新的事件到epoll句柄中時躏救,會把所有的fd拷貝進內(nèi)核吱瘩,而不是在epoll_wait的時候重復(fù)拷貝幔虏。這樣就保證了每個fd在整個過程中只會拷貝一次吝梅。
對于第二個缺點,epoll的解決思路是每當(dāng)一個fd準備就緒坛善,就調(diào)用對應(yīng)的回調(diào)函數(shù)將其加入一個就緒鏈表喘蟆,然后只需要遍歷這個就緒鏈表即可镇眷,不需要遍歷所有fd
對于第三個缺點季惯,epoll沒有這個限制吠各,它所支持的fd上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠大于1024,舉個例子,在1GB內(nèi)存的機器上大約是10萬左右勉抓,具體數(shù)目可以cat /proc/sys/fs/file-max察看贾漏,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大
另外順便提一下Windows下的異步IO實現(xiàn)機制:I/O Completion Ports,或簡稱IOCP藕筋,個人覺得它的設(shè)計比較好纵散,極大的減少線程切換對性能的影響,同時又能保證CPU保持在較高的利用率,有興趣可以閱讀一下這篇文章
總結(jié)
本篇主要介紹了Netty相關(guān)的基礎(chǔ)知識伍掀,核心在于各種IO模型掰茶,特別是異步IO模型,作用在于為本系列的第二篇Netty剖析 - 2. 實現(xiàn)做準備硕盹,如果需要對IO模型進行更深入的了解符匾,可以參考下面幾篇文章: