JuiceFS調(diào)優(yōu)全記錄:從緩存到FUSE的實(shí)戰(zhàn)指南)
最近在一個(gè) ARM 服務(wù)器集群里做了一次存儲(chǔ)服務(wù)遷移把原本跑在 x86 上的應(yīng)用數(shù)據(jù)層全部換成 JuiceFS 這套方案。遷移本身不算復(fù)雜真正花時(shí)間的是性能調(diào)優(yōu)。ARM 平臺(tái)的單核處理能力和生態(tài)細(xì)節(jié)決定了“跑起來”和“跑得快”之間還有一段不小的距離。這篇文章就把我在 ARM 上優(yōu)化 JuiceFS 的完整過程、參數(shù)調(diào)整思路和踩坑記錄整理出來給同樣在 ARM 環(huán)境里做數(shù)據(jù)服務(wù)的人一個(gè)參考。JuiceFS 的本質(zhì)是“客戶端文件系統(tǒng) 元數(shù)據(jù)服務(wù) 對(duì)象存儲(chǔ)”一個(gè) mount 點(diǎn)上接了 Redis 這類元數(shù)據(jù)存儲(chǔ)后面掛 S3、OOS 或本地對(duì)象存儲(chǔ)。數(shù)據(jù)不落地時(shí)單機(jī)應(yīng)用看到的就是一個(gè)本地目錄所有緩存和 I/O 路徑都在用戶態(tài)處理。正因?yàn)檫@種架構(gòu)ARM 上的優(yōu)化重點(diǎn)和傳統(tǒng)文件系統(tǒng)完全不同不是光調(diào)磁盤陣列而是要把 FUSE 鏈路、緩存命中、并發(fā)連接和 Redis 元數(shù)據(jù)吞吐這幾條線全部理順。這篇文章適合做私有化部署、ARM 邊緣計(jì)算集群或者正在把存儲(chǔ)服務(wù)從 x86 遷移到 ARM 的運(yùn)維和開發(fā)者。1. 先理思路ARM 上調(diào) JuiceFS 到底在調(diào)什么1.1 先把 JuiceFS 的性能模型拆清楚任何性能問題第一步不是調(diào)參數(shù)而是搞清楚瓶頸到底在哪一段。JuiceFS 的一次讀請(qǐng)求會(huì)經(jīng)過至少三層應(yīng)用調(diào)用 FUSE 進(jìn)入內(nèi)核內(nèi)核把請(qǐng)求轉(zhuǎn)給用戶態(tài)的 JuiceFS 進(jìn)程JuiceFS 先查本地緩存緩存沒有命中再去對(duì)象存儲(chǔ)拿數(shù)據(jù)同時(shí)還要和 Redis 確認(rèn)元數(shù)據(jù)。這個(gè)鏈路里任何一段抖動(dòng)最終都會(huì)體現(xiàn)為應(yīng)用里的延遲或吞吐下降。我在 ARM 環(huán)境上遇到的第一個(gè)坑是拿 x86 上的默認(rèn)配置直接跑結(jié)果順序讀可以到很高但小文件并發(fā)一上來就卡。原因是默認(rèn)配置里很多參數(shù)偏保守而且 ARM 上 FUSE 線程和網(wǎng)絡(luò)中斷的協(xié)同方式比 x86 更敏感。所以優(yōu)化前我先把 JuiceFS 拆成三個(gè)子問題元數(shù)據(jù)處理、數(shù)據(jù)緩存、對(duì)象存儲(chǔ)往返。每次只動(dòng)一個(gè)環(huán)節(jié)不要眉毛胡子一把抓。1.2 ARM 平臺(tái)引入的額外變量ARM 服務(wù)器和 x86 的性能差異不是只體現(xiàn)在跑分上有幾個(gè)點(diǎn)會(huì)影響 JuiceFS 的配置選型。一是單核性能。JuiceFS 的元數(shù)據(jù)操作里有很多串行處理邏輯比如 Redis 的 Lua 腳本執(zhí)行、目錄項(xiàng)更新這些操作如果單核跑不動(dòng)光加核數(shù)解決不了問題。二是內(nèi)存帶寬。JuiceFS 在緩存讀寫的時(shí)候會(huì)做大量內(nèi)存拷貝內(nèi)存帶寬低的 ARM 方案上大緩存可能反而讓 CPU 長時(shí)間排隊(duì)。三是驅(qū)動(dòng)生態(tài)。很多 ARM 板卡或云實(shí)例的網(wǎng)絡(luò)驅(qū)動(dòng)、NVMe 驅(qū)動(dòng)在中斷處理和多隊(duì)列配置上不如 x86 成熟網(wǎng)卡中斷集中到某幾個(gè) CPU 上沒有自動(dòng)均衡并發(fā)高時(shí)會(huì)出現(xiàn)“某個(gè)核被打滿其他核閑著”的情況。還有一個(gè)最容易忽略的盡量不要用非原生翻譯層跑 JuiceFS。如果為了省事直接在 ARM 機(jī)器上跑 x86 版本的二進(jìn)制CPU 要花額外時(shí)間做指令翻譯元數(shù)據(jù)操作延遲直接翻倍性能再調(diào)也是白調(diào)。所以第一步就是確保安裝包和依賴庫全部是 ARM 原生版本。1.3 優(yōu)化前先做減法很多人在 ARM 上遇到性能問題就立刻加緩存把緩存目錄調(diào)大把并發(fā)數(shù)調(diào)高。這個(gè)思路容易走偏。先做減法把那些明顯不對(duì)勁的配置砍掉再看真實(shí)瓶頸。我自己的排查順序是這樣的先看 mount 后 FUSE 層面是否正常用juicefs stats觀察掛載點(diǎn)的吞吐和延遲曲線再看 Redis 的元數(shù)據(jù)耗時(shí)如果 Redis 的延遲都很高后面對(duì)象存儲(chǔ)再快也白搭最后才輪到對(duì)象存儲(chǔ)桶的區(qū)域、鏈接數(shù)和超時(shí)設(shè)置。這個(gè)順序能幫你快速確認(rèn)是哪一段在拖后腿。如果你連基線數(shù)據(jù)都沒有上來一頓調(diào)參后面很難判斷哪一步真正起了作用。2. 核心參數(shù)與優(yōu)化細(xì)節(jié)解析2.1 數(shù)據(jù)緩存優(yōu)先級(jí)最高的第一塊JuiceFS 的數(shù)據(jù)緩存決定了大部分讀請(qǐng)求能不能“落地”。命中緩存時(shí)走的是本地磁盤或內(nèi)存沒命中時(shí)要經(jīng)過一次完整的對(duì)象存儲(chǔ)拉取延遲完全不是一個(gè)量級(jí)。所以 ARM 上優(yōu)化 JuiceFS我建議先把數(shù)據(jù)緩存策略定下來。緩存相關(guān)的三個(gè)核心參數(shù)是緩存目錄、緩存容量和最小可用空間比。緩存目錄最好不要和系統(tǒng)盤放一起因?yàn)?JuiceFS 的緩存寫入會(huì)持續(xù)產(chǎn)生小 IO系統(tǒng)盤如果本身要跑數(shù)據(jù)庫相互干擾會(huì)很明顯。有條件的話用獨(dú)立 SSD 做緩存盤或者用內(nèi)存盤來放小文件緩存。內(nèi)存盤這里要單獨(dú)提醒一下如果服務(wù)器內(nèi)存充足偶爾的業(yè)務(wù)高峰又比較集中可以把--cache-dir指到/dev/shm下的子目錄這樣小文件的隨機(jī)讀延遲能做到微秒級(jí)。但緩存重啟就丟而且內(nèi)存占用是動(dòng)態(tài)的如果內(nèi)存本身吃緊或者容器里限制了內(nèi)存上限就不要這么干否則很容易觸發(fā) OOM。這個(gè)方案適合“讀多、且緩存允許重建”的場(chǎng)景。緩存容量也不是越大越好。ARM 服務(wù)器的 CPU 在遍歷大緩存索引時(shí)也會(huì)產(chǎn)生開銷緩存目錄里的文件數(shù)量越多索引遍歷越費(fèi) CPU。我在一臺(tái) 64G 內(nèi)存的 ARM 設(shè)備上試過把緩存設(shè)到 48G結(jié)果高頻小讀的場(chǎng)景吞吐反而比 32G 緩存時(shí)下降。后來用監(jiān)控看到 CPU 的 cache 掃描占了一大截。所以緩存大小的選擇應(yīng)該是“夠用、命中率高”不是“塞滿整塊盤”。2.2 元數(shù)據(jù)緩存讓 ls 和 stat 不再慢JuiceFS 的元數(shù)據(jù)默認(rèn)放在 Redis 等外部存儲(chǔ)里每次ls、stat或find都要走一次網(wǎng)絡(luò)請(qǐng)求。對(duì)于 ARM 設(shè)備上的小集群網(wǎng)絡(luò)往返雖然只有幾百微秒但積少成多目錄一深、文件一多體感就是“一執(zhí)行 ls 就卡”。元數(shù)據(jù)緩存參數(shù)主要控制三個(gè)層面的緩存時(shí)間文件屬性緩存、目錄項(xiàng)緩存、目錄條目緩存。這三個(gè)參數(shù)的單位是秒含義比較簡單但調(diào)優(yōu)要理解背后的取舍。比如文件屬性緩存如果設(shè)成 5 秒那 JuiceFS 在這 5 秒內(nèi)不會(huì)再向 Redis 確認(rèn)文件修改時(shí)間和大小如果同一文件被多臺(tái)機(jī)器同時(shí)寫另一臺(tái)機(jī)器可能讀不到最新屬性。如果業(yè)務(wù)里沒有大規(guī)模并發(fā)寫同一份文件把這三個(gè)值適當(dāng)調(diào)高是非常劃算的。我在 ARM 集群里的經(jīng)驗(yàn)是文件屬性緩存開 3 到 5 秒目錄項(xiàng)緩存開 3 秒左右目錄條目緩存可以稍微大一點(diǎn)比如 5 秒。這樣在處理軟件包解壓、源代碼編譯這類元數(shù)據(jù)密集型操作時(shí)性能提升非常明顯。但如果是多租戶共享同一個(gè)文件系統(tǒng)并且對(duì)一致性要求很高這三個(gè)值最好不要超過 1 秒否則會(huì)出問題。2.3 并發(fā)與連接別把所有壓力壓在一兩個(gè)線程上JuiceFS 的上傳和下載并發(fā)通過連接池參數(shù)控制比如--max-uploads、--max-connections以及每個(gè)對(duì)象存儲(chǔ)連接的超時(shí)重試。默認(rèn)值通常偏保守ARM 在 CPU 核數(shù)不少但單核性能有限的情況下更需要多個(gè)線程分擔(dān)不能只靠一兩個(gè)線程把請(qǐng)求打完。但并發(fā)也不是想調(diào)多大就調(diào)多大。對(duì)象存儲(chǔ)服務(wù)端和本機(jī)網(wǎng)卡都有上限如果并發(fā)數(shù)超過服務(wù)端隊(duì)列處理能力超時(shí)和重試會(huì)迅速增加反而拉低吞吐。我一般會(huì)把--max-uploads從默認(rèn)的小值逐漸往上加每次加 64 或 128觀察延遲和錯(cuò)誤率的拐點(diǎn)。還要注意上傳線程如果設(shè)置得過多CPU 上下文切換會(huì)明顯增加在單核性能較弱的 ARM 設(shè)備上尤其明顯。這里可以做一個(gè)簡單的表格來歸納并發(fā)相關(guān)的參數(shù)參數(shù)作用調(diào)整方向注意點(diǎn)max-uploads上傳對(duì)象存儲(chǔ)的并發(fā)線程數(shù)從低到高逐步加過高會(huì)增加 CPU 切換注意超時(shí)率max-connections每個(gè)對(duì)象桶的連接池上限配合并發(fā)上調(diào)受限于服務(wù)端連接數(shù)限制io-retries網(wǎng)絡(luò)異常后的重試次數(shù)網(wǎng)絡(luò)差的場(chǎng)景加大重試太多會(huì)放大延遲buffer-size讀寫 buffer 大小順序大文件可提高過大占用內(nèi)存影響緩存空間buffer-size 這里要特別提一下它表示單個(gè) FUSE 請(qǐng)求在用戶態(tài)分配的內(nèi)存 buffer 大小。順序大文件讀的時(shí)候小 buffer 會(huì)導(dǎo)致數(shù)據(jù)分多次往返吞吐上不去但把 buffer 調(diào)得很大又會(huì)占用內(nèi)存。ARM 服務(wù)端如果內(nèi)存緊張容易因?yàn)榇?buffer 加緩存把內(nèi)存打滿。2.4 FUSE 層與內(nèi)核參數(shù)用戶態(tài)和內(nèi)核態(tài)之間的橋梁FUSE 是 JuiceFS 性能繞不開的一環(huán)。應(yīng)用發(fā)起的讀寫在經(jīng)過 VFS 之后會(huì)由內(nèi)核通過 FUSE 協(xié)議傳給用戶態(tài)進(jìn)程這個(gè)傳遞過程是典型的 handler 模式每一步都要經(jīng)過內(nèi)核到用戶態(tài)的上下文切換。ARM 上這個(gè)切換成本比 x86 更高所以優(yōu)化 FUSE 層對(duì)整體性能特別關(guān)鍵。JuiceFS 支持通過--fuse-options傳一些 FUSE 掛載選項(xiàng)比如noatime這個(gè)可以避免每次讀取都更新訪問時(shí)間省掉一次元數(shù)據(jù)寫入。如果不需要開機(jī)自動(dòng)掛載和內(nèi)核客戶端的某些通知能力用noatime幾乎是無損優(yōu)化。還有max_read調(diào)整單次可讀大小適合順序讀場(chǎng)景。但要注意max_read不是越大越好kernel 和 FUSE 守護(hù)進(jìn)程之間的 buffer 分配會(huì)跟隨這個(gè)值走ARM 上數(shù)據(jù)拷貝性能有限過大的單次讀反而會(huì)讓 CPU 陷入長時(shí)間 memcpy。FUSE 線程數(shù)也是一個(gè)關(guān)鍵項(xiàng)。JuiceFS 的掛載參數(shù)里有設(shè)置 FUSE 處理線程數(shù)量的選項(xiàng)像--fuse-threads就表示用于處理 FUSE 請(qǐng)求的IO處理線程數(shù)。默認(rèn)情況下線程數(shù)可能較小一旦并發(fā)文件訪問變多請(qǐng)求就會(huì)排隊(duì)。建議根據(jù) CPU 核數(shù)調(diào)整一般從 4 或 8 開始觀察延遲分布。如果看到“讀延遲尾部特別長”通常就是 FUSE 線程不夠或緩存命中率太低。另外系統(tǒng)層面還可以調(diào)整/sys/fs/fuse/max_background這個(gè)參數(shù)控制內(nèi)核 FUSE 在后臺(tái)排隊(duì)處理的最大請(qǐng)求數(shù)。調(diào)大以后可以緩解突發(fā)流量下的請(qǐng)求堆積問題。但這是全局 sysfs 參數(shù)會(huì)影響所有 FUSE 文件系統(tǒng)最好在專用掛載節(jié)點(diǎn)上改。2.5 元數(shù)據(jù)后端Redis 側(cè)的關(guān)鍵調(diào)整JuiceFS 的元數(shù)據(jù)操作會(huì)轉(zhuǎn)換成一堆 Redis 命令配合 Lua 腳本執(zhí)行。Redis 本身的性能直接影響文件系統(tǒng)元數(shù)據(jù)操作的延遲和吞吐。ARM 上自建 Redis 時(shí)先把三個(gè)地方檢查一下內(nèi)存是否足夠、CPU 是否綁核、持久化策略是否合理。JuiceFS 對(duì)元數(shù)據(jù)一致性要求高一般會(huì)開啟 Redis AOF 持久化但如果 AOF 的刷盤策略設(shè)置成每次寫入都always刷盤寫延遲會(huì)非常難看。很多部署為了性能用everysec這時(shí)候極端情況下可能丟 1 秒內(nèi)數(shù)據(jù)需要業(yè)務(wù)側(cè)接受。如果沒有特別嚴(yán)格的元數(shù)據(jù)恢復(fù)要求可以考慮只保留 AOF 的everysec或者用 Redis 集群加足夠副本的方式保證可靠性而不是每次都同步刷盤。連接數(shù)方面JuiceFS 會(huì)維護(hù)到 Redis 的連接池連接池太小會(huì)導(dǎo)致元數(shù)據(jù)操作互相等待。我一般會(huì)先在 Redis 上用redis-cli info看當(dāng)前連接數(shù)和拒絕數(shù)如果rejected_connections明顯增長說明 Redis 的maxclients或 JuiceFS 連接池上限已經(jīng)逼近邊界。在 ARM 單機(jī)部署多個(gè) JuiceFS 掛載點(diǎn)時(shí)要特別留意每個(gè)掛載點(diǎn)都有獨(dú)立的連接池連接數(shù)會(huì)成倍上漲。Redis 6.0 以上提供了多線程 IO 能力可以在加載大 key 或高并發(fā)小命令時(shí)降低主線程壓力。如果 ARM 機(jī)核心數(shù)較多而 Redis 瓶頸出現(xiàn)在網(wǎng)絡(luò)讀取和命令解析上可以適當(dāng)調(diào)整 Redis 的 IO 線程數(shù)量。但元數(shù)據(jù)命令本身的執(zhí)行仍然在主線程所以增加 IO 線程只對(duì)吞吐有幫助對(duì)單個(gè)高耗時(shí)命令的延遲沒有明顯幫助。3. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 先跑通基線測(cè)試拿到一臺(tái)新的 ARM 服務(wù)器后我建議先做一輪基線測(cè)試把當(dāng)前硬件和軟件組合的原始性能記錄下來。沒有基線后續(xù)調(diào)優(yōu)就沒有參照物。第一步用系統(tǒng)盤測(cè)一下裸盤的順序讀和隨機(jī)讀能力這決定緩存盤的選型和預(yù)期上限。第二步直接掛載一個(gè)默認(rèn)參數(shù)的 JuiceFS用 fio 跑一輪標(biāo)準(zhǔn)負(fù)載。以下命令可以快速測(cè)順序讀fio --nameseqread --rwread --bs1M --size2G --iodepth32 \ --ioenginelibaio --direct1 --numjobs4 \ --directory/mnt/jfs --group_reporting再跑一下小文件并發(fā)讀fio --namerandread --rwrandread --bs4k --size512M --iodepth16 \ --ioenginelibaio --direct1 --numjobs8 \ --directory/mnt/jfs --group_reporting這里要注意--direct1默認(rèn)是繞過頁緩存的但 JuiceFS 本身已經(jīng)做了用戶態(tài)緩存所以 fio 結(jié)果反映的不是本地磁盤能力而是整個(gè) JuiceFS 鏈路的實(shí)際表現(xiàn)。記錄下這兩個(gè)吞吐值再順手執(zhí)行幾十次ls -l記錄元數(shù)據(jù)操作耗時(shí)作為后續(xù)對(duì)比的基準(zhǔn)。3.2 ARM 原生編譯與部署如果用官方發(fā)布包直接選擇linux_arm64版本不要在 x86 版本上碰運(yùn)氣。如果需要自己編譯關(guān)鍵點(diǎn)是設(shè)置好 Go 的交叉編譯環(huán)境變量同時(shí)確認(rèn)依賴庫是否有 ARM 版本。JuiceFS 客戶端主要由 Go 編寫編譯時(shí)需要注意 CGO 依賴。例如如果元數(shù)據(jù)后端使用 SQLite就需要確保編譯器能找到對(duì)應(yīng)的 ARM 架構(gòu) C 庫。常見的編譯命令形如GOOSlinux GOARCHarm64 CGO_ENABLED1 \ CCaarch64-linux-gnu-gcc \ make如果沒有特殊依賴也可以CGO_ENABLED0編譯靜態(tài)二進(jìn)制。這樣部署更簡單不依賴目標(biāo)機(jī)器的動(dòng)態(tài)鏈接庫但某些依賴 CGO 的元數(shù)據(jù)存儲(chǔ)后端可能無法工作。所以要看一下自己的后端類型再?zèng)Q定。二進(jìn)制文件拷貝到 ARM 機(jī)器后用file命令確認(rèn)架構(gòu)標(biāo)志file juicefs輸出里出現(xiàn)ARM aarch64就說明是原生二進(jìn)制。如果是容器部署鏡像基底建議直接用arm64v8系列不要拉默認(rèn)的 amd64 鏡像再用模擬層運(yùn)行。3.3 一套可落地的掛載配置模板結(jié)合前面的思路我在一個(gè)中型 ARM 集群上用下來效果不錯(cuò)的掛載參數(shù)可以作為一個(gè)起點(diǎn)模板。注意這些都是示例值部署前請(qǐng)按自己的場(chǎng)景驗(yàn)證。juicefs mount -d \ --meta redis://user:password10.0.0.5:6379/1 \ --bucket s3://my-bucket \ --cache-dir /data/jfscache \ --cache-size 20480 \ --free-space-ratio 0.1 \ --attr-cache 3 \ --entry-cache 3 \ --dir-entry-cache 5 \ --buffer-size 256 \ --max-uploads 128 \ --max-connections 64 \ --fuse-options noatime,big_writes,max_read131072 \ --fuse-threads 8 \ --writeback \ /mnt/jfs這個(gè)模板的思路是確保本地緩存足夠大優(yōu)先提高命中率元數(shù)據(jù)緩存適當(dāng)延長減少 Redis 往返FUSE 線程調(diào)到 8解決并發(fā)排隊(duì)開啟 writeback 讓寫操作先落本地緩存再異步上傳應(yīng)對(duì) ARM 節(jié)點(diǎn)上傳帶寬不足的問題。--writeback要單獨(dú)說明它在提升寫入吞吐的同時(shí)也帶來了數(shù)據(jù)安全風(fēng)險(xiǎn)。開啟后應(yīng)用認(rèn)為寫入已經(jīng)完成但數(shù)據(jù)可能還在本地緩存中未上傳到對(duì)象存儲(chǔ)。一旦節(jié)點(diǎn)電源故障未上傳的寫入會(huì)丟失。如果跑的是可重建的臨時(shí)數(shù)據(jù)這個(gè)選項(xiàng)很劃算如果跑財(cái)務(wù)數(shù)據(jù)或強(qiáng)一致業(yè)務(wù)就要謹(jǐn)慎最好在應(yīng)用層做雙寫或事后校驗(yàn)。3.4 性能觀測(cè)與驗(yàn)證參數(shù)改完后用 JuiceFS 自帶工具觀測(cè)效果不要只看應(yīng)用層的單一指標(biāo)。juicefs stats /mnt/jfs可以實(shí)時(shí)查看掛載點(diǎn)的 IOPS、吞吐、緩存命中率和元數(shù)據(jù)操作數(shù)。這里重點(diǎn)看cache行命中率到 95% 以上后讀性能基本就穩(wěn)了。juicefs profile /mnt/jfs可以看到更細(xì)的延遲分布比如元數(shù)據(jù)操作和對(duì)象存儲(chǔ)操作分別占多少耗時(shí)。我在調(diào)優(yōu)時(shí)發(fā)現(xiàn)文件的open操作有時(shí)會(huì)包含一次到 Redis 的屬性檢查如果目錄層級(jí)特別深這個(gè)檢查會(huì)被多次觸發(fā)。定位到具體操作以后再去決定是調(diào)整元數(shù)據(jù)緩存還是優(yōu)化目錄結(jié)構(gòu)而不是漫無目的地加并發(fā)。一輪調(diào)優(yōu)后重新跑一遍 3.1 的 fio 測(cè)試把結(jié)果和基線對(duì)比。我建議至少給每個(gè)配置組合記錄五組數(shù)據(jù)因?yàn)閷?duì)象存儲(chǔ)的延遲抖動(dòng)比較大單次結(jié)果很容易被異常值帶偏。3.5 按不同業(yè)務(wù)場(chǎng)景再微調(diào)不同讀寫模式下ARM 上 JuiceFS 的優(yōu)化側(cè)重點(diǎn)差別很大。我總結(jié)了一張常用調(diào)整表業(yè)務(wù)場(chǎng)景核心矛盾優(yōu)先調(diào)整項(xiàng)不建議高并發(fā)隨機(jī)讀緩存命中率緩存目錄、attr/entry-cache盲目調(diào)大 buffer-size順序大文件讀單連接吞吐buffer-size、max_read、prefetch過多提升 numjobs大量小文件寫上傳線程和對(duì)象存儲(chǔ)往返writeback、max-uploads開過大 FUSE 線程源數(shù)據(jù)編譯目錄元數(shù)據(jù)往返entry-cache、dir-entry-cache調(diào)低緩存時(shí)間多終端共享目錄一致性和并發(fā)控制緩存時(shí)間、調(diào)大連接池長時(shí)間 attr-cache小文件寫密集場(chǎng)景下ARM 的 CPU 大多耗在 FUSE 到對(duì)象存儲(chǔ)的協(xié)議轉(zhuǎn)換上開啟 writeback 后性能提升非常直觀但一定要監(jiān)控本地緩存盤的占用和上傳積壓量。如果積壓數(shù)據(jù)一直漲說明上傳能力跟不上寫入速度這時(shí)候加并發(fā)線程或換更高帶寬實(shí)例才是正解。4. 常見問題與排查技巧實(shí)錄4.1 高并發(fā)讀延遲抖動(dòng)明顯現(xiàn)象是 fio 并發(fā)從 4 加到 16 以后平均延遲還行但 P99 延遲突然從幾十毫秒漲到幾百毫秒。第一反應(yīng)是緩存不夠但檢查后發(fā)現(xiàn)緩存命中率已經(jīng)很高。實(shí)際原因是 FUSE 線程數(shù)和內(nèi)核后臺(tái)隊(duì)列限制。應(yīng)用層請(qǐng)求在 FUSE 隊(duì)列里排隊(duì)默認(rèn)的最大后臺(tái)請(qǐng)求數(shù)不夠在突發(fā)流量時(shí)請(qǐng)求堆積。我當(dāng)時(shí)的處理是把--fuse-threads從 4 提到 8同時(shí)調(diào)整/sys/fs/fuse/max_background從默認(rèn)值往上翻了幾倍P99 立刻回落。這兩個(gè)參數(shù)一個(gè)管用戶態(tài)處理線程一個(gè)管內(nèi)核態(tài)排隊(duì)深度缺一不可。4.2 元數(shù)據(jù)操作卡頓表現(xiàn)是掛載點(diǎn)執(zhí)行l(wèi)s還好但目錄一多或者執(zhí)行編譯操作時(shí)每個(gè)任務(wù)都像被卡住一樣。先看 Redis 慢日志確認(rèn)是不是有大量耗時(shí)命令。JuiceFS 的元數(shù)據(jù)結(jié)構(gòu)在 Redis 里的存儲(chǔ)方式比較緊湊但目錄項(xiàng)緩存過期時(shí)會(huì)出現(xiàn)批量讀單核弱的 ARM 機(jī)器容易被這一波讀卡住。我當(dāng)時(shí)把--dir-entry-cache從默認(rèn)的 1 秒調(diào)到 5 秒后元數(shù)據(jù)吞吐明顯上來了。還要留意 Redis 實(shí)例是不是和 JuiceFS 搶 CPU如果服務(wù)和 Redis 在同一臺(tái) ARM 機(jī)器上運(yùn)行建議給 Redis 核心綁定到不同 CPU避免元數(shù)據(jù)操作和文件數(shù)據(jù)讀寫相互搶占。4.3 CPU 打滿但吞吐上不去這是一類典型的 ARM 問題。多核 CPU 中有一個(gè)或幾個(gè)核跑到 100%其他核比較空閑整體吞吐卻上不去。用mpstat或perf top確認(rèn)熱點(diǎn)在哪里。如果看到大量時(shí)間花在內(nèi)核中斷處理上多半是網(wǎng)卡多隊(duì)列沒有正確鋪開。檢查網(wǎng)絡(luò)設(shè)備的隊(duì)列數(shù)把 IRQ 中斷均勻分配到各個(gè) CPU 上或者在虛擬化平臺(tái)確認(rèn)網(wǎng)卡驅(qū)動(dòng)支持多隊(duì)列。ARM 平臺(tái)的性能事件和 x86 有些差異看 CPU 利用率不要只看整體平均數(shù)要看單核分布。還有一種情況是 FUSE 觸發(fā)大量上下文切換用戶態(tài)線程和內(nèi)核態(tài)之間的切換頻繁CPU 空轉(zhuǎn)在調(diào)度上。這個(gè)可以把--fuse-threads調(diào)大一點(diǎn)讓并發(fā)請(qǐng)求分流到不同線程減少單個(gè)線程上的切換壓力。4.4 緩存目錄越用越慢剛開始掛載時(shí)性能不錯(cuò)運(yùn)行幾天后隨機(jī)讀性能下降。檢查后發(fā)現(xiàn)緩存目錄所在的磁盤碎片嚴(yán)重或者用的還是讀取性能偏弱的擴(kuò)展卡方案。JuiceFS 緩存的數(shù)據(jù)文件比較碎大量小文件并發(fā)讀時(shí)磁盤尋道成為瓶頸。建議緩存盤格式化時(shí)用 XFS 并調(diào)整 inode 分配或者直接換 NVMe。如果是 ARM 開發(fā)板一類的設(shè)備很多“高速緩存盤”實(shí)際上是外接存儲(chǔ)芯片順序讀很快但隨機(jī)讀一般這種盤不適合做混合負(fù)載緩存。有一條偷懶的辦法把緩存目錄放到內(nèi)存盤性能瞬間拉滿但千萬別忘了數(shù)據(jù)會(huì)在重啟時(shí)清空。我后來在緩存盤前面再加了一層內(nèi)存緩存用小文件的冷熱分層來緩解磁盤壓力效果比單純擴(kuò)大緩存目錄明顯得多。4.5 每次重啟都要重新緩存應(yīng)用每次部署或重啟后JuiceFS 的緩存都會(huì)被清掉導(dǎo)致重啟后一兩小時(shí)內(nèi)性能處于低谷。可以加一個(gè)緩存預(yù)熱動(dòng)作。對(duì)于已知的常用路徑在系統(tǒng)啟動(dòng)后主動(dòng)跑一輪順序讀find /mnt/jfs/data/hot -type f -exec dd if{} of/dev/null bs1M count8 2/dev/null \;這種方式會(huì)把熱點(diǎn)文件的前幾兆數(shù)據(jù)拉進(jìn)緩存讓業(yè)務(wù)啟動(dòng)后的首次訪問不至于全部穿透到對(duì)象存儲(chǔ)。缺點(diǎn)是預(yù)熱本身也會(huì)消耗對(duì)象存儲(chǔ)帶寬量要控制好。也可以用業(yè)務(wù)自身的啟動(dòng)流程來帶比如應(yīng)用啟動(dòng)時(shí)做幾輪冷啟動(dòng)ls間接把目錄項(xiàng)和文件屬性緩存填上。4.6 異常斷點(diǎn)后緩存數(shù)據(jù)不一致開啟--writeback后斷電或強(qiáng)制重啟可能導(dǎo)致本地緩存里的寫入數(shù)據(jù)沒有上傳到對(duì)象存儲(chǔ)。重啟后掛載點(diǎn)可能檢測(cè)到緩存不一致出現(xiàn)部分文件內(nèi)容缺失或讀取錯(cuò)誤。排查方式是看日志里有沒有緩存寫回相關(guān)報(bào)錯(cuò)確認(rèn)以后最穩(wěn)妥的恢復(fù)辦法是清空該節(jié)點(diǎn)緩存目錄再重新掛載。不要嘗試保留緩存繼續(xù)用因?yàn)槟男?shù)據(jù)已經(jīng)完整上傳、哪些還在本地很難從外部判斷。我最開始吃了一次虧后來在部署文檔里強(qiáng)制約定開啟 writeback 的節(jié)點(diǎn)必須在業(yè)務(wù)側(cè)保證數(shù)據(jù)可重建。5. 寫在最后的實(shí)操心得ARM 上做 JuiceFS 性能優(yōu)化我的總體感受是參數(shù)調(diào)整的套路和 x86 相似但每一次調(diào)整的收益都比 x86 更“敏感”。因?yàn)槠款i經(jīng)常出現(xiàn)在 CPU 單核能力、中斷均衡和內(nèi)存拷貝上而不是單純的網(wǎng)絡(luò)帶寬或磁盤速度。所以整個(gè)優(yōu)化的順序很重要先保證 ARM 原生二進(jìn)制再確認(rèn)緩存命中率然后調(diào) FUSE 線程和系統(tǒng)隊(duì)列最后才去碰并發(fā)和 buffer 這類資源型參數(shù)。另外一個(gè)小技巧把每次調(diào)參的每一項(xiàng)都記錄成表格并配合當(dāng)時(shí)跑出的吞吐和延遲數(shù)據(jù)。調(diào)參這件事非常依賴現(xiàn)場(chǎng)環(huán)境同一個(gè)配置換一批盤或者換一個(gè)對(duì)象存儲(chǔ)網(wǎng)關(guān)結(jié)論可能就變了。記錄不僅方便回滾也能在后續(xù)新節(jié)點(diǎn)上用更少的時(shí)間找到合適的起點(diǎn)。最后想說JuiceFS 的優(yōu)化沒有一勞永逸的答案。如果你的場(chǎng)景是大量小文件隨機(jī)讀那就把精力放在緩存命中率和元數(shù)據(jù)緩存上如果你的場(chǎng)景是寫日志那就先把 writeback 和上傳并發(fā)弄順。把瓶頸定位準(zhǔn)確再用這臺(tái) ARM 機(jī)器的實(shí)際數(shù)據(jù)來選擇參數(shù)這套思路可以復(fù)用到任何存儲(chǔ)組件上。