在高性能計(jì)算場景中,往往采用全閃存架構(gòu)和內(nèi)核態(tài)并行文件系統(tǒng),以滿足性能要求。隨著數(shù)據(jù)規(guī)模的增加和分布式系統(tǒng)集群規(guī)模的增加,全閃存的高成本和內(nèi)核客戶端的運(yùn)維復(fù)雜性成為主要挑戰(zhàn)。
JuiceFS,是一款全用戶態(tài)的云原生分布式文件系統(tǒng),通過分布式緩存大幅提升 I/O 吞吐量,并使用成本較低的對象存儲來完成數(shù)據(jù)存儲,適用于大規(guī)模 AI 業(yè)務(wù)。
JuiceFS 數(shù)據(jù)讀取流程從客戶端的讀請求開始,然后再經(jīng)過 FUSE 發(fā)送給 JuiceFS 客戶端,通過預(yù)讀緩沖層,接著進(jìn)入緩存層,最終訪問對象存儲。為了提高讀取效率,JuiceFS 在其架構(gòu)設(shè)計(jì)中采用了包括數(shù)據(jù)預(yù)讀、預(yù)取和緩存在內(nèi)的多種策略。
本文將詳細(xì)解析這些策略的工作原理,并分享我們在特定場景下的測試結(jié)果,以便讀者深入理解 JuiceFS 的性能優(yōu)勢及一些相關(guān)的限制,從而更有效地應(yīng)用于各種使用場景。
鑒于文章內(nèi)容的深度和技術(shù)性,需要讀者有一定操作系統(tǒng)知識,建議收藏以便在需要時(shí)進(jìn)行仔細(xì)閱讀。
01 JuiceFS 架構(gòu)簡介
JuiceFS 社區(qū)版架構(gòu)分為客戶端、數(shù)據(jù)存儲和元數(shù)據(jù)三部分。 數(shù)據(jù)訪問支持多種接口,包括 POSIX 、HDFS API、S3 API,還有 Kubernetes CSI,以滿足不同的應(yīng)用場景。在數(shù)據(jù)存儲方面,JuiceFS 支持幾十種對象存儲,包括公共云服務(wù)和自托管解決方案,如 Ceph 和 MinIO。元數(shù)據(jù)引擎支持多種常見的數(shù)據(jù)庫,包括 Redis、TiKV 和 MySQL 等。

企業(yè)版與社區(qū)版的主要區(qū)別在圖片左下角元數(shù)據(jù)引擎和數(shù)據(jù)緩存的處理。企業(yè)版包括一個(gè)自研的分布式元數(shù)據(jù)引擎,并支持分布式緩存,社區(qū)版只支持本地緩存。
02 Linux 中關(guān)于“讀”的幾個(gè)概念
在 Linux 系統(tǒng)中,數(shù)據(jù)讀取主要通過幾種方式實(shí)現(xiàn):
- Buffered I/O:這是標(biāo)準(zhǔn)的文件讀取方式,數(shù)據(jù)會(huì)通過內(nèi)核緩沖區(qū),并且內(nèi)核會(huì)進(jìn)行預(yù)讀操作,以優(yōu)化讀取效率;
- Direct I/O:允許繞過內(nèi)核緩沖區(qū)直接進(jìn)行文件 I/O 操作,以減少數(shù)據(jù)拷貝和內(nèi)存占用,適合大量數(shù)據(jù)傳輸;
- Asynchronous I/O:通常與 Direct I/O 一起使用。它允許應(yīng)用程序在單個(gè)線程中發(fā)出多個(gè) I/O 請求,而不必等待每個(gè)請求完成,從而提高 I/O 并發(fā)性能;
- Memory Map:將文件映射到進(jìn)程的地址空間,可以通過指針直接訪問文件內(nèi)容。通過內(nèi)存映射,應(yīng)用程序可以像訪問普通內(nèi)存一樣訪問映射的文件區(qū)域,由內(nèi)核自動(dòng)處理數(shù)據(jù)的讀取和寫入。
幾種主要的讀取模式及它們對存儲系統(tǒng)帶來的挑戰(zhàn):
- 隨機(jī)讀取,包括隨機(jī)大 I/O 讀和隨機(jī)小 I/O 讀,主要考驗(yàn)存儲系統(tǒng)的延遲和 IOPS。
- 順序讀取,主要考驗(yàn)存儲系統(tǒng)的帶寬。
- 大量小文件讀取,主要考驗(yàn)存儲系統(tǒng)的元數(shù)據(jù)引擎的性能和系統(tǒng)整體的 IOPS 能力。
03 JuiceFS 讀流程原理解析
JuiceFS 采用了文件分塊存儲的策略。 一個(gè)文件首先邏輯上被分割成若干個(gè) chunk,每個(gè) chunk 固定大小為 64MB。每個(gè) chunk 進(jìn)一步被細(xì)分為若干個(gè) 4MB 的 block,block 是對象存儲中的實(shí)際存儲單元,JuiceFS 的設(shè)計(jì)中有不少性能優(yōu)化措施與這個(gè)分塊策略緊密相關(guān)。(進(jìn)一步了解 JuiceFS 存儲模式)。 為了優(yōu)化讀取性能, JuiceFS 采取了預(yù)讀、預(yù)取與緩存等多種優(yōu)化方案。

預(yù)讀 readahead
預(yù)讀(readahead):通過預(yù)測用戶未來的讀請求,提前從對象存儲中加載數(shù)據(jù)到內(nèi)存中,以降低訪問延遲,提高實(shí)際 I/O 并發(fā)。下面這張圖是一張簡化的讀取流程的示意圖。虛線以下代表應(yīng)用層,虛線以上是內(nèi)核層。
當(dāng)用戶進(jìn)程(左下角標(biāo)藍(lán)色的應(yīng)用層) 發(fā)起文件讀寫的系統(tǒng)調(diào)用時(shí),請求首先通過內(nèi)核的 VFS,然后傳遞給內(nèi)核的 FUSE 模塊,經(jīng)過 /dev/fuse 設(shè)備與 JuiceFS 的客戶端進(jìn)程通信。

右下角所示的流程是后續(xù)在 JuiceFS 中進(jìn)行的預(yù)讀優(yōu)化。系統(tǒng)通過引入“session” 跟蹤一系列連續(xù)讀。每個(gè) session 記錄了上一次讀取的偏移量、連續(xù)讀取的長度以及當(dāng)前預(yù)讀窗口大小,這些信息可用于判斷新來的讀請求是否命中這個(gè) session,并自動(dòng)調(diào)整/移動(dòng)預(yù)讀窗口。 通過維護(hù)多個(gè) session,JuiceFS 還能輕松支持高性能的并發(fā)連續(xù)讀。
為了提升連續(xù)讀的性能,在系統(tǒng)設(shè)計(jì)中,我們增加了提升并發(fā)的措施。具體來說,預(yù)讀窗口中的每一個(gè) block (4MB) 都會(huì)啟動(dòng)一個(gè) goroutine 來讀數(shù)據(jù)。 這里需要注意的是,并發(fā)數(shù)會(huì)受限于 buffer-size 參數(shù)。在默認(rèn) 300MB 設(shè)置下,理論最大對象存儲并發(fā)數(shù)為 75(300MB 除以 4MB),這個(gè)設(shè)置在一些高性能場景下是不夠的,用戶需要根據(jù)自己的資源配置和場景需求去調(diào)整這個(gè)參數(shù),下文中我們也對不同參數(shù)進(jìn)行了測試。
以下圖第二行為例,當(dāng)系統(tǒng)接收到連續(xù)的第二個(gè)讀請求時(shí),實(shí)際上會(huì)發(fā)起一個(gè)包含預(yù)讀窗口和讀請求的連續(xù)三個(gè)數(shù)據(jù)塊的請求。按照預(yù)讀的設(shè)置,接下來的兩個(gè)請求都會(huì)直接命中預(yù)讀的 buffer 并被立即返回。

如果第一個(gè)和第二個(gè)請求沒有被預(yù)讀,而是直接訪問對象存儲,延遲會(huì)比較高(通常大于10ms)。而當(dāng)延遲降低在 100 微秒以內(nèi),則說明這個(gè) I/O 請求成功使用了預(yù)讀,即第三個(gè)和第四個(gè)請求,直接命中了內(nèi)存中預(yù)讀的數(shù)據(jù)。
預(yù)取 prefetch
預(yù)取(prefetch):當(dāng)隨機(jī)讀取文件中的一小段數(shù)據(jù)時(shí),我們假設(shè)這段數(shù)據(jù)附近的區(qū)域也可能會(huì)被讀取,因此客戶端會(huì)異步將這一小段數(shù)據(jù)所在的整個(gè) block 下載下來。
但是在有些場景,預(yù)取這個(gè)策略不適用,例如應(yīng)用對大文件進(jìn)行大幅偏移的、稀疏的隨機(jī)讀取,預(yù)取會(huì)訪問到一些不必要的數(shù)據(jù),導(dǎo)致讀放大。因此,用戶如果已經(jīng)深入了解應(yīng)用場景的讀取模式,并確認(rèn)不需要預(yù)取,可以通過 --prefetch=0 禁用該行為。

緩存 cache
在之前的一次分享中,我們的架構(gòu)師高昌健詳細(xì)介紹了 JuiceFS的緩存機(jī)制,或查看緩存文檔。 在這篇文章中,對于緩存的介紹會(huì)以基本概念為主。
頁緩存 page cache
頁緩存(page cache)是 Linux 內(nèi)核提供的機(jī)制。它的核心功能之一就是預(yù)讀(readahead),它通過預(yù)先讀取數(shù)據(jù)到緩存中,確保在實(shí)際請求數(shù)據(jù)時(shí)能夠快速響應(yīng)。
進(jìn)一步,頁緩存(page cache)在特定場景下的應(yīng)用也非常關(guān)鍵,例如在處理隨機(jī)讀操作時(shí),如果用戶能策略性地使用頁緩存,將文件數(shù)據(jù)提前填充至進(jìn)頁緩存,如內(nèi)存空閑的情況下提前完整連續(xù)讀一遍文件,可以顯著提高后續(xù)隨機(jī)讀的性能,從而極大地提升業(yè)務(wù)整體性能。
本地緩存 local cache
JuiceFS 的本地緩存可以利用本地內(nèi)存或本地磁盤保存 block,從而在應(yīng)用訪問這些數(shù)據(jù)時(shí)可以實(shí)現(xiàn)本地命中,降低網(wǎng)絡(luò)時(shí)延并提升性能。我們通常推薦使用高性能 SSD。數(shù)據(jù)緩存的默認(rèn)單元是一個(gè) block,大小為 4MB,該 block 會(huì)在首次從對象存儲中讀取后異步寫入本地緩存。
關(guān)于本地緩存的配置,如 --cache-dir 和 --cache-size 等細(xì)節(jié),企業(yè)版用戶可以查看文檔。
分布式緩存 cache group
分布式緩存是企業(yè)版的一個(gè)重要特性。與本地緩存相比,分布式緩存將多個(gè)節(jié)點(diǎn)的本地緩存聚合成同一個(gè)緩存池,提高緩存的命中率。但由于分布式緩存增加了一次網(wǎng)絡(luò)請求,這導(dǎo)致其在時(shí)延上通常稍高于本地緩存,分布式緩存隨機(jī)讀延遲一般是 1-2ms,而本地緩存隨機(jī)讀延遲一般是 0.2-0.5ms。關(guān)于分布式緩存具體架構(gòu),可以查看官網(wǎng)文檔。
04 FUSE & 對象存儲的性能表現(xiàn)
JuiceFS 的讀請求都要經(jīng)過 FUSE,數(shù)據(jù)要從對象存儲讀取,因此理解 FUSE 和對象存儲的性能表現(xiàn)是理解 JuiceFS 性能表現(xiàn)的基礎(chǔ)。
關(guān)于 FUSE 的性能
我們對 FUSE 性能進(jìn)行了兩組測試。測試場景是當(dāng) I/O 請求到達(dá) FUSE 掛載進(jìn)程后,數(shù)據(jù)被直接填充到內(nèi)存中并立即返回。測試主要評估 FUSE 在不同線程數(shù)量下的總帶寬,單個(gè)線程平均帶寬以及CPU使用情況。硬件方面,測試 1 是 Intel Xeon 架構(gòu),測試 2 則是 AMD EPYC 架構(gòu)。
| Threads | Bandwidth(GiB/s) | Bandwidth per Thread (GiB/s) | CPU Usage(cores) |
|---|---|---|---|
| 1 | 7.95 | 7.95 | 0.9 |
| 2 | 15.4 | 7.7 | 1.8 |
| 3 | 20.9 | 6.9 | 2.7 |
| 4 | 27.6 | 6.9 | 3.6 |
| 6 | 43 | 7.2 | 5.3 |
| 8 | 55 | 6.9 | 7.1 |
| 10 | 69.6 | 6.96 | 8.6 |
| 15 | 90 | 6 | 13.6 |
| 20 | 104 | 5.2 | 18 |
| 25 | 102 | 4.08 | 22.6 |
| 30 | 98.5 | 3.28 | 27.4 |
FUSE 性能測試 1, 基于 Intel Xeon CPU 架構(gòu)
- 在單線程測試中,最大帶寬達(dá)到 7.95GiB/s,同時(shí) CPU 使用量不到一個(gè)核;
- 隨著線程數(shù)增加,帶寬基本實(shí)現(xiàn)線性擴(kuò)展,當(dāng)線程數(shù)增加到 20 時(shí),總帶寬增加到 104 GiB/s;
此處,用戶需要特別注意的是,相同 CPU 架構(gòu)下使用不同硬件型號、不同操作系統(tǒng)測得的 FUSE 帶寬表現(xiàn)都有可能不同。我們使用過多種機(jī)型進(jìn)行測試,在其中一種機(jī)型上測得的最大單線程帶寬僅為 3.9GiB/s。
| Threads | Bandwidth(GiB/s) | Bandwidth per Thread (GiB/s) | CPU Usage(cores) |
|---|---|---|---|
| 1 | 3.5 | 3.5 | 1 |
| 2 | 6.3 | 3.15 | 1.9 |
| 3 | 9.5 | 3.16 | 2.8 |
| 4 | 9.7 | 2.43 | 3.8 |
| 6 | 14.0 | 2.33 | 5.7 |
| 8 | 17.0 | 2.13 | 7.6 |
| 10 | 18.6 | 1.9 | 9.4 |
| 15 | 21 | 1.4 | 13.7 |
FUSE 性能測試 2, 基于 AMD EPYC CPU 架構(gòu)
- 在測試 2 中,帶寬不能線性擴(kuò)展,特別是當(dāng)并發(fā)數(shù)量達(dá)到 10 個(gè)時(shí),每個(gè)并發(fā)的帶寬不足 2GiB/s;
在多并發(fā)情況下,測試 2( EPYC 架構(gòu))帶寬峰值約為 20GiBps;測試 1(Intel Xeon 架構(gòu))表現(xiàn)出更高的性能空間,峰值通常在 CPU 資源被完全占用后出現(xiàn),這時(shí)應(yīng)用進(jìn)程和 FUSE 進(jìn)程的 CPU 都達(dá)到了資源極限。
在實(shí)際應(yīng)用中,由于各個(gè)環(huán)節(jié)的時(shí)間開銷,實(shí)際的 I/O 性能往往會(huì)低于上述測試峰值 3.5GiB/s。例如,在模型加載的場景中,加載 pickle 格式的模型文件,通常單線程帶寬只能達(dá)到 1.5 到 1.8GiB/s。這主要是因?yàn)樽x取 pickle 文件的同時(shí),要進(jìn)行數(shù)據(jù)反序列化,還會(huì)遇到 CPU 單核處理的瓶頸。即使是不經(jīng)過 FUSE,直接從內(nèi)存讀取的情況下,帶寬也最多只能達(dá)到 2.8GiB/s。
關(guān)于對象存儲的性能
我們使用 juicefs objbench 工具進(jìn)行測試,測試涵蓋了單并發(fā)、10 并發(fā)、200 并發(fā)以及 800 并發(fā)的不同負(fù)載。用戶需要注意的是,不同對象存儲的性能差距可能很大。
| 上傳帶寬 upload objects- MiB/s | 下載帶寬 download objects MiB/s | 上傳平均耗時(shí) ms/object | 下載平均耗時(shí) ms/object | |
|---|---|---|---|---|
| 單并發(fā) | 32.89 | 40.46 | 121.63 | 98.85ms |
| 10 并發(fā) | 332.75 | 364.82 | 10.02 | 10.96 |
| 200 并發(fā) | 5590.26 | 3551.65 | 067 | 1.13 |
| 800 并發(fā) | 8270.28 | 4038.41 | 0.48 | 0.99 |
當(dāng)我們增加對象存儲 GET 操作的并發(fā)數(shù)到 200 和 800 后,才能夠達(dá)到非常高的帶寬。這說明直接從對象存儲上讀數(shù)據(jù)時(shí),單并發(fā)帶寬非常有限,提高并發(fā)對整體的帶寬性能至關(guān)重要。
05 連續(xù)讀與隨機(jī)讀測試
為了給大家提供一個(gè)直觀的基準(zhǔn)參考,我們使用 fio 工具測試了 JuiceFS 企業(yè)版在連續(xù)讀取和隨機(jī)讀場景下的性能。
連續(xù)讀
從下圖可以看到 99% 的數(shù)據(jù)都小于 200 微秒。在連續(xù)讀場景下,預(yù)讀窗口總能很好地發(fā)揮作用,因此延遲很低。

同時(shí),我們也能通過加大預(yù)讀窗口,以提高 I/O 并發(fā),從而提升帶寬。當(dāng)我們將 buffer-size 從默認(rèn) 300MiB 調(diào)整為 2GiB 后,讀并發(fā)不再受限,讀帶寬從 674MiB/s 提升到了 1418 MiB/s,此時(shí)達(dá)到單線程 FUSE 的性能峰值,進(jìn)一步提高帶寬需要提高業(yè)務(wù)代碼中 I/O 并發(fā)度。
| buffer-size | 帶寬 |
|---|---|
| 300MiB | 674MiB/s |
| 2GiB | 1418MiB/s |
不同 buffer-size 帶寬性能測試(單線程)
當(dāng)提高業(yè)務(wù)線程數(shù)到 4 線程時(shí),帶寬能達(dá)到 3456MiB/s;16 線程時(shí),帶寬達(dá)到了 5457MiB/s,此時(shí)網(wǎng)絡(luò)帶寬已經(jīng)達(dá)到飽和。
| buffer-size | 帶寬 |
|---|---|
| 1線程 | 1418MiB/s |
| 4線程 | 3456MiB/s |
| 16線程 | 5457MiB/s |
不同線程數(shù)量下帶寬性能測試 (buffer-size:2GiB)
隨機(jī)讀
對于小 I/O 隨機(jī)讀,其性能主要由延遲和 IOPS 決定,由于總 IOPS 能夠通過增加節(jié)點(diǎn)線性擴(kuò)展,所以我們先關(guān)注單節(jié)點(diǎn)上的延遲數(shù)據(jù)。
“FUSE 數(shù)據(jù)帶寬”是指通過 FUSE 層傳輸?shù)臄?shù)據(jù)量,代表用戶應(yīng)用實(shí)際可觀察和操作的數(shù)據(jù)傳輸速率;“底層數(shù)據(jù)帶寬”則指的存儲系統(tǒng)本身在物理層或操作系統(tǒng)層面處理數(shù)據(jù)的帶寬。
從表格中可以看到與穿透到對象存儲相比,命中本地緩存和分布式緩存的情況下,延遲都更低,當(dāng)我們需要優(yōu)化隨機(jī)讀延遲的時(shí)候就需要考慮提高數(shù)據(jù)的緩存命中率。同時(shí),我們也能看到使用異步 I/O 接口及提高線程數(shù)可以大大提高 IOPS。

不同于小 I/O 的場景,大 I/O 隨機(jī)讀場景還要注意讀放大問題。如下表所示,底層數(shù)據(jù)帶寬高于 FUSE 數(shù)據(jù)帶寬,這是因?yàn)轭A(yù)讀的作用,實(shí)際的數(shù)據(jù)請求會(huì)比來自于應(yīng)用的數(shù)據(jù)請求多1-3倍,此時(shí)可以嘗試關(guān)閉 prefetch 并調(diào)整最大預(yù)讀窗口來調(diào)優(yōu)。
| 分類 | FUSE 數(shù)據(jù)帶寬 | 底層數(shù)據(jù)帶寬 |
|---|---|---|
| 1MB buffered IO | 92MiB | 290MiB |
| 2MB buffered IO | 155MiB | 435MiB |
| 4MB buffered IO | 181MiB | 575MiB |
| 1MB direct IO | 306MiB | 306MiB |
| 2MB direct IO | 199MiB | 340MiB |
| 4MB direct IO | 245MiB | 735MiB |
JuiceFS(開啟分布式緩存) 大 I/O 隨機(jī)讀測試結(jié)果