來源:cnblogs.com/crazymakercircle/p/10225159.html
前文我們提到了 Java I/O疤坝,順著這個坡诱贿,我們進入 I/O 的世界吧,本來想嘗試寫點 I/O的底層?xùn)|西算柳,但發(fā)現(xiàn)一篇好文已經(jīng)講非常通俗易懂了嗜愈,重新編輯了下,推薦大家张遭!
1邓萨、Java IO 讀寫原理
無論是 Socket 的讀寫還是文件的讀寫,在 Java 層面的應(yīng)用開發(fā)或者是 linux 系統(tǒng)底層開發(fā)菊卷,都屬于輸入 input 和輸出 output 的處理缔恳,簡稱為 IO 讀寫。在原理上和處理流程上洁闰,都是一致的歉甚。區(qū)別在于參數(shù)的不同。
用戶程序進行 IO 的讀寫扑眉,基本上會用到 read&write 兩大系統(tǒng)調(diào)用纸泄。可能不同操作系統(tǒng)腰素,名稱不完全一樣聘裁,但是功能是一樣的。
先強調(diào)一個基礎(chǔ)知識:
read 系統(tǒng)調(diào)用弓千,并不是把數(shù)據(jù)直接從物理設(shè)備衡便,讀數(shù)據(jù)到內(nèi)存。write 系統(tǒng)調(diào)用洋访,也不是直接把數(shù)據(jù)镣陕,寫入到物理設(shè)備。
read 系統(tǒng)調(diào)用捌显,是把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到進程緩沖區(qū)茁彭;而 write 系統(tǒng)調(diào)用,是把數(shù)據(jù)從進程緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū)扶歪。這個兩個系統(tǒng)調(diào)用理肺,都不負責(zé)數(shù)據(jù)在內(nèi)核緩沖區(qū)和磁盤之間的交換摄闸。底層的讀寫交換,是由操作系統(tǒng) kernel 內(nèi)核完成的妹萨。
1.1 內(nèi)核緩沖與進程緩沖區(qū)
緩沖區(qū)的目的年枕,是為了減少頻繁的系統(tǒng) IO 調(diào)用。大家都知道乎完,系統(tǒng)調(diào)用需要保存之前的進程數(shù)據(jù)和狀態(tài)等信息熏兄,而結(jié)束調(diào)用之后回來還需要恢復(fù)之前的信息,為了減少這種損耗時間树姨、也損耗性能的系統(tǒng)調(diào)用摩桶,于是出現(xiàn)了緩沖區(qū)。
有了緩沖區(qū)帽揪,操作系統(tǒng)使用 read 函數(shù)把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到進程緩沖區(qū)硝清,write 把數(shù)據(jù)從進程緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū)中。等待緩沖區(qū)達到一定數(shù)量的時候转晰,再進行 IO 的調(diào)用芦拿,提升性能。至于什么時候讀取和存儲則由內(nèi)核來決定查邢,用戶程序不需要關(guān)心蔗崎。
在 linux 系統(tǒng)中,系統(tǒng)內(nèi)核也有個緩沖區(qū)叫做內(nèi)核緩沖區(qū)扰藕。每個進程有自己獨立的緩沖區(qū)缓苛,叫做進程緩沖區(qū)。
所以实胸,用戶程序的 IO 讀寫程序他嫡,大多數(shù)情況下,并沒有進行實際的 IO 操作庐完,而是在讀寫自己的進程緩沖區(qū)钢属。
1.2 java IO讀寫的底層流程
用戶程序進行 IO 的讀寫,基本上會用到系統(tǒng)調(diào)用 read&write门躯,read 把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到進程緩沖區(qū)淆党,write 把數(shù)據(jù)從進程緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū),它們不等價于數(shù)據(jù)在內(nèi)核緩沖區(qū)和磁盤之間的交換讶凉。首先看看一個典型Java 服務(wù)端處理網(wǎng)絡(luò)請求的典型過程:
(1)客戶端請求染乌,Linux通過網(wǎng)卡,讀取客戶斷的請求數(shù)據(jù)懂讯,將數(shù)據(jù)讀取到內(nèi)核緩沖區(qū)
(2)獲取請求數(shù)據(jù)荷憋,服務(wù)器從內(nèi)核緩沖區(qū)讀取數(shù)據(jù)到 Java 進程緩沖區(qū)。
(3)服務(wù)器端業(yè)務(wù)處理褐望,Java 服務(wù)端在自己的用戶空間中勒庄,處理客戶端的請求串前。
(4)服務(wù)器端返回數(shù)據(jù),Java 服務(wù)端已構(gòu)建好的響應(yīng)实蔽,從用戶緩沖區(qū)寫入系統(tǒng)緩沖區(qū)荡碾。
(5)發(fā)送給客戶端,Linux 內(nèi)核通過網(wǎng)絡(luò) I/O 局装,將內(nèi)核緩沖區(qū)中的數(shù)據(jù)坛吁,寫入網(wǎng)卡,網(wǎng)卡通過底層的通訊協(xié)議铐尚,會將數(shù)據(jù)發(fā)送給目標客戶端拨脉。
2、 四種主要的 IO 模型
服務(wù)器端編程經(jīng)常需要構(gòu)造高性能的 IO 模型塑径,常見的 IO 模型有四種:
(1)同步阻塞 IO (Blocking IO)
首先女坑,解釋一下這里的阻塞與非阻塞:
阻塞 IO填具,指的是需要內(nèi)核 IO 操作徹底完成后统舀,才返回到用戶空間,執(zhí)行用戶的操作劳景。阻塞指的是用戶空間程序的執(zhí)行狀態(tài)誉简,用戶空間程序需等到 IO 操作徹底完成。傳統(tǒng)的 IO 模型都是同步阻塞 IO盟广。在 java 中闷串,默認創(chuàng)建的 socket 都是阻塞的。
其次筋量,解釋一下同步與異步:
同步 IO烹吵,是一種用戶空間與內(nèi)核空間的調(diào)用發(fā)起方式。同步 IO 是指用戶空間線程是主動發(fā)起 IO 請求的一方桨武,內(nèi)核空間是被動接受方肋拔。異步 IO 則反過來,是指內(nèi)核 kernel 是主動發(fā)起 IO 請求的一方呀酸,用戶線程是被動接受方凉蜂,用簡單的來說就是回調(diào)。
(2)同步非阻塞 IO (Non-blocking IO)
非阻塞 IO性誉,指的是用戶程序不需要等待內(nèi)核 IO 操作完成后窿吩,內(nèi)核立即返回給用戶一個狀態(tài)值,用戶空間無需等到內(nèi)核的 IO 操作徹底完成错览,可以立即返回用戶空間纫雁,執(zhí)行用戶的操作,處于非阻塞的狀態(tài)倾哺。
簡單的說:阻塞是指用戶空間(調(diào)用線程)一直在等待皱碘,而且別的事情什么都不做;非阻塞是指用戶空間(調(diào)用線程)拿到狀態(tài)就返回汁掠,IO 操作可以干就干夹纫,不可以干,就去干的事情因块。非阻塞 IO 要求 socket 被設(shè)置為 NONBLOCK。強調(diào)一下,這里所說的 NIO (同步非阻塞 IO )模型翘地,并非 Java 的 NIO(New IO)庫。
(3) IO 多路復(fù)用(IO Multiplexing)
即經(jīng)典的 Reactor 設(shè)計模式癌幕,有時也稱為異步阻塞 IO衙耕,Java 中的 Selector 和Linux中的 epoll 都是這種模型。
(4)異步 IO (Asynchronous IO)
異步 IO勺远,指的是用戶空間與內(nèi)核空間的調(diào)用方式反過來橙喘。用戶空間線程是變成被動接受的,內(nèi)核空間是主動調(diào)用者胶逢。這一點厅瞎,有點類似于 Java 中比較典型的模式是回調(diào)模式,用戶空間線程向內(nèi)核空間注冊各種 IO 事件的回調(diào)函數(shù)初坠,由內(nèi)核去主動調(diào)用和簸。
3、同步阻塞 IO
在 linux 中的 Java 進程中碟刺,默認情況下所有的 socket 都是 blocking IO锁保。在阻塞式 I/O 模型中,應(yīng)用程序在從 IO 系統(tǒng)調(diào)用開始半沽,一直到到系統(tǒng)調(diào)用返回爽柒,這段時間是阻塞的。返回成功后者填,應(yīng)用進程開始處理用戶空間的緩存數(shù)據(jù)浩村。舉個栗子,發(fā)起一個 blocking socket 的 read 讀操作系統(tǒng)調(diào)用幔托,流程大概是這樣:
(1)當(dāng)用戶線程調(diào)用了 read 系統(tǒng)調(diào)用穴亏,內(nèi)核( kernel )就開始了 IO 的第一個階段:準備數(shù)據(jù)。很多時候重挑,數(shù)據(jù)在一開始還沒有到達(比如嗓化,還沒有收到一個完整的 Socket 數(shù)據(jù)包),這個時候 kernel 就要等待足夠的數(shù)據(jù)到來谬哀。
(2)當(dāng) kernel 一直等到數(shù)據(jù)準備好了刺覆,它就會將數(shù)據(jù)從 kernel 內(nèi)核緩沖區(qū),拷貝到用戶緩沖區(qū)(用戶內(nèi)存)史煎,然后 kernel 返回結(jié)果谦屑。
(3)從開始 IO 讀的 read 系統(tǒng)調(diào)用開始驳糯,用戶線程就進入阻塞狀態(tài)。一直到kernel返回結(jié)果后氢橙,用戶線程才解除 block 的狀態(tài)酝枢,重新運行起來。
所以悍手,blocking IO 的特點就是在內(nèi)核進行 IO 執(zhí)行的兩個階段帘睦,用戶線程都被block了。
BIO 的優(yōu)點:
程序簡單坦康,在阻塞等待數(shù)據(jù)期間竣付,用戶線程掛起。用戶線程基本不會占用 CPU 資源滞欠。
BIO 的缺點:
一般情況下古胆,會為每個連接配套一條獨立的線程,或者說一條線程維護一個連接成功的 IO 流的讀寫筛璧。在并發(fā)量小的情況下逸绎,這個沒有什么問題。但是隧哮,當(dāng)在高并發(fā)的場景下桶良,需要大量的線程來維護大量的網(wǎng)絡(luò)連接,內(nèi)存沮翔、線程切換開銷會非常巨大。因此曲秉,基本上采蚀,BIO 模型在高并發(fā)場景下是不可用的。
4承二、同步非阻塞 NIO
在 linux 系統(tǒng)下榆鼠,可以通過設(shè)置 socket 使其變?yōu)?non-blocking。NIO 模型中應(yīng)用程序在一旦開始 IO 系統(tǒng)調(diào)用亥鸠,會出現(xiàn)以下兩種情況:
(1)在內(nèi)核緩沖區(qū)沒有數(shù)據(jù)的情況下妆够,系統(tǒng)調(diào)用會立即返回,返回一個調(diào)用失敗的信息负蚊。
(2)在內(nèi)核緩沖區(qū)有數(shù)據(jù)的情況下神妹,是阻塞的,直到數(shù)據(jù)從內(nèi)核緩沖復(fù)制到用戶進程緩沖家妆。復(fù)制完成后鸵荠,系統(tǒng)調(diào)用返回成功,應(yīng)用進程開始處理用戶空間的緩存數(shù)據(jù)伤极。
舉個栗子蛹找。發(fā)起一個 non-blocking socket 的 read 讀操作系統(tǒng)調(diào)用姨伤,流程是這個樣子:
(1)在內(nèi)核數(shù)據(jù)沒有準備好的階段,用戶線程發(fā)起 IO 請求時庸疾,立即返回乍楚。用戶線程需要不斷地發(fā)起 IO 系統(tǒng)調(diào)用。
(2)內(nèi)核數(shù)據(jù)到達后届慈,用戶線程發(fā)起系統(tǒng)調(diào)用炊豪,用戶線程阻塞。內(nèi)核開始復(fù)制數(shù)據(jù)拧篮。它就會將數(shù)據(jù)從 kernel 內(nèi)核緩沖區(qū)词渤,拷貝到用戶緩沖區(qū)(用戶內(nèi)存),然后 kernel 返回結(jié)果串绩。
(3)用戶線程才解除 block 的狀態(tài)缺虐,重新運行起來。經(jīng)過多次的嘗試礁凡,用戶線程終于真正讀取到數(shù)據(jù)高氮,繼續(xù)執(zhí)行。
所以 NIO 的特點是應(yīng)用程序的線程需要不斷的進行 I/O 系統(tǒng)調(diào)用顷牌,輪詢數(shù)據(jù)是否已經(jīng)準備好剪芍,如果沒有準備好,繼續(xù)輪詢窟蓝,直到完成系統(tǒng)調(diào)用為止罪裹。
NIO 的優(yōu)點:
每次發(fā)起的 IO 系統(tǒng)調(diào)用,在內(nèi)核的等待數(shù)據(jù)過程中可以立即返回运挫。用戶線程不會阻塞状共,實時性較好。
NIO的缺點:
需要不斷的重復(fù)發(fā)起 IO 系統(tǒng)調(diào)用谁帕,這種不斷的輪詢峡继,將會不斷地詢問內(nèi)核,這將占用大量的 CPU 時間匈挖,系統(tǒng)資源利用率較低碾牌。總之儡循,NIO 模型在高并發(fā)場景下舶吗,也是不可用的。
一般 Web 服務(wù)器不使用這種 IO 模型贮折。一般很少直接使用這種模型裤翩,而是在其他 IO 模型中使用非阻塞 IO 這一特性。Java 的實際開發(fā)中,也不會涉及這種 IO 模型踊赠。
再次說明呵扛,Java NIO(New IO) 不是 IO 模型中的 NIO 模型,而是另外的一種模型筐带,叫做 IO 多路復(fù)用模型(IO multiplexing)
5今穿、IO 多路復(fù)用模型
如何避免同步非阻塞 NIO 模型中輪詢等待的問題呢?這就是 IO 多路復(fù)用模型伦籍。
IO 多路復(fù)用模型蓝晒,就是通過一種新的系統(tǒng)調(diào)用,一個進程可以監(jiān)視多個文件描述符帖鸦,一旦某個描述符就緒(一般是內(nèi)核緩沖區(qū)可讀/可寫)芝薇,內(nèi)核 kernel 能夠通知程序進行相應(yīng)的 IO 系統(tǒng)調(diào)用。目前支持 IO 多路復(fù)用的系統(tǒng)調(diào)用作儿,有 select洛二,epoll 等等。
select 系統(tǒng)調(diào)用攻锰,是目前幾乎在所有的操作系統(tǒng)上都有支持晾嘶,具有良好跨平臺特性。epoll 是在 linux 2.6 內(nèi)核中提出的娶吞,是 select 系統(tǒng)調(diào)用的 linux 增強版本垒迂。
IO 多路復(fù)用模型的基本原理就是 select/epoll 系統(tǒng)調(diào)用,單個線程不斷的輪詢select/epoll 系統(tǒng)調(diào)用所負責(zé)的成百上千的 socket 連接妒蛇,當(dāng)某個或者某些 socket 網(wǎng)絡(luò)連接有數(shù)據(jù)到達了机断,就返回這些可以讀寫的連接。因此材部,好處也就顯而易見了——通過一次 select/epoll 系統(tǒng)調(diào)用毫缆,就查詢到到可以讀寫的一個甚至是成百上千的網(wǎng)絡(luò)連接。舉個栗子乐导。發(fā)起一個多路復(fù)用 IO 的的 read 讀操作系統(tǒng)調(diào)用,流程是這個樣子:
在這種模式中浸颓,首先不是進行 read 系統(tǒng)調(diào)動物臂,而是進行 select/epoll 系統(tǒng)調(diào)用。當(dāng)然产上,這里有一個前提棵磷,需要將目標網(wǎng)絡(luò)連接,提前注冊到 select/epoll 的可查詢 socket 列表中晋涣。然后仪媒,才可以開啟整個的 IO 多路復(fù)用模型的讀流程。
(1)進行 select/epoll 系統(tǒng)調(diào)用谢鹊,查詢可以讀的連接算吩。Kernel 會查詢所有 select的可查詢 socket 列表留凭,當(dāng)任何一個 socket 中的數(shù)據(jù)準備好了,select 就會返回偎巢。當(dāng)用戶進程調(diào)用了select蔼夜,那么整個線程會被 block(阻塞掉)。
(2)用戶線程獲得了目標連接后压昼,發(fā)起 read 系統(tǒng)調(diào)用求冷,用戶線程阻塞。內(nèi)核開始復(fù)制數(shù)據(jù)窍霞。它就會將數(shù)據(jù)從 kernel 內(nèi)核緩沖區(qū)匠题,拷貝到用戶緩沖區(qū)(用戶內(nèi)存),然后 kernel 返回結(jié)果但金。
(3)用戶線程才解除 block 的狀態(tài)韭山,用戶線程終于真正讀取到數(shù)據(jù),繼續(xù)執(zhí)行傲绣。
所以多路復(fù)用 IO 的特點掠哥,IO 多路復(fù)用模型,建立在操作系統(tǒng) kernel 內(nèi)核能夠提供的多路分離系統(tǒng)調(diào)用 select/epoll 基礎(chǔ)之上的秃诵。多路復(fù)用 IO 需要用到兩個系統(tǒng)調(diào)用(system call)续搀, 一個 select/epoll 查詢調(diào)用,一個是 IO 的讀取調(diào)用菠净。
和 NIO 模型相似禁舷,多路復(fù)用 IO 需要輪詢。負責(zé) select/epoll 查詢調(diào)用的線程毅往,需要不斷的進行 select/epoll 輪詢牵咙,查找出可以進行 IO 操作的連接。另外攀唯,多路復(fù)用 IO 模型與前面的 NIO 模型洁桌,是有關(guān)系的。對于每一個可以查詢的socket侯嘀,一般都設(shè)置成為 non-blocking 模型另凌。只是這一點,對于用戶程序是透明的戒幔。
多路復(fù)用 IO 的優(yōu)點:
用 select/epoll 的優(yōu)勢在于吠谢,它可以同時處理成千上萬個連接。與一條線程維護一個連接相比诗茎, I/O 多路復(fù)用技術(shù)的最大優(yōu)勢是:**系統(tǒng)不必創(chuàng)建線程工坊,也不必維護這些線程,從而大大減小了系統(tǒng)的開銷。
**Java 的 NIO (new IO)技術(shù)王污,使用的就是 IO 多路復(fù)用模型罢吃。在 linux 系統(tǒng)上,使用的是 epoll 系統(tǒng)調(diào)用玉掸。
多路復(fù)用 IO 的缺點:
本質(zhì)上刃麸,select/epoll 系統(tǒng)調(diào)用,屬于同步 IO司浪,也是阻塞 IO泊业。都需要在讀寫事件就緒后,自己負責(zé)進行讀寫啊易,也就是說這個讀寫過程是阻塞的吁伺。如何充分的解除線程的阻塞呢?那就是異步 IO 模型租谈。
6篮奄、異步IO模型
如何進一步提升效率,解除最后一點阻塞呢割去?這就是異步 IO 模型窟却,全稱asynchronous I/O,簡稱為 AIO呻逆。
AIO 的基本流程是:用戶線程通過系統(tǒng)調(diào)用夸赫,告知 kernel 內(nèi)核啟動某個 IO 操作,用戶線程返回咖城。Kernel 內(nèi)核在整個 IO 操作(包括數(shù)據(jù)準備茬腿、數(shù)據(jù)復(fù)制)完成后,通知用戶程序宜雀,用戶執(zhí)行后續(xù)的業(yè)務(wù)操作切平。
kernel 的數(shù)據(jù)準備是將數(shù)據(jù)從網(wǎng)絡(luò)物理設(shè)備(網(wǎng)卡)讀取到內(nèi)核緩沖區(qū);kernel 的數(shù)據(jù)復(fù)制是將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶程序空間的緩沖區(qū)辐董。
(1)當(dāng)用戶線程調(diào)用了 read 系統(tǒng)調(diào)用悴品,立刻就可以開始去做其它的事,用戶線程不阻塞简烘。
(2)內(nèi)核(kernel)就開始了 IO 的第一個階段:準備數(shù)據(jù)他匪。當(dāng) kernel 一直等到數(shù)據(jù)準備好了,它就會將數(shù)據(jù)從 kernel 內(nèi)核緩沖區(qū)夸研,拷貝到用戶緩沖區(qū)(用戶內(nèi)存)。
(3)kernel 會給用戶線程發(fā)送一個信號(signal)依鸥,或者回調(diào)用戶線程注冊的回調(diào)接口亥至,告訴用戶線程 read 操作完成了。
(4)用戶線程讀取用戶緩沖區(qū)的數(shù)據(jù),完成后續(xù)的業(yè)務(wù)操作姐扮。
異步 IO 模型的特點:
在內(nèi)核 kernel 的等待數(shù)據(jù)和復(fù)制數(shù)據(jù)的兩個階段絮供,用戶線程都不是 block (阻塞)的。用戶線程需要接受 kernel 的 IO 操作完成的事件茶敏,或者說注冊 IO 操作完成的回調(diào)函數(shù)壤靶,到操作系統(tǒng)的內(nèi)核。所以說惊搏,異步 IO 有的時候贮乳,也叫做信號驅(qū)動 IO 。
異步IO模型缺點:
需要完成事件的注冊與傳遞恬惯,這里邊需要底層操作系統(tǒng)提供大量的支持向拆,去做大量的工作。目前來說酪耳, Windows 系統(tǒng)下通過 IOCP 實現(xiàn)了真正的異步 I/O浓恳。
但是,就目前的業(yè)界形式來說碗暗,Windows 系統(tǒng)颈将,很少作為百萬級以上或者說高并發(fā)應(yīng)用的服務(wù)器操作系統(tǒng)來使用。而在 Linux 系統(tǒng)下言疗,異步 IO 模型在 2.6 版本才引入晴圾,目前并不完善。所以洲守,這也是在 Linux 下疑务,實現(xiàn)高并發(fā)網(wǎng)絡(luò)編程時都是以 IO 復(fù)用模型模式為主。
7梗醇、小結(jié)一下
假設(shè)有這么一個場景知允,有一排水壺(客戶)在燒水:
同步阻塞 IO的做法是,叫一個線程停留在一個水壺那叙谨,直到這個水壺?zé)_温鸽,才去處理下一個水壺。
IO 多路復(fù)用或非阻塞 IO 的做法是手负,叫一個線程不停的循環(huán)觀察每一個水壺涤垫,根據(jù)每個水壺當(dāng)前的狀態(tài)去處理。
異步 IO 的做法是竟终,每個水壺上裝一個開關(guān)蝠猬,當(dāng)水開了以后會提醒對應(yīng)的線程去處理。
四種 IO 模型统捶,理論上越往后榆芦,阻塞越少柄粹,效率也是最優(yōu)。在這四種 I/O 模型中匆绣,前三種屬于同步 I/O驻右,因為其中真正的 I/O 操作將阻塞線程。只有最后一種崎淳,才是真正的異步 I/O 模型堪夭,可惜目前 Linux 操作系統(tǒng)尚欠完善。
挖坑序列文章
編碼字符集和字符集編碼傻傻分不清楚森爽!看完這篇文章你就懂了?