亞寵展、全球寵物產業風向標——亞洲寵物展覽會深度解析
1245
2022-05-28
文章目錄
其他文章
網絡為什么要分層?
發送數據包
零拷貝
為什么要有DMA技術
傳統的文件傳輸有多糟糕?
如何優化文件傳輸的性能?
如何實現零拷貝
mmap + write
sendfile
PageCache有什么作用?
大文件傳輸用什么方式實現?
其他文章
操作系統——概述
操作系統——內存管理
操作系統——進程和線程
操作系統——進程間通信
操作系統——文件系統
操作系統——設備管理
操作系統——網絡系統
一臺機器將自己想要表達的內容,按照某種約定好的格式發送出去,當另外一臺機器收到這些信息后,也能夠按照約定好的格式解析出來,從而準確、可靠地獲得發送方想要表達的內容。這種約定好的格式就是網絡協議(Networking Protocol)。
網絡為什么要分層?
我們這里先構建一個相對簡單的場景,之后幾節內容,我們都要基于這個場景進行講解。
我們假設這里就涉及三臺機器。Linux 服務器 A 和 Linux 服務器 B 處于不同的網段,通過中間的 Linux 服務器作為路由器進行轉發。
說到網絡協議,我們還需要簡要介紹一下兩種網絡協議模型,一種是OSI 的標準七層模型,一種是業界標準的 TCP/IP 模型。它們的對應關系如下圖所示:
為什么網絡要分層呢?因為網絡環境過于復雜,不是一個能夠集中控制的體系。全球數以億記的服務器和設備各有各的體系,但是都可以通過同一套網絡協議棧通過切分成多個層次和組合,來滿足不同服務器和設備的通信需求。
我們這里簡單介紹一下網絡協議的幾個層次。
我們從哪一個層次開始呢?從第三層,網絡層開始,因為這一層有我們熟悉的 IP 地址。也因此,這一層我們也叫 IP 層。
我們通常看到的 IP 地址都是這個樣子的:192.168.1.100/24。斜杠前面是 IP 地址,這個地址被點分隔為四個部分,每個部分 8 位,總共是 32 位。斜線后面 24 的意思是,32 位中,前 24 位是網絡號,后 8 位是主機號。
為什么要這樣分呢?我們可以想象,雖然全世界組成一張大的互聯網,美國的網站你也能夠訪問的,但是這個網絡不是一整個的。你們小區有一個網絡,你們公司也有一個網絡,聯通、移動、電信運營商也各有各的網絡,所以一個大網絡是被分成個小的網絡。
那如何區分這些網絡呢?這就是網絡號的概念。一個網絡里面會有多個設備,這些設備的網絡號一樣,主機號不一樣。不信你可以觀察一下你家里的手機、電視、電腦。
連接到網絡上的每一個設備都至少有一個 IP 地址,用于定位這個設備。無論是近在咫尺的你旁邊同學的電腦,還是遠在天邊的電商網站,都可以通過 IP 地址進行定位。因此,IP 地址類似互聯網上的郵寄地址,是有全局定位功能的。
就算你要訪問美國的一個地址,也可以從你身邊的網絡出發,通過不斷的打聽道兒,經過多個網絡,最終到達目的地址,和快遞員送包裹的過程差不多。打聽道兒的協議也在第三層,稱為路由協議(Routing protocol),將網絡包從一個網絡轉發給另一個網絡的設備稱為路由器。
總而言之,第三層干的事情,就是網絡包從一個起始的 IP 地址,沿著路由協議指的道兒,經過多個網絡,通過多次路由器轉發,到達目標 IP 地址。
從第三層,我們往下看,第二層是數據鏈路層。有時候我們簡稱為二層或者 MAC 層。所謂 MAC,就是每個網卡都有的唯一的硬件地址(不絕對唯一,相對大概率唯一即可)。這雖然也是一個地址,但是這個地址是沒有全局定位功能的。
就像給你送外賣的小哥,不可能根據手機尾號找到你家,但是手機尾號有本地定位功能的,只不過這個定位主要靠“吼”。外賣小哥到了你的樓層就開始大喊:“尾號 xxxx 的,你外賣到了!”
MAC 地址的定位功能局限在一個網絡里面,也即同一個網絡號下的 IP 地址之間,可以通過 MAC 進行定位和通信。從 IP 地址獲取 MAC 地址要通過 ARP 協議,是通過在本地發送廣播包,也就是“吼”,獲得的 MAC 地址。
由于同一個網絡內的機器數量有限,通過 MAC 地址的好處就是簡單。匹配上 MAC 地址就接收,匹配不上就不接收,沒有什么所謂路由協議這樣復雜的協議。當然壞處就是,MAC 地址的作用范圍不能出本地網絡,所以一旦跨網絡通信,雖然 IP 地址保持不變,但是 MAC 地址每經過一個路由器就要換一次。
我們看前面的圖。服務器 A 發送網絡包給服務器 B,原 IP 地址始終是 192.168.1.100,目標 IP 地址始終是 192.168.2.100,但是在網絡 1 里面,原 MAC 地址是 MAC1,目標 MAC 地址是路由器的 MAC2,路由器轉發之后,原 MAC 地址是路由器的 MAC3,目標 MAC 地址是 MAC4。
所以第二層干的事情,就是網絡包在本地網絡中的服務器之間定位及通信的機制。
我們再往下看,第一層,物理層,這一層就是物理設備。例如連著電腦的網線,我們能連上的 WiFi
從第三層往上看,第四層是傳輸層,這里面有兩個著名的協議 TCP 和 UDP。尤其是 TCP,更是廣泛使用,在 IP 層的代碼邏輯中,僅僅負責數據從一個 IP 地址發送給另一個 IP 地址,丟包、亂序、重傳、擁塞,這些 IP 層都不管。處理這些問題的代碼邏輯寫在了傳輸層的 TCP 協議里面。
我們常稱,TCP 是可靠傳輸協議,也是難為它了。因為從第一層到第三層都不可靠,網絡包說丟就丟,是 TCP 這一層通過各種編號、重傳等機制,讓本來不可靠的網絡對于更上層來講,變得“看起來”可靠。哪有什么應用層歲月靜好,只不過 TCP 層幫你負重前行。
傳輸層再往上就是應用層,例如咱們在瀏覽器里面輸入的 HTTP,Java 服務端寫的 Servlet,都是這一層的。
二層到四層都是在 Linux 內核里面處理的,應用層例如瀏覽器、Nginx、Tomcat 都是用戶態的。內核里面對于網絡包的處理是不區分應用的。
從四層再往上,就需要區分網絡包發給哪個應用。在傳輸層的 TCP 和 UDP 協議里面,都有端口的概念,不同的應用監聽不同的端口。例如,服務端 Nginx 監聽 80、Tomcat 監聽 8080;再如客戶端瀏覽器監聽一個隨機端口,FTP 客戶端監聽另外一個隨機端口。
應用層和內核互通的機制,就是通過 Socket 系統調用。所以經常有人會問,Socket 屬于哪一層,其實它哪一層都不屬于,它屬于操作系統的概念,而非網絡協議分層的概念。只不過操作系統選擇對于網絡協議的實現模式是,二到四層的處理代碼在內核里面,七層的處理代碼讓應用自己去做,兩者需要跨內核態和用戶態通信,就需要一個系統調用完成這個銜接,這就是 Socket。
發送數據包
網絡分完層之后,對于數據包的發送,就是層層封裝的過程。
就像下面的圖中展示的一樣,在 Linux 服務器 B 上部署的服務端 Nginx 和 Tomcat,都是通過 Socket 監聽 80 和 8080 端口。這個時候,內核的數據結構就知道了。如果遇到發送到這兩個端口的,就發送給這兩個進程。
在 Linux 服務器 A 上的客戶端,打開一個 Firefox 連接 Ngnix。也是通過 Socket,客戶端會被分配一個隨機端口 12345。同理,打開一個 Chrome 連接 Tomcat,同樣通過 Socket 分配隨機端口 12346。
在客戶端瀏覽器,我們將請求封裝為 HTTP 協議,通過 Socket 發送到內核。內核的網絡協議棧里面,在 TCP 層創建用于維護連接、序列號、重傳、擁塞控制的數據結構,將 HTTP 包加上 TCP 頭,發送給 IP 層,IP 層加上 IP 頭,發送給 MAC 層,MAC 層加上 MAC 頭,從硬件網卡發出去。
網絡包會先到達網絡 1 的交換機。我們常稱交換機為二層設備,這是因為,交換機只會處理到第二層,然后它會將網絡包的 MAC 頭拿下來,發現目標 MAC 是在自己右面的網口,于是就從這個網口發出去。
網絡包會到達中間的 Linux 路由器,它左面的網卡會收到網絡包,發現 MAC 地址匹配,就交給 IP 層,在 IP 層根據 IP 頭中的信息,在路由表中查找。下一跳在哪里,應該從哪個網口發出去?在這個例子中,最終會從右面的網口發出去。我們常把路由器稱為三層設備,因為它只會處理到第三層。
從路由器右面的網口發出去的包會到網絡 2 的交換機,還是會經歷一次二層的處理,轉發到交換機右面的網口。
最終網絡包會被轉發到 Linux 服務器 B,它發現 MAC 地址匹配,就將 MAC 頭取下來,交給上一層。IP 層發現 IP 地址匹配,將 IP 頭取下來,交給上一層。TCP 層會根據 TCP 頭中的序列號等信息,發現它是一個正確的網絡包,就會將網絡包緩存起來,等待應用層的讀取。
應用層通過 Socket 監聽某個端口,因而讀取的時候,內核會根據 TCP 頭中的端口號,將網絡包發給相應的應用。
HTTP 層的頭和正文,是應用層來解析的。通過解析,應用層知道了客戶端的請求,例如購買一個商品,還是請求一個網頁。當應用層處理完 HTTP 的請求,會將結果仍然封裝為 HTTP 的網絡包,通過 Socket 接口,發送給內核。
內核會經過層層封裝,從物理網口發送出去,經過網絡 2 的交換機,Linux 路由器到達網絡 1,經過網絡 1 的交換機,到達 Linux 服務器 A。在 Linux 服務器 A 上,經過層層解封裝,通過 socket 接口,根據客戶端的隨機端口號,發送給客戶端的應用程序,瀏覽器。于是瀏覽器就能夠顯示出一個絢麗多彩的頁面了。
即便在如此簡單的一個環境中,網絡包的發送過程,竟然如此的復雜。
零拷貝
為什么要有DMA技術
在沒有 DMA 技術前,I/O 的過程是這樣的:
CPU 發出對應的指令給磁盤控制器,然后返回;
磁盤控制器收到指令后,于是就開始準備數據,會把數據放入到磁盤控制器的內部緩沖區中,然后產生一個中斷;
CPU 收到中斷信號后,停下手頭的工作,接著把磁盤控制器的緩沖區的數據一次一個字節地讀進自己的寄存器,然后再把寄存器里的數據寫入到內存,而在數據傳輸的期間 CPU 是無法執行其他任務的。
為了方便你理解,我畫了一副圖:
可以看到,整個數據的傳輸過程,都要需要 CPU 親自參與搬運數據的過程,而且這個過程,CPU 是不能做其他事情的。
簡單的搬運幾個字符數據那沒問題,但是如果我們用千兆網卡或者硬盤傳輸大量數據的時候,都用 CPU 來搬運的話,肯定忙不過來。
計算機科學家們發現了事情的嚴重性后,于是就發明了 DMA 技術,也就是直接內存訪問(Direct Memory Access) 技術。
什么是 DMA 技術?簡單理解就是,在進行 I/O 設備和內存的數據傳輸的時候,數據搬運的工作全部交給 DMA 控制器,而 CPU 不再參與任何與數據搬運相關的事情,這樣 CPU 就可以去處理別的事務。
那使用 DMA 控制器進行數據傳輸的過程究竟是什么樣的呢?下面我們來具體看看。
具體過程:
用戶進程調用 read 方法,向操作系統發出 I/O 請求,請求讀取數據到自己的內存緩沖區中,進程進入阻塞狀態;
操作系統收到請求后,進一步將 I/O 請求發送 DMA,然后讓 CPU 執行其他任務;
DMA 進一步將 I/O 請求發送給磁盤;
磁盤收到 DMA 的 I/O 請求,把數據從磁盤讀取到磁盤控制器的緩沖區中,當磁盤控制器的緩沖區被讀滿后,向 DMA 發起中斷信號,告知自己緩沖區已滿;
DMA 收到磁盤的信號,將磁盤控制器緩沖區中的數據拷貝到內核緩沖區中,此時不占用 CPU,CPU 可以執行其他任務;
當 DMA 讀取了足夠多的數據,就會發送中斷信號給 CPU;
CPU 收到 DMA 的信號,知道數據已經準備好,于是將數據從內核拷貝到用戶空間,系統調用返回;
可以看到, 整個數據傳輸的過程,CPU 不再參與數據搬運的工作,而是全程由 DMA 完成,但是 CPU 在這個過程中也是必不可少的,因為傳輸什么數據,從哪里傳輸到哪里,都需要 CPU 來告訴 DMA 控制器。
早期 DMA 只存在在主板上,如今由于 I/O 設備越來越多,數據傳輸的需求也不盡相同,所以每個 I/O 設備里面都有自己的 DMA 控制器。
傳統的文件傳輸有多糟糕?
如果服務端要提供文件傳輸的功能,我們能想到的最簡單的方式是:將磁盤上的文件讀取出來,然后通過網絡協議發送給客戶端。
傳統 I/O 的工作方式是,數據讀取和寫入是從用戶空間到內核空間來回復制,而內核空間的數據是通過操作系統層面的 I/O 接口從磁盤讀取或寫入。
代碼通常如下,一般會需要兩個系統調用:
read(file, tmp_buf, len); write(socket, tmp_buf, len);
1
2
代碼很簡單,雖然就兩行代碼,但是這里面發生了不少的事情。
首先,期間共發生了 4 次用戶態與內核態的上下文切換,因為發生了兩次系統調用,一次是 read() ,一次是 write(),每次系統調用都得先從用戶態切換到內核態,等內核完成任務后,再從內核態切換回用戶態。
上下文切換到成本并不小,一次切換需要耗時幾十納秒到幾微秒,雖然時間看上去很短,但是在高并發的場景下,這類時間容易被累積和放大,從而影響系統的性能。
其次,還發生了 4 次數據拷貝,其中兩次是 DMA 的拷貝,另外兩次則是通過 CPU 拷貝的,下面說一下這個過程:
第一次拷貝,把磁盤上的數據拷貝到操作系統內核的緩沖區里,這個拷貝的過程是通過 DMA 搬運的。
第二次拷貝,把內核緩沖區的數據拷貝到用戶的緩沖區里,于是我們應用程序就可以使用這部分數據了,這個拷貝到過程是由 CPU 完成的。
第三次拷貝,把剛才拷貝到用戶的緩沖區里的數據,再拷貝到內核的 socket 的緩沖區里,這個過程依然還是由 CPU 搬運的。
第四次拷貝,把內核的 socket 緩沖區里的數據,拷貝到網卡的緩沖區里,這個過程又是由 DMA 搬運的。
我們回過頭看這個文件傳輸的過程,我們只是搬運一份數據,結果卻搬運了 4 次,過多的數據拷貝無疑會消耗 CPU 資源,大大降低了系統性能。
這種簡單又傳統的文件傳輸方式,存在冗余的上文切換和數據拷貝,在高并發系統里是非常糟糕的,多了很多不必要的開銷,會嚴重影響系統性能。
所以,要想提高文件傳輸的性能,就需要減少「用戶態與內核態的上下文切換」和「內存拷貝」的次數。
如何優化文件傳輸的性能?
先來看看,如何減少「用戶態與內核態的上下文切換」的次數呢?
讀取磁盤數據的時候,之所以要發生上下文切換,這是因為用戶空間沒有權限操作磁盤或網卡,內核的權限最高,這些操作設備的過程都需要交由操作系統內核來完成,所以一般要通過內核去完成某些任務的時候,就需要使用操作系統提供的系統調用函數。
而一次系統調用必然會發生 2 次上下文切換:首先從用戶態切換到內核態,當內核執行完任務后,再切換回用戶態交由進程代碼執行。
所以,要想減少上下文切換到次數,就要減少系統調用的次數。
再來看看,如何減少「數據拷貝」的次數?
在前面我們知道了,傳統的文件傳輸方式會歷經 4 次數據拷貝,而且這里面,「從內核的讀緩沖區拷貝到用戶的緩沖區里,再從用戶的緩沖區里拷貝到 socket 的緩沖區里」,這個過程是沒有必要的。
因為文件傳輸的應用場景中,在用戶空間我們并不會對數據「再加工」,所以數據實際上可以不用搬運到用戶空間,因此用戶的緩沖區是沒有必要存在的。
如何實現零拷貝
零拷貝技術實現的方式通常有 2 種:
mmap + write
sendfile
下面就談一談,它們是如何減少「上下文切換」和「數據拷貝」的次數。
在前面我們知道,read() 系統調用的過程中會把內核緩沖區的數據拷貝到用戶的緩沖區里,于是為了減少這一步開銷,我們可以用 mmap() 替換 read() 系統調用函數。
buf = mmap(file, len); write(sockfd, buf, len);
1
2
mmap() 系統調用函數會直接把內核緩沖區里的數據「映射」到用戶空間,這樣,操作系統內核與用戶空間就不需要再進行任何的數據拷貝操作。
具體過程如下:
應用進程調用了 mmap() 后,DMA 會把磁盤的數據拷貝到內核的緩沖區里。接著,應用進程跟操作系統內核「共享」這個緩沖區;
應用進程再調用 write(),操作系統直接將內核緩沖區的數據拷貝到 socket 緩沖區中,這一切都發生在內核態,由 CPU 來搬運數據;
最后,把內核的 socket 緩沖區里的數據,拷貝到網卡的緩沖區里,這個過程是由 DMA 搬運的。
我們可以得知,通過使用 mmap() 來代替 read(), 可以減少一次數據拷貝的過程。
但這還不是最理想的零拷貝,因為仍然需要通過 CPU 把內核緩沖區的數據拷貝到 socket 緩沖區里,而且仍然需要 4 次上下文切換,因為系統調用還是 2 次。
在 Linux 內核版本 2.1 中,提供了一個專門發送文件的系統調用函數 sendfile(),函數形式如下:
#include
1
2
它的前兩個參數分別是目的端和源端的文件描述符,后面兩個參數是源端的偏移量和復制數據的長度,返回值是實際復制數據的長度。
首先,它可以替代前面的 read() 和 write() 這兩個系統調用,這樣就可以減少一次系統調用,也就減少了 2 次上下文切換的開銷。
其次,該系統調用,可以直接把內核緩沖區里的數據拷貝到 socket 緩沖區里,不再拷貝到用戶態,這樣就只有 2 次上下文切換,和 3 次數據拷貝。如下圖:
但是這還不是真正的零拷貝技術,如果網卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術(和普通的 DMA 有所不同),我們可以進一步減少通過 CPU 把內核緩沖區里的數據拷貝到 socket 緩沖區的過程。
你可以在你的 Linux 系統通過下面這個命令,查看網卡是否支持 scatter-gather 特性:
$ ethtool -k eth0 | grep scatter-gather scatter-gather: on
1
2
于是,從 Linux 內核 2.4 版本開始起,對于支持網卡支持 SG-DMA 技術的情況下, sendfile() 系統調用的過程發生了點變化,具體過程如下:
第一步,通過 DMA 將磁盤上的數據拷貝到內核緩沖區里;
第二步,緩沖區描述符和數據長度傳到 socket 緩沖區,這樣網卡的 SG-DMA 控制器就可以直接將內核緩存中的數據拷貝到網卡的緩沖區里,此過程不需要將數據從操作系統內核緩沖區拷貝到 socket 緩沖區中,這樣就減少了一次數據拷貝;
所以,這個過程之中,只進行了 2 次數據拷貝,如下圖:
這就是所謂的零拷貝(Zero-copy)技術,因為我們沒有在內存層面去拷貝數據,也就是說全程沒有通過 CPU 來搬運數據,所有的數據都是通過 DMA 來進行傳輸的。
零拷貝技術的文件傳輸方式相比傳統文件傳輸的方式,減少了 2 次上下文切換和數據拷貝次數,只需要 2 次上下文切換和數據拷貝次數,就可以完成文件的傳輸,而且 2 次的數據拷貝過程,都不需要通過 CPU,2 次都是由 DMA 來搬運。
所以,總體來看,零拷貝技術可以把文件傳輸的性能提高至少一倍以上。
PageCache有什么作用?
回顧前面說道文件傳輸過程,其中第一步都是先需要先把磁盤文件數據拷貝「內核緩沖區」里,這個「內核緩沖區」實際上是磁盤高速緩存(PageCache)。
由于零拷貝使用了 PageCache 技術,可以使得零拷貝進一步提升了性能,我們接下來看看 PageCache 是如何做到這一點的。
讀寫磁盤相比讀寫內存的速度慢太多了,所以我們應該想辦法把「讀寫磁盤」替換成「讀寫內存」。于是,我們會通過 DMA 把磁盤里的數據搬運到內存里,這樣就可以用讀內存替換讀磁盤。
但是,內存空間遠比磁盤要小,內存注定只能拷貝磁盤里的一小部分數據。
那問題來了,選擇哪些磁盤數據拷貝到內存呢?
我們都知道程序運行的時候,具有「局部性」,所以通常,剛被訪問的數據在短時間內再次被訪問的概率很高,于是我們可以用 PageCache 來緩存最近被訪問的數據,當空間不足時淘汰最久未被訪問的緩存。
所以,讀磁盤數據的時候,優先在 PageCache 找,如果數據存在則可以直接返回;如果沒有,則從磁盤中讀取,然后緩存 PageCache 中。
還有一點,讀取磁盤數據的時候,需要找到數據所在的位置,但是對于機械磁盤來說,就是通過磁頭旋轉到數據所在的扇區,再開始「順序」讀取數據,但是旋轉磁頭這個物理動作是非常耗時的,為了降低它的影響,PageCache 使用了「預讀功能」。
比如,假設 read 方法每次只會讀 32 KB 的字節,雖然 read 剛開始只會讀 0 ~ 32 KB 的字節,但內核會把其后面的 32~64 KB 也讀取到 PageCache,這樣后面讀取 32~64 KB 的成本就很低,如果在 32~64 KB 淘汰出 PageCache 前,進程讀取到它了,收益就非常大。
所以,PageCache 的優點主要是兩個:
緩存最近被訪問的數據;
預讀功能;
這兩個做法,將大大提高讀寫磁盤的性能。
但是,在傳輸大文件(GB 級別的文件)的時候,PageCache 會不起作用,那就白白浪費 DMA 多做的一次數據拷貝,造成性能的降低,即使使用了 PageCache 的零拷貝也會損失性能
這是因為如果你有很多 GB 級別文件需要傳輸,每當用戶訪問這些大文件的時候,內核就會把它們載入 PageCache 中,于是 PageCache 空間很快被這些大文件占滿。
另外,由于文件太大,可能某些部分的文件數據被再次訪問的概率比較低,這樣就會帶來 2 個問題:
PageCache 由于長時間被大文件占據,其他「熱點」的小文件可能就無法充分使用到 PageCache,于是這樣磁盤讀寫的性能就會下降了;
PageCache 中的大文件數據,由于沒有享受到緩存帶來的好處,但卻耗費 DMA 多拷貝到 PageCache 一次;
所以,針對大文件的傳輸,不應該使用 PageCache,也就是說不應該使用零拷貝技術,因為可能由于 PageCache 被大文件占據,而導致「熱點」小文件無法利用到 PageCache,這樣在高并發的環境下,會帶來嚴重的性能問題。
大文件傳輸用什么方式實現?
那針對大文件的傳輸,我們應該使用什么方式呢?
我們先來看看最初的例子,當調用 read 方法讀取文件時,進程實際上會阻塞在 read 方法調用,因為要等待磁盤數據的返回,如下圖:
具體過程:
當調用 read 方法時,會阻塞著,此時內核會向磁盤發起 I/O 請求,磁盤收到請求后,便會尋址,當磁盤數據準備好后,就會向內核發起 I/O 中斷,告知內核磁盤數據已經準備好;
內核收到 I/O 中斷后,就將數據從磁盤控制器緩沖區拷貝到 PageCache 里;
最后,內核再把 PageCache 中的數據拷貝到用戶緩沖區,于是 read 調用就正常返回了。
對于阻塞的問題,可以用異步 I/O 來解決,它工作方式如下圖:
它把讀操作分為兩部分:
前半部分,內核向磁盤發起讀請求,但是可以不等待數據就位就可以返回,于是進程此時可以處理其他任務;
后半部分,當內核將磁盤中的數據拷貝到進程緩沖區后,進程將接收到內核的通知,再去處理數據;
而且,我們可以發現,異步 I/O 并沒有涉及到 PageCache,所以使用異步 I/O 就意味著要繞開 PageCache。
繞開 PageCache 的 I/O 叫直接 I/O,使用 PageCache 的 I/O 則叫緩存 I/O。通常,對于磁盤,異步 I/O 只支持直接 I/O。
前面也提到,大文件的傳輸不應該使用 PageCache,因為可能由于 PageCache 被大文件占據,而導致「熱點」小文件無法利用到 PageCache。
于是,在高并發的場景下,針對大文件的傳輸的方式,應該使用「異步 I/O + 直接 I/O」來替代零拷貝技術。
直接 I/O 應用場景常見的兩種:
應用程序已經實現了磁盤數據的緩存,那么可以不需要 PageCache 再次緩存,減少額外的性能損耗。在 MySQL 數據庫中,可以通過參數設置開啟直接 I/O,默認是不開啟;
傳輸大文件的時候,由于大文件難以命中 PageCache 緩存,而且會占滿 PageCache 導致「熱點」文件無法充分利用緩存,從而增大了性能開銷,因此,這時應該使用直接 I/O。
另外,由于直接 I/O 繞過了 PageCache,就無法享受內核的這兩點的優化:
內核的 I/O 調度算法會緩存盡可能多的 I/O 請求在 PageCache 中,最后「合并」成一個更大的 I/O 請求再發給磁盤,這樣做是為了減少磁盤的尋址操作;
內核也會「預讀」后續的 I/O 請求放在 PageCache 中,一樣是為了減少對磁盤的操作;
于是,傳輸大文件的時候,使用「異步 I/O + 直接 I/O」了,就可以無阻塞地讀取文件了。
所以,傳輸文件的時候,我們要根據文件的大小來使用不同的方式:
傳輸大文件的時候,使用「異步 I/O + 直接 I/O」;
傳輸小文件的時候,則使用「零拷貝技術」;
在 Nginx 中,我們可以用如下配置,來根據文件的大小來使用不同的方式:
location /video/ { sendfile on; aio on; directio 1024m; }
1
2
3
4
5
當文件大小大于 directio 值后,使用「異步 I/O + 直接 I/O」,否則使用「零拷貝技術」。
TCP/IP 網絡
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。