字節(jié)二面:為何還執(zhí)著傳統(tǒng)數(shù)據(jù)復(fù)制,零拷貝它不香嗎?
在當(dāng)今這個(gè)數(shù)據(jù)爆炸的時(shí)代,無(wú)論是互聯(lián)網(wǎng)巨頭的海量數(shù)據(jù)處理,還是新興創(chuàng)業(yè)公司對(duì)高效性能的追求,數(shù)據(jù)傳輸效率都成了決定系統(tǒng)成敗的關(guān)鍵因素。技術(shù)面試作為篩選人才的重要關(guān)卡,也越發(fā)關(guān)注候選人對(duì)前沿技術(shù)的掌握程度。字節(jié)跳動(dòng),作為行業(yè)內(nèi)的技術(shù)先鋒,在面試中更是不斷拋出極具挑戰(zhàn)性的問題,以選拔出真正的技術(shù)高手。
“字節(jié)二面:還要使用傳統(tǒng)數(shù)據(jù)復(fù)制,為什么不用零拷貝?” 當(dāng)這個(gè)問題擺在面試者面前時(shí),看似簡(jiǎn)單的詢問背后,實(shí)則隱藏著對(duì)候選人技術(shù)深度和廣度的全方位考察。它不僅要求你對(duì)零拷貝技術(shù)的原理了如指掌,更需要你能清晰洞察傳統(tǒng)數(shù)據(jù)復(fù)制在現(xiàn)代技術(shù)體系中的位置,以及在不同場(chǎng)景下二者的優(yōu)劣抉擇。
對(duì)于每一位渴望在技術(shù)領(lǐng)域嶄露頭角的開發(fā)者而言,理解這個(gè)問題,就如同掌握了一把通往高效數(shù)據(jù)處理世界的鑰匙。接下來(lái),就讓我們一同深入剖析傳統(tǒng)數(shù)據(jù)復(fù)制與零拷貝技術(shù)的本質(zhì)區(qū)別,探尋在字節(jié)跳動(dòng)這樣的技術(shù)驅(qū)動(dòng)型企業(yè)中,如何通過技術(shù)選型實(shí)現(xiàn)系統(tǒng)性能的飛躍 。
一、傳統(tǒng) I/O 的困境
在深入了解零拷貝之前,讓我們先來(lái)看一下傳統(tǒng)的 I/O 數(shù)據(jù)傳輸方式。想象一下,你正在從服務(wù)器上下載一個(gè)文件,這個(gè)看似簡(jiǎn)單的操作背后,其實(shí)涉及到了一系列復(fù)雜的數(shù)據(jù)傳輸過程。
當(dāng)你發(fā)起下載請(qǐng)求時(shí),操作系統(tǒng)會(huì)先從磁盤中讀取文件數(shù)據(jù)。這個(gè)過程中,數(shù)據(jù)首先會(huì)被讀取到內(nèi)核緩沖區(qū),然后再被拷貝到用戶空間的應(yīng)用程序緩沖區(qū)。接著,應(yīng)用程序?qū)?shù)據(jù)發(fā)送到網(wǎng)絡(luò),數(shù)據(jù)又會(huì)從用戶空間緩沖區(qū)拷貝到內(nèi)核空間的 socket 緩沖區(qū),最后通過網(wǎng)卡發(fā)送出去。
具體來(lái)說,傳統(tǒng) I/O 的數(shù)據(jù)傳輸過程如下:
- 用戶態(tài)到內(nèi)核態(tài)切換:應(yīng)用程序調(diào)用 read 系統(tǒng)調(diào)用,請(qǐng)求讀取文件數(shù)據(jù)。此時(shí),CPU 從用戶態(tài)切換到內(nèi)核態(tài),開始執(zhí)行內(nèi)核中的代碼。
- 磁盤數(shù)據(jù)讀取到內(nèi)核緩沖區(qū):內(nèi)核通過 DMA(直接內(nèi)存訪問)技術(shù),將磁盤數(shù)據(jù)直接拷貝到內(nèi)核緩沖區(qū)。DMA 技術(shù)可以讓硬件設(shè)備(如磁盤控制器)直接訪問內(nèi)存,而不需要 CPU 的干預(yù),從而減輕 CPU 的負(fù)擔(dān)。
- 內(nèi)核緩沖區(qū)數(shù)據(jù)拷貝到用戶緩沖區(qū):數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶空間的應(yīng)用程序緩沖區(qū)。這一步需要 CPU 的參與,因?yàn)橛脩艨臻g和內(nèi)核空間是相互隔離的,數(shù)據(jù)不能直接在兩者之間傳遞。
- 內(nèi)核態(tài)到用戶態(tài)切換:read 系統(tǒng)調(diào)用返回,CPU 從內(nèi)核態(tài)切換回用戶態(tài),應(yīng)用程序可以處理用戶緩沖區(qū)中的數(shù)據(jù)。
- 用戶態(tài)到內(nèi)核態(tài)切換:應(yīng)用程序調(diào)用 write 系統(tǒng)調(diào)用,請(qǐng)求將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)。CPU 再次從用戶態(tài)切換到內(nèi)核態(tài)。
- 用戶緩沖區(qū)數(shù)據(jù)拷貝到 socket 緩沖區(qū):數(shù)據(jù)從用戶緩沖區(qū)拷貝到內(nèi)核空間的 socket 緩沖區(qū),準(zhǔn)備通過網(wǎng)絡(luò)發(fā)送出去。這一步同樣需要 CPU 的參與。
- socket 緩沖區(qū)數(shù)據(jù)發(fā)送到網(wǎng)卡:內(nèi)核通過 DMA 技術(shù),將 socket 緩沖區(qū)中的數(shù)據(jù)拷貝到網(wǎng)卡緩沖區(qū),然后通過網(wǎng)絡(luò)發(fā)送出去。
- 內(nèi)核態(tài)到用戶態(tài)切換:write 系統(tǒng)調(diào)用返回,CPU 從內(nèi)核態(tài)切換回用戶態(tài),數(shù)據(jù)傳輸完成。
從上述過程可以看出,傳統(tǒng) I/O 在一次簡(jiǎn)單的文件傳輸中,就涉及了 4 次用戶態(tài)與內(nèi)核態(tài)的上下文切換,以及 4 次數(shù)據(jù)拷貝(其中 2 次是 DMA 拷貝,2 次是 CPU 拷貝)。上下文切換和數(shù)據(jù)拷貝都會(huì)消耗 CPU 資源和時(shí)間,在高并發(fā)的場(chǎng)景下,這些開銷會(huì)嚴(yán)重影響系統(tǒng)的性能。
例如,在一個(gè)高并發(fā)的文件服務(wù)器中,如果每一次文件傳輸都要經(jīng)歷如此繁瑣的過程,那么服務(wù)器的吞吐量將會(huì)受到極大的限制,響應(yīng)速度也會(huì)變慢,用戶體驗(yàn)也會(huì)大打折扣。因此,為了提高系統(tǒng)的性能,我們需要尋找一種更高效的數(shù)據(jù)傳輸方式,這就是零拷貝技術(shù)應(yīng)運(yùn)而生的背景。
二、零拷貝技術(shù)閃亮登場(chǎng)
零拷貝(zero-copy)基本思想是:數(shù)據(jù)報(bào)從網(wǎng)絡(luò)設(shè)備到用戶程序空間傳遞的過程中,減少數(shù)據(jù)拷貝次數(shù),減少系統(tǒng)調(diào)用,實(shí)現(xiàn)CPU的零參與,徹底消除 CPU在這方面的負(fù)載。實(shí)現(xiàn)零拷貝用到的最主要技術(shù)是DMA數(shù)據(jù)傳輸技術(shù)和內(nèi)存區(qū)域映射技術(shù)。如圖1所示,傳統(tǒng)的網(wǎng)絡(luò)數(shù)據(jù)報(bào)處理,需要經(jīng)過網(wǎng)絡(luò)設(shè)備到操作系統(tǒng)內(nèi)存空間,系統(tǒng)內(nèi)存空間到用戶應(yīng)用程序空間這兩次拷貝,同時(shí)還需要經(jīng)歷用戶向系統(tǒng)發(fā)出的系統(tǒng)調(diào)用。
而零拷貝技術(shù)則首先利用DMA技術(shù)將網(wǎng)絡(luò)數(shù)據(jù)報(bào)直接傳遞到系統(tǒng)內(nèi)核預(yù)先分配的地址空間中,避免CPU的參與;同時(shí),將系統(tǒng)內(nèi)核中存儲(chǔ)數(shù)據(jù)報(bào)的內(nèi)存區(qū)域映射到檢測(cè)程序的應(yīng)用程序空間(還有一種方式是在用戶空間建立一緩存,并將其映射到內(nèi)核空間,類似于linux系統(tǒng)下的kiobuf技術(shù)),檢測(cè)程序直接對(duì)這塊內(nèi)存進(jìn)行訪問,從而減少了系統(tǒng)內(nèi)核向用戶空間的內(nèi)存拷貝,同時(shí)減少了系統(tǒng)調(diào)用的開銷,實(shí)現(xiàn)了真正的“零拷貝”。
圖片
2.1什么是零拷貝?
簡(jiǎn)單一點(diǎn)來(lái)說,零拷貝就是一種避免 CPU 將數(shù)據(jù)從一塊存儲(chǔ)拷貝到另外一塊存儲(chǔ)的技術(shù)。針對(duì)操作系統(tǒng)中的設(shè)備驅(qū)動(dòng)程序、文件系統(tǒng)以及網(wǎng)絡(luò)協(xié)議堆棧而出現(xiàn)的各種零拷貝技術(shù)極大地提升了特定應(yīng)用程序的性能,并且使得這些應(yīng)用程序可以更加有效地利用系統(tǒng)資源。這種性能的提升就是通過在數(shù)據(jù)拷貝進(jìn)行的同時(shí),允許 CPU 執(zhí)行其他的任務(wù)來(lái)實(shí)現(xiàn)的。
零拷貝技術(shù)可以減少數(shù)據(jù)拷貝和共享總線操作的次數(shù),消除傳輸數(shù)據(jù)在存儲(chǔ)器之間不必要的中間拷貝次數(shù),從而有效地提高數(shù)據(jù)傳輸效率。而且,零拷貝技術(shù)減少了用戶應(yīng)用程序地址空間和操作系統(tǒng)內(nèi)核地址空間之間因?yàn)樯舷挛那袚Q而帶來(lái)的開銷。進(jìn)行大量的數(shù)據(jù)拷貝操作其實(shí)是一件簡(jiǎn)單的任務(wù),從操作系統(tǒng)的角度來(lái)說,如果 CPU 一直被占用著去執(zhí)行這項(xiàng)簡(jiǎn)單的任務(wù),那么這將會(huì)是很浪費(fèi)資源的;如果有其他比較簡(jiǎn)單的系統(tǒng)部件可以代勞這件事情,從而使得 CPU 解脫出來(lái)可以做別的事情,那么系統(tǒng)資源的利用則會(huì)更加有效。
零拷貝技術(shù),就是為了解決傳統(tǒng) I/O 的性能瓶頸而誕生的。簡(jiǎn)單來(lái)說,零拷貝技術(shù)的核心就是減少數(shù)據(jù)在內(nèi)存之間的拷貝次數(shù),從而提高數(shù)據(jù)傳輸?shù)男省_@里的 “零拷貝” 并不是指完全沒有數(shù)據(jù)拷貝,而是盡可能地減少不必要的拷貝操作。
在零拷貝技術(shù)中,數(shù)據(jù)可以直接在內(nèi)核空間中進(jìn)行傳輸,而不需要經(jīng)過用戶空間的緩沖區(qū)。這樣就避免了傳統(tǒng) I/O 中數(shù)據(jù)在用戶空間和內(nèi)核空間之間的多次拷貝,減少了 CPU 的上下文切換和數(shù)據(jù)拷貝的開銷,提高了系統(tǒng)的性能和吞吐量。
以 Linux 系統(tǒng)中的sendfile系統(tǒng)調(diào)用為例,這是一種常見的零拷貝實(shí)現(xiàn)方式。在使用sendfile時(shí),數(shù)據(jù)可以直接從內(nèi)核緩沖區(qū)傳輸?shù)?socket 緩沖區(qū),而不需要經(jīng)過用戶空間。具體過程如下:
- 用戶態(tài)到內(nèi)核態(tài)切換:應(yīng)用程序調(diào)用sendfile系統(tǒng)調(diào)用,請(qǐng)求將文件數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)。CPU 從用戶態(tài)切換到內(nèi)核態(tài)。
- 磁盤數(shù)據(jù)讀取到內(nèi)核緩沖區(qū):內(nèi)核通過 DMA 技術(shù),將磁盤數(shù)據(jù)直接拷貝到內(nèi)核緩沖區(qū)。
- 內(nèi)核緩沖區(qū)數(shù)據(jù)直接傳輸?shù)?socket 緩沖區(qū):內(nèi)核直接將內(nèi)核緩沖區(qū)中的數(shù)據(jù)傳輸?shù)?socket 緩沖區(qū),而不需要經(jīng)過用戶空間。這一步利用了內(nèi)核的特殊機(jī)制,直接在內(nèi)核空間中完成數(shù)據(jù)的傳輸,避免了數(shù)據(jù)在用戶空間和內(nèi)核空間之間的拷貝。
- socket 緩沖區(qū)數(shù)據(jù)發(fā)送到網(wǎng)卡:內(nèi)核通過 DMA 技術(shù),將 socket 緩沖區(qū)中的數(shù)據(jù)拷貝到網(wǎng)卡緩沖區(qū),然后通過網(wǎng)絡(luò)發(fā)送出去。
- 內(nèi)核態(tài)到用戶態(tài)切換:sendfile系統(tǒng)調(diào)用返回,CPU 從內(nèi)核態(tài)切換回用戶態(tài),數(shù)據(jù)傳輸完成。
對(duì)比傳統(tǒng) I/O 和零拷貝技術(shù)的數(shù)據(jù)傳輸路徑,可以明顯看出零拷貝技術(shù)的優(yōu)勢(shì)。在傳統(tǒng) I/O 中,數(shù)據(jù)需要在用戶空間和內(nèi)核空間之間多次拷貝,而在零拷貝技術(shù)中,數(shù)據(jù)可以直接在內(nèi)核空間中傳輸,減少了數(shù)據(jù)拷貝的次數(shù)和上下文切換的開銷。這就好比在物流運(yùn)輸中,傳統(tǒng) I/O 就像是貨物需要多次裝卸、轉(zhuǎn)運(yùn),而零拷貝技術(shù)則像是貨物可以直接從起點(diǎn)運(yùn)輸?shù)浇K點(diǎn),中間不需要多次中轉(zhuǎn),大大提高了運(yùn)輸?shù)男省?/p>
避免數(shù)據(jù)拷貝:
- 避免操作系統(tǒng)內(nèi)核緩沖區(qū)之間進(jìn)行數(shù)據(jù)拷貝操作。
- 避免操作系統(tǒng)內(nèi)核和用戶應(yīng)用程序地址空間這兩者之間進(jìn)行數(shù)據(jù)拷貝操作。
- 用戶應(yīng)用程序可以避開操作系統(tǒng)直接訪問硬件存儲(chǔ)。
- 數(shù)據(jù)傳輸盡量讓 DMA 來(lái)做。
將多種操作結(jié)合在一起
- 避免不必要的系統(tǒng)調(diào)用和上下文切換。
- 需要拷貝的數(shù)據(jù)可以先被緩存起來(lái)。
- 對(duì)數(shù)據(jù)進(jìn)行處理盡量讓硬件來(lái)做。
對(duì)于高速網(wǎng)絡(luò)來(lái)說,零拷貝技術(shù)是非常重要的。這是因?yàn)楦咚倬W(wǎng)絡(luò)的網(wǎng)絡(luò)鏈接能力與 CPU 的處理能力接近,甚至?xí)^ CPU 的處理能力。
如果是這樣的話,那么 CPU 就有可能需要花費(fèi)幾乎所有的時(shí)間去拷貝要傳輸?shù)臄?shù)據(jù),而沒有能力再去做別的事情,這就產(chǎn)生了性能瓶頸,限制了通訊速率,從而降低了網(wǎng)絡(luò)連接的能力。一般來(lái)說,一個(gè) CPU 時(shí)鐘周期可以處理一位的數(shù)據(jù)。舉例來(lái)說,一個(gè) 1 GHz 的處理器可以對(duì) 1Gbit/s 的網(wǎng)絡(luò)鏈接進(jìn)行傳統(tǒng)的數(shù)據(jù)拷貝操作,但是如果是 10 Gbit/s 的網(wǎng)絡(luò),那么對(duì)于相同的處理器來(lái)說,零拷貝技術(shù)就變得非常重要了。
對(duì)于超過 1 Gbit/s 的網(wǎng)絡(luò)鏈接來(lái)說,零拷貝技術(shù)在超級(jí)計(jì)算機(jī)集群以及大型的商業(yè)數(shù)據(jù)中心中都有所應(yīng)用。然而,隨著信息技術(shù)的發(fā)展,1 Gbit/s,10 Gbit/s 以及 100 Gbit/s 的網(wǎng)絡(luò)會(huì)越來(lái)越普及,那么零拷貝技術(shù)也會(huì)變得越來(lái)越普及,這是因?yàn)榫W(wǎng)絡(luò)鏈接的處理能力比 CPU 的處理能力的增長(zhǎng)要快得多。傳統(tǒng)的數(shù)據(jù)拷貝受限于傳統(tǒng)的操作系統(tǒng)或者通信協(xié)議,這就限制了數(shù)據(jù)傳輸性能。零拷貝技術(shù)通過減少數(shù)據(jù)拷貝次數(shù),簡(jiǎn)化協(xié)議處理的層次,在應(yīng)用程序和網(wǎng)絡(luò)之間提供更快的數(shù)據(jù)傳輸方法,從而可以有效地降低通信延遲,提高網(wǎng)絡(luò)吞吐率。零拷貝技術(shù)是實(shí)現(xiàn)主機(jī)或者路由器等設(shè)備高速網(wǎng)絡(luò)接口的主要技術(shù)之一。
現(xiàn)代的 CPU 和存儲(chǔ)體系結(jié)構(gòu)提供了很多相關(guān)的功能來(lái)減少或避免 I/O 操作過程中產(chǎn)生的不必要的 CPU 數(shù)據(jù)拷貝操作,但是,CPU 和存儲(chǔ)體系結(jié)構(gòu)的這種優(yōu)勢(shì)經(jīng)常被過高估計(jì)。存儲(chǔ)體系結(jié)構(gòu)的復(fù)雜性以及網(wǎng)絡(luò)協(xié)議中必需的數(shù)據(jù)傳輸可能會(huì)產(chǎn)生問題,有時(shí)甚至?xí)?dǎo)致零拷貝這種技術(shù)的優(yōu)點(diǎn)完全喪失。在下一章中,我們會(huì)介紹幾種 Linux 操作系統(tǒng)中出現(xiàn)的零拷貝技術(shù),簡(jiǎn)單描述一下它們的實(shí)現(xiàn)方法,并對(duì)它們的弱點(diǎn)進(jìn)行分析。
2.2零拷貝技術(shù)分類
零拷貝技術(shù)的發(fā)展很多樣化,現(xiàn)有的零拷貝技術(shù)種類也非常多,而當(dāng)前并沒有一個(gè)適合于所有場(chǎng)景的零拷貝技術(shù)的出現(xiàn)。對(duì)于 Linux 來(lái)說,現(xiàn)存的零拷貝技術(shù)也比較多,這些零拷貝技術(shù)大部分存在于不同的 Linux 內(nèi)核版本,有些舊的技術(shù)在不同的 Linux 內(nèi)核版本間得到了很大的發(fā)展或者已經(jīng)漸漸被新的技術(shù)所代替。本文針對(duì)這些零拷貝技術(shù)所適用的不同場(chǎng)景對(duì)它們進(jìn)行了劃分。概括起來(lái),Linux 中的零拷貝技術(shù)主要有下面這幾種:
直接 I/O:對(duì)于這種數(shù)據(jù)傳輸方式來(lái)說,應(yīng)用程序可以直接訪問硬件存儲(chǔ),操作系統(tǒng)內(nèi)核只是輔助數(shù)據(jù)傳輸:這類零拷貝技術(shù)針對(duì)的是操作系統(tǒng)內(nèi)核并不需要對(duì)數(shù)據(jù)進(jìn)行直接處理的情況,數(shù)據(jù)可以在應(yīng)用程序地址空間的緩沖區(qū)和磁盤之間直接進(jìn)行傳輸,完全不需要 Linux 操作系統(tǒng)內(nèi)核提供的頁(yè)緩存的支持。
在數(shù)據(jù)傳輸?shù)倪^程中,避免數(shù)據(jù)在操作系統(tǒng)內(nèi)核地址空間的緩沖區(qū)和用戶應(yīng)用程序地址空間的緩沖區(qū)之間進(jìn)行拷貝。有的時(shí)候,應(yīng)用程序在數(shù)據(jù)進(jìn)行傳輸?shù)倪^程中不需要對(duì)數(shù)據(jù)進(jìn)行訪問,那么,將數(shù)據(jù)從 Linux 的頁(yè)緩存拷貝到用戶進(jìn)程的緩沖區(qū)中就可以完全避免,傳輸?shù)臄?shù)據(jù)在頁(yè)緩存中就可以得到處理。在某些特殊的情況下,這種零拷貝技術(shù)可以獲得較好的性能。Linux 中提供類似的系統(tǒng)調(diào)用主要有 mmap(),sendfile() 以及 splice()。
對(duì)數(shù)據(jù)在 Linux 的頁(yè)緩存和用戶進(jìn)程的緩沖區(qū)之間的傳輸過程進(jìn)行優(yōu)化。該零拷貝技術(shù)側(cè)重于靈活地處理數(shù)據(jù)在用戶進(jìn)程的緩沖區(qū)和操作系統(tǒng)的頁(yè)緩存之間的拷貝操作。這種方法延續(xù)了傳統(tǒng)的通信方式,但是更加靈活。在 Linux 中,該方法主要利用了寫時(shí)復(fù)制技術(shù)。
前兩類方法的目的主要是為了避免應(yīng)用程序地址空間和操作系統(tǒng)內(nèi)核地址空間這兩者之間的緩沖區(qū)拷貝操作。這兩類零拷貝技術(shù)通常適用在某些特殊的情況下,比如要傳送的數(shù)據(jù)不需要經(jīng)過操作系統(tǒng)內(nèi)核的處理或者不需要經(jīng)過應(yīng)用程序的處理。第三類方法則繼承了傳統(tǒng)的應(yīng)用程序地址空間和操作系統(tǒng)內(nèi)核地址空間之間數(shù)據(jù)傳輸?shù)母拍?,進(jìn)而針對(duì)數(shù)據(jù)傳輸本身進(jìn)行優(yōu)化。
我們知道,硬件和軟件之間的數(shù)據(jù)傳輸可以通過使用 DMA 來(lái)進(jìn)行,DMA進(jìn)行數(shù)據(jù)傳輸?shù)倪^程中幾乎不需要CPU參與,這樣就可以把 CPU 解放出來(lái)去做更多其他的事情,但是當(dāng)數(shù)據(jù)需要在用戶地址空間的緩沖區(qū)和Linux操作系統(tǒng)內(nèi)核的頁(yè)緩存之間進(jìn)行傳輸?shù)臅r(shí)候,并沒有類似DMA這種工具可以使用,CPU 需要全程參與到這種數(shù)據(jù)拷貝操作中,所以這第三類方法的目的是可以有效地改善數(shù)據(jù)在用戶地址空間和操作系統(tǒng)內(nèi)核地址空間之間傳遞的效率。
三、零拷貝的定義
Zero-copy, 就是在操作數(shù)據(jù)時(shí), 不需要將數(shù)據(jù) buffer 從一個(gè)內(nèi)存區(qū)域拷貝到另一個(gè)內(nèi)存區(qū)域. 因?yàn)樯倭艘淮蝺?nèi)存的拷貝, 因此 CPU 的效率就得到的提升;在OS層面上的 Zero-copy 通常指避免在 用戶態(tài)(User-space) 與 內(nèi)核態(tài)(Kernel-space) 之間來(lái)回拷貝數(shù)據(jù)。
Netty 中的 Zero-copy 與 OS 的 Zero-copy 不太一樣, Netty的 Zero-coyp 完全是在用戶態(tài)(Java 層面)的, 它的 Zero-copy 的更多的是偏向于優(yōu)化數(shù)據(jù)操作。
3.1Netty的“零拷貝”
主要體現(xiàn)在如下三個(gè)方面:
- Netty的接收和發(fā)送ByteBuffer采用DIRECT BUFFERS,使用堆外直接內(nèi)存進(jìn)行Socket讀寫,不需要進(jìn)行字節(jié)緩沖區(qū)的二次拷貝。如果使用傳統(tǒng)的堆內(nèi)存(HEAP BUFFERS)進(jìn)行Socket讀寫,JVM會(huì)將堆內(nèi)存Buffer拷貝一份到直接內(nèi)存中,然后才寫入Socket中。相比于堆外直接內(nèi)存,消息在發(fā)送過程中多了一次緩沖區(qū)的內(nèi)存拷貝。
- Netty提供了組合Buffer對(duì)象,可以聚合多個(gè)ByteBuffer對(duì)象,用戶可以像操作一個(gè)Buffer那樣方便得對(duì)組合Buffer進(jìn)行操作,避免了傳統(tǒng)通過內(nèi)存拷貝的方式將幾個(gè)小Buffer合并成一個(gè)大的Buffer。
- Netty的文件傳輸采用了transferTo方法,它可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo)Channel,避免了傳統(tǒng)通過循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問題。
3.2傳統(tǒng)IO方式
在 java 開發(fā)中,從某臺(tái)機(jī)器將一份數(shù)據(jù)通過網(wǎng)絡(luò)傳輸?shù)搅硗庖慌_(tái)機(jī)器,大致的代碼如下:
Socket socket = new Socket(HOST, PORT);
InputStream inputStream = new FileInputStream(FILE_PATH);
OutputStream outputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
while (inputStream.read(buffer) >= 0) {
outputStream.write(buffer);
}
outputStream.close();
socket.close();
inputStream.close();
看起來(lái)代碼很簡(jiǎn)單,但如果我們深入到操作系統(tǒng)層面,就會(huì)發(fā)現(xiàn)實(shí)際的微觀操作更復(fù)雜。具體操作如下圖:
圖片
1.用戶進(jìn)程向OS發(fā)出read()系統(tǒng)調(diào)用,觸發(fā)上下文切換,從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)。
2.CPU發(fā)起IO請(qǐng)求,通過直接內(nèi)存訪問(DMA)從磁盤讀取文件內(nèi)容,復(fù)制到內(nèi)核緩沖區(qū)PageCache中
3.將內(nèi)核緩沖區(qū)數(shù)據(jù),拷貝到用戶空間緩沖區(qū),觸發(fā)上下文切換,從內(nèi)核態(tài)轉(zhuǎn)換到用戶態(tài)。
4.用戶進(jìn)程向OS發(fā)起write系統(tǒng)調(diào)用,觸發(fā)上下文切換,從用戶態(tài)切換到內(nèi)核態(tài)。
5.將數(shù)據(jù)從用戶緩沖區(qū)拷貝到內(nèi)核中與目的地Socket關(guān)聯(lián)的緩沖區(qū)。
6.數(shù)據(jù)最終經(jīng)由Socket通過DMA傳送到硬件(網(wǎng)卡)緩沖區(qū),write()系統(tǒng)調(diào)用返回,并從內(nèi)核態(tài)切換回用戶態(tài)。
圖片
四、零拷貝(Zero-copy)
4.1數(shù)據(jù)拷貝基礎(chǔ)過程
在Linux系統(tǒng)內(nèi)部緩存和內(nèi)存容量都是有限的,更多的數(shù)據(jù)都是存儲(chǔ)在磁盤中。對(duì)于Web服務(wù)器來(lái)說,經(jīng)常需要從磁盤中讀取數(shù)據(jù)到內(nèi)存,然后再通過網(wǎng)卡傳輸給用戶:
圖片
上述數(shù)據(jù)流轉(zhuǎn)只是大框,接下來(lái)看看幾種模式。
(1)僅CPU方式
- 當(dāng)應(yīng)用程序需要讀取磁盤數(shù)據(jù)時(shí),調(diào)用read()從用戶態(tài)陷入內(nèi)核態(tài),read()這個(gè)系統(tǒng)調(diào)用最終由CPU來(lái)完成;
- CPU向磁盤發(fā)起I/O請(qǐng)求,磁盤收到之后開始準(zhǔn)備數(shù)據(jù);
- 磁盤將數(shù)據(jù)放到磁盤緩沖區(qū)之后,向CPU發(fā)起I/O中斷,報(bào)告CPU數(shù)據(jù)已經(jīng)Ready了;
- CPU收到磁盤控制器的I/O中斷之后,開始拷貝數(shù)據(jù),完成之后read()返回,再?gòu)膬?nèi)核態(tài)切換到用戶態(tài);
圖片
(2)CPU&DMA方式
CPU的時(shí)間寶貴,讓它做雜活就是浪費(fèi)資源。
直接內(nèi)存訪問(Direct Memory Access),是一種硬件設(shè)備繞開CPU獨(dú)立直接訪問內(nèi)存的機(jī)制。所以DMA在一定程度上解放了CPU,把之前CPU的雜活讓硬件直接自己做了,提高了CPU效率。
目前支持DMA的硬件包括:網(wǎng)卡、聲卡、顯卡、磁盤控制器等。
有了DMA的參與之后的流程發(fā)生了一些變化:
圖片
主要的變化是,CPU不再和磁盤直接交互,而是DMA和磁盤交互并且將數(shù)據(jù)從磁盤緩沖區(qū)拷貝到內(nèi)核緩沖區(qū),之后的過程類似。
“【敲黑板】無(wú)論從僅CPU方式和DMA&CPU方式,都存在多次冗余數(shù)據(jù)拷貝和內(nèi)核態(tài)&用戶態(tài)的切換?!?/p>
我們繼續(xù)思考Web服務(wù)器讀取本地磁盤文件數(shù)據(jù)再通過網(wǎng)絡(luò)傳輸給用戶的詳細(xì)過程。
4.2普通模式數(shù)據(jù)交互
一次完成的數(shù)據(jù)交互包括幾個(gè)部分:系統(tǒng)調(diào)用syscall、CPU、DMA、網(wǎng)卡、磁盤等。
圖片
系統(tǒng)調(diào)用syscall是應(yīng)用程序和內(nèi)核交互的橋梁,每次進(jìn)行調(diào)用/返回就會(huì)產(chǎn)生兩次切換:
- 調(diào)用syscall 從用戶態(tài)切換到內(nèi)核態(tài)
- syscall返回 從內(nèi)核態(tài)切換到用戶態(tài)
圖片
來(lái)看下完整的數(shù)據(jù)拷貝過程簡(jiǎn)圖:
圖片
讀數(shù)據(jù)過程:
- 應(yīng)用程序要讀取磁盤數(shù)據(jù),調(diào)用read()函數(shù)從而實(shí)現(xiàn)用戶態(tài)切換內(nèi)核態(tài),這是第1次狀態(tài)切換;
- DMA控制器將數(shù)據(jù)從磁盤拷貝到內(nèi)核緩沖區(qū),這是第1次DMA拷貝;
- CPU將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū),這是第1次CPU拷貝;
- CPU完成拷貝之后,read()函數(shù)返回實(shí)現(xiàn)用戶態(tài)切換用戶態(tài),這是第2次狀態(tài)切換;
寫數(shù)據(jù)過程:
- 應(yīng)用程序要向網(wǎng)卡寫數(shù)據(jù),調(diào)用write()函數(shù)實(shí)現(xiàn)用戶態(tài)切換內(nèi)核態(tài),這是第1次切換;
- CPU將用戶緩沖區(qū)數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū),這是第1次CPU拷貝;
- DMA控制器將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到socket緩沖區(qū),這是第1次DMA拷貝;
- 完成拷貝之后,write()函數(shù)返回實(shí)現(xiàn)內(nèi)核態(tài)切換用戶態(tài),這是第2次切換;
綜上所述:
- 讀過程涉及2次空間切換、1次DMA拷貝、1次CPU拷貝;
- 寫過程涉及2次空間切換、1次DMA拷貝、1次CPU拷貝;
可見傳統(tǒng)模式下,涉及多次空間切換和數(shù)據(jù)冗余拷貝,效率并不高,接下來(lái)就該零拷貝技術(shù)出場(chǎng)了。
4.3零拷貝技術(shù)
(1)出現(xiàn)原因
我們可以看到,如果應(yīng)用程序不對(duì)數(shù)據(jù)做修改,從內(nèi)核緩沖區(qū)到用戶緩沖區(qū),再?gòu)挠脩艟彌_區(qū)到內(nèi)核緩沖區(qū)。兩次數(shù)據(jù)拷貝都需要CPU的參與,并且涉及用戶態(tài)與內(nèi)核態(tài)的多次切換,加重了CPU負(fù)擔(dān)。
我們需要降低冗余數(shù)據(jù)拷貝、解放CPU,這也就是零拷貝Zero-Copy技術(shù)。
(2)解決思路
目前來(lái)看,零拷貝技術(shù)的幾個(gè)實(shí)現(xiàn)手段包括:mmap+write、sendfile、sendfile+DMA收集、splice等。
圖片
(3)mmap方式
mmap是Linux提供的一種內(nèi)存映射文件的機(jī)制,它實(shí)現(xiàn)了將內(nèi)核中讀緩沖區(qū)地址與用戶空間緩沖區(qū)地址進(jìn)行映射,從而實(shí)現(xiàn)內(nèi)核緩沖區(qū)與用戶緩沖區(qū)的共享。這樣就減少了一次用戶態(tài)和內(nèi)核態(tài)的CPU拷貝,但是在內(nèi)核空間內(nèi)仍然有一次CPU拷貝。
圖片
mmap對(duì)大文件傳輸有一定優(yōu)勢(shì),但是小文件可能出現(xiàn)碎片,并且在多個(gè)進(jìn)程同時(shí)操作文件時(shí)可能產(chǎn)生引發(fā)coredump的signal。
(4)sendfile方式
mmap+write方式有一定改進(jìn),但是由系統(tǒng)調(diào)用引起的狀態(tài)切換并沒有減少。
sendfile系統(tǒng)調(diào)用是在 Linux 內(nèi)核2.1版本中被引入,它建立了兩個(gè)文件之間的傳輸通道。
sendfile方式只使用一個(gè)函數(shù)就可以完成之前的read+write 和 mmap+write的功能,這樣就少了2次狀態(tài)切換,由于數(shù)據(jù)不經(jīng)過用戶緩沖區(qū),因此該數(shù)據(jù)無(wú)法被修改。
圖片
splice 系統(tǒng)調(diào)用可以在內(nèi)核緩沖區(qū)和socket緩沖區(qū)之間建立管道來(lái)傳輸數(shù)據(jù),避免了兩者之間的 CPU 拷貝操作。
圖片
splice也有一些局限,它的兩個(gè)文件描述符參數(shù)中有一個(gè)必須是管道設(shè)備。
以下使用 FileChannel.transferTo 方法,實(shí)現(xiàn) zero-copy:
SocketAddress socketAddress = new InetSocketAddress(HOST, PORT);
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(socketAddress);
File file = new File(FILE_PATH);
FileChannel fileChannel = new FileInputStream(file).getChannel();
fileChannel.transferTo(0, file.length(), socketChannel);
fileChannel.close();
socketChannel.close();
相比傳統(tǒng)方式,零拷貝的執(zhí)行流程如下圖:
圖片
可以看到,相比傳統(tǒng)方式,零拷貝不走數(shù)據(jù)緩沖區(qū)減少了一些不必要的操作。
4.4零拷貝的應(yīng)用
零拷貝在很多框架中得到了廣泛使用,常見的比如 Netty、Kafka 等等。
在 kafka 中使用了很多設(shè)計(jì)思想,比如分區(qū)并行、順序?qū)懭?、?yè)緩存、高效序列化、零拷貝等等。
上邊博客分析了 Kafka 的大概架構(gòu),知道了 kafka 中的文件都是以.log 文件存儲(chǔ),每個(gè)日志文件對(duì)應(yīng)兩個(gè)索引文件.index 與.timeindex。
kafka 在傳輸數(shù)據(jù)時(shí)利用索引,使用 fileChannel.transferTo (position, count, socketChannel) 指定數(shù)據(jù)位置與大小實(shí)現(xiàn)零拷貝。
kafka 底層傳輸源碼:(TransportLayer)
/**
* Transfers bytes from `fileChannel` to this `TransportLayer`.
*
* This method will delegate to {@link FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)},
* but it will unwrap the destination channel, if possible, in order to benefit from zero copy. This is required
* because the fast path of `transferTo` is only executed if the destination buffer inherits from an internal JDK
* class.
*
* @param fileChannel The source channel
* @param position The position within the file at which the transfer is to begin; must be non-negative
* @param count The maximum number of bytes to be transferred; must be non-negative
* @return The number of bytes, possibly zero, that were actually transferred
* @see FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)
*/
long transferFrom(FileChannel fileChannel, long position, long count) throws IOException;
實(shí)現(xiàn)類(PlaintextTransportLayer):
@OverRide
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
return fileChannel.transferTo(position, count, socketChannel);
}
該方法的功能是將 FileChannel 中的數(shù)據(jù)傳輸?shù)?TransportLayer,也就是 SocketChannel。在實(shí)現(xiàn)類 PlaintextTransportLayer 的對(duì)應(yīng)方法中,就是直接調(diào)用了 FileChannel.transferTo () 方法。
五、零拷貝在Java世界的奇妙冒險(xiǎn)
5.1NIO 的零拷貝絕技
在 Java 的世界里,NIO(New I/O)為我們提供了實(shí)現(xiàn)零拷貝的強(qiáng)大工具。其中,F(xiàn)ileChannel類的transferTo和transferFrom方法就是實(shí)現(xiàn)零拷貝的關(guān)鍵。
transferTo方法可以將當(dāng)前通道中的數(shù)據(jù)直接傳輸?shù)侥繕?biāo)通道,而transferFrom方法則是從源通道讀取數(shù)據(jù)并傳輸?shù)疆?dāng)前通道。這兩個(gè)方法的實(shí)現(xiàn)依賴于底層操作系統(tǒng)的支持,在支持零拷貝的操作系統(tǒng)上,它們可以直接利用操作系統(tǒng)的零拷貝機(jī)制,避免數(shù)據(jù)在用戶空間和內(nèi)核空間之間的拷貝,從而大大提高數(shù)據(jù)傳輸?shù)男省?/p>
下面通過一個(gè)簡(jiǎn)單的代碼示例來(lái)展示如何使用transferTo方法實(shí)現(xiàn)文件到網(wǎng)絡(luò)通道的零拷貝傳輸:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class ZeroCopyExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("source.txt");
FileOutputStream fos = new FileOutputStream("destination.txt");
FileChannel sourceChannel = fis.getChannel();
FileChannel destChannel = fos.getChannel()) {
long position = 0;
long count = sourceChannel.size();
// 使用transferTo方法實(shí)現(xiàn)零拷貝
sourceChannel.transferTo(position, count, destChannel);
System.out.println("File copied successfully using zero copy!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在這個(gè)示例中,我們首先創(chuàng)建了兩個(gè)FileChannel,一個(gè)用于讀取源文件,另一個(gè)用于寫入目標(biāo)文件。然后,通過transferTo方法將源文件通道中的數(shù)據(jù)直接傳輸?shù)侥繕?biāo)文件通道,整個(gè)過程中數(shù)據(jù)沒有經(jīng)過用戶空間的緩沖區(qū),實(shí)現(xiàn)了零拷貝。
5.2Netty框架的零拷貝奧秘
Netty 是一個(gè)高性能的 Java 網(wǎng)絡(luò)框架,它在設(shè)計(jì)中充分利用了零拷貝技術(shù),以提升數(shù)據(jù)傳輸?shù)男?。?Netty 中,零拷貝主要體現(xiàn)在以下幾個(gè)方面:
首先,Netty 接收和發(fā)送ByteBuffer采用的都是堆外直接內(nèi)存。使用堆外直接內(nèi)存進(jìn)行 Socket 的讀 / 寫,無(wú)須進(jìn)行字節(jié)緩沖區(qū)的二次拷貝。如果使用傳統(tǒng)的堆內(nèi)存進(jìn)行 Socket 的讀 / 寫,則 JVM 會(huì)將堆內(nèi)存 Buffer 數(shù)據(jù)拷貝到堆外直接內(nèi)存中,然后才寫入 Socket 中。與堆外直接內(nèi)存相比,使用傳統(tǒng)的堆內(nèi)存,在消息的發(fā)送過程中多了一次緩沖區(qū)的內(nèi)存拷貝 。
其次,Netty 的ByteBuf提供了強(qiáng)大的零拷貝功能。ByteBuf是 Netty 的數(shù)據(jù)容器,它通過切片(slice)和組合(CompositeByteBuf)等操作實(shí)現(xiàn)了零拷貝。例如,slice方法可以將一個(gè)ByteBuf分解為多個(gè)共享同一個(gè)存儲(chǔ)區(qū)域的ByteBuf,避免了內(nèi)存的拷貝。假設(shè)有一個(gè)ByteBuf包含了消息的頭部和消息體,我們可以通過slice方法分別獲取頭部和消息體的ByteBuf,而不需要進(jìn)行數(shù)據(jù)的拷貝:
ByteBuf buffer = Unpooled.wrappedBuffer(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
ByteBuf header = buffer.slice(0, 5);
ByteBuf body = buffer.slice(5, 5);
在這個(gè)例子中,header和body共享了buffer的底層存儲(chǔ)區(qū)域,對(duì)buffer的修改會(huì)反映在header和body上,反之亦然。
另外,CompositeByteBuf可以將多個(gè)ByteBuf合并為一個(gè)邏輯上的ByteBuf,避免了各個(gè)ByteBuf之間的拷貝。比如,在處理協(xié)議數(shù)據(jù)時(shí),協(xié)議數(shù)據(jù)可能由頭部和消息體組成,而頭部和消息體分別存放在兩個(gè)ByteBuf中,我們可以使用CompositeByteBuf將它們合并為一個(gè)邏輯上的整體:
ByteBuf header = Unpooled.wrappedBuffer(new byte[]{1, 2, 3, 4, 5});
ByteBuf body = Unpooled.wrappedBuffer(new byte[]{6, 7, 8, 9, 10});
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body);
這樣,compositeByteBuf就將header和body組合在了一起,在后續(xù)的處理中可以像操作一個(gè)ByteBuf一樣操作它,而不需要進(jìn)行數(shù)據(jù)的拷貝。
最后,Netty 使用FileRegion實(shí)現(xiàn)文件傳輸?shù)牧憧截?。FileRegion底層封裝了FileChannel的transferTo方法,可以將文件緩沖區(qū)的數(shù)據(jù)直接傳輸?shù)侥繕?biāo)通道,避免內(nèi)核緩沖區(qū)和用戶態(tài)緩沖區(qū)之間的數(shù)據(jù)拷貝,這屬于操作系統(tǒng)級(jí)別的零拷貝。例如,在一個(gè)文件服務(wù)器中,當(dāng)客戶端請(qǐng)求下載文件時(shí),服務(wù)器可以使用FileRegion將文件數(shù)據(jù)直接傳輸給客戶端,而不需要將文件數(shù)據(jù)先讀取到用戶空間再發(fā)送出去,從而提高了文件傳輸?shù)男省?/p>
六、零拷貝的應(yīng)用舞臺(tái)
6.1網(wǎng)絡(luò)服務(wù)器:高效傳輸?shù)幕?/h3>
在網(wǎng)絡(luò)服務(wù)器領(lǐng)域,零拷貝技術(shù)是提升性能的關(guān)鍵。以 Nginx 為例,它作為一款高性能的 Web 服務(wù)器,廣泛應(yīng)用零拷貝技術(shù)來(lái)提高靜態(tài)文件的傳輸效率。當(dāng)客戶端請(qǐng)求一個(gè)靜態(tài)文件時(shí),傳統(tǒng)的服務(wù)器可能需要先將文件數(shù)據(jù)從磁盤讀取到內(nèi)核緩沖區(qū),再拷貝到用戶空間的應(yīng)用程序緩沖區(qū),最后再發(fā)送到網(wǎng)絡(luò)。而 Nginx 利用sendfile系統(tǒng)調(diào)用實(shí)現(xiàn)零拷貝,數(shù)據(jù)可以直接從磁盤內(nèi)核緩沖區(qū)傳輸?shù)骄W(wǎng)絡(luò) socket 緩沖區(qū),避免了用戶空間的拷貝操作,大大提高了文件傳輸?shù)乃俣?,減少了 CPU 的開銷。
在處理大量并發(fā)請(qǐng)求時(shí),零拷貝技術(shù)的優(yōu)勢(shì)更加明顯。假設(shè)一個(gè)高并發(fā)的文件下載服務(wù)器,每秒要處理數(shù)千個(gè)文件下載請(qǐng)求,如果每個(gè)請(qǐng)求都采用傳統(tǒng)的 I/O 方式,那么 CPU 將忙于數(shù)據(jù)拷貝和上下文切換,很快就會(huì)達(dá)到性能瓶頸。而采用零拷貝技術(shù),CPU 可以將更多的資源用于處理其他任務(wù),服務(wù)器的吞吐量會(huì)大幅提升,能夠輕松應(yīng)對(duì)高并發(fā)的場(chǎng)景,為用戶提供更快的響應(yīng)速度。
6.2文件系統(tǒng):快速操作的秘訣
在文件系統(tǒng)中,零拷貝技術(shù)也發(fā)揮著重要作用。當(dāng)我們進(jìn)行文件復(fù)制操作時(shí),傳統(tǒng)方式是將源文件的數(shù)據(jù)從磁盤讀取到內(nèi)核緩沖區(qū),再拷貝到用戶空間緩沖區(qū),然后寫入目標(biāo)文件,這個(gè)過程涉及多次數(shù)據(jù)拷貝和上下文切換。而利用零拷貝技術(shù),如通過mmap將文件映射到內(nèi)存,數(shù)據(jù)可以直接在內(nèi)核空間中進(jìn)行傳輸和處理,減少了 I/O 開銷。
例如,在一個(gè)大數(shù)據(jù)存儲(chǔ)系統(tǒng)中,經(jīng)常需要進(jìn)行大規(guī)模的數(shù)據(jù)文件遷移操作。如果使用傳統(tǒng)的文件復(fù)制方式,在遷移大量文件時(shí),不僅會(huì)消耗大量的時(shí)間,還會(huì)占用大量的系統(tǒng)資源,導(dǎo)致系統(tǒng)性能下降。而采用零拷貝技術(shù),通過mmap和sendfile等機(jī)制,可以大大減少數(shù)據(jù)拷貝的次數(shù),提高文件遷移的速度,同時(shí)降低系統(tǒng)的負(fù)載,讓系統(tǒng)能夠更高效地運(yùn)行。
6.3多媒體流傳輸:流暢體驗(yàn)的保障
在多媒體流傳輸領(lǐng)域,零拷貝技術(shù)是實(shí)現(xiàn)流暢播放體驗(yàn)的關(guān)鍵。以在線視頻播放為例,視頻數(shù)據(jù)需要從服務(wù)器快速傳輸?shù)娇蛻舳耍⑶乙蟮脱舆t。傳統(tǒng)的數(shù)據(jù)傳輸方式在多次數(shù)據(jù)拷貝過程中會(huì)增加延遲,導(dǎo)致視頻播放卡頓。而利用零拷貝技術(shù),視頻數(shù)據(jù)可以直接從磁盤通過內(nèi)核空間傳輸?shù)骄W(wǎng)絡(luò),減少了數(shù)據(jù)處理的時(shí)間,降低了延遲。
在音頻直播場(chǎng)景中,零拷貝技術(shù)同樣重要。音頻數(shù)據(jù)需要實(shí)時(shí)傳輸?shù)铰牨姷脑O(shè)備上,對(duì)延遲要求極高。通過零拷貝技術(shù),音頻數(shù)據(jù)能夠快速地從服務(wù)器發(fā)送到網(wǎng)絡(luò),保證了音頻直播的實(shí)時(shí)性和流暢性,讓聽眾能夠獲得更好的收聽體驗(yàn)。