JuiceFS 在攜程海量冷數(shù)據(jù)場(chǎng)景下的實(shí)踐
作者|妙成,攜程云原生研發(fā)工程師,主要從事Elasticsearch、JuiceFS的研發(fā)運(yùn)維,關(guān)注分布式數(shù)據(jù)庫(kù)、NoSQL。小峰, 攜程云原生研發(fā)工程師,主要專注于數(shù)據(jù)庫(kù)容器化領(lǐng)域,對(duì)分布式存儲(chǔ)有濃厚興趣。
一、摘要
攜程的冷數(shù)據(jù)規(guī)模在 10PB+,包括備份數(shù)據(jù)、圖片語(yǔ)音訓(xùn)練數(shù)據(jù)和日志數(shù)據(jù)等,存儲(chǔ)方案主要是本地磁盤和GlusterFS。在實(shí)際使用中這些方案遇到了不少痛點(diǎn):
- GlusterFS 在單目錄下文件眾多時(shí),ls命令速度很慢;
- 受疫情期間機(jī)器采購(gòu)周期的制約,無(wú)法靈活地根據(jù)實(shí)際需求彈性擴(kuò)縮容,存儲(chǔ)成本控制困難;
- 磁盤損壞等故障帶來的機(jī)器替換和擴(kuò)縮容操作,使得運(yùn)維成本居高不下。
隨著云計(jì)算技術(shù)的發(fā)展,公有云廠商為混合云客戶提供了海量冷數(shù)據(jù)的廉價(jià)存儲(chǔ)方案,經(jīng)過嚴(yán)謹(jǐn)?shù)某杀居?jì)算,我們發(fā)現(xiàn)使用公有云的對(duì)象存儲(chǔ)可以顯著降低存儲(chǔ)和運(yùn)維成本。為了減少遷移成本,我們一直在尋找后端存儲(chǔ)能支持各類公有云對(duì)象存儲(chǔ)、高性能的文件系統(tǒng),直到JuiceFS 出現(xiàn)在我們的視野中。JuiceFS有以下優(yōu)勢(shì):
- POSIX 接口,對(duì)應(yīng)用無(wú)侵入
- 強(qiáng)一致性,文件修改立刻可見,為同一個(gè) volume 被多臺(tái)機(jī)器掛載的場(chǎng)景提供 了close-to-open 保證
- 支持了主流的公有云對(duì)象存儲(chǔ),支持開源軟件作為元數(shù)據(jù)引擎(Redis、TiKV)等
- 支持云原生,能夠?qū)olume以 CSI 的方式掛載到Pod上
- 社區(qū)活躍,代碼更新快
經(jīng)過大半年的測(cè)試和使用,我們已經(jīng)對(duì)接了數(shù)據(jù)庫(kù)備份和 ElasticSearch 冷數(shù)據(jù)存儲(chǔ),將2PB+的數(shù)據(jù)遷移到了JuiceFS,預(yù)計(jì)后續(xù)還會(huì)有10PB+的數(shù)據(jù)接入。目前JuiceFS系統(tǒng)穩(wěn)定,在降低運(yùn)維成本和存儲(chǔ)成本方面取得了良好的效果。本文將對(duì)JuiceFS原理以及我們?cè)谑褂弥兴龅降膯栴}和采取的優(yōu)化方案進(jìn)行簡(jiǎn)單介紹。
二、JuiceFS 架構(gòu)與POC 測(cè)試
2.1 架構(gòu)簡(jiǎn)介
JuiceFS 將元數(shù)據(jù)信息和真實(shí)數(shù)據(jù)塊分開管理,通過 FUSE 實(shí)現(xiàn) POSIX 接口,允許用戶像本地文件系統(tǒng)一樣使用。用戶將文件寫入JuiceFS掛載的目錄后,文件數(shù)據(jù)會(huì)存儲(chǔ)到對(duì)象存儲(chǔ),相應(yīng)的元數(shù)據(jù)(文件名、文件大小、權(quán)限組、創(chuàng)建修改時(shí)間和目錄結(jié)構(gòu)等)會(huì)存到元數(shù)據(jù)引擎中。在該架構(gòu)下,ls、數(shù)據(jù)刪除等操作只是對(duì)元數(shù)據(jù)引擎的操作,不受到對(duì)象存儲(chǔ)的速度限制,性能上會(huì)有較好的保證。

2.2 元數(shù)據(jù)引擎選型與測(cè)試
JuiceFS 的元數(shù)據(jù)引擎有很多選擇,包括開源的Redis、TiKV以及官方提供的閉源的企業(yè)版元數(shù)據(jù)引擎。考慮到攜程的數(shù)據(jù)規(guī)模較大并且后續(xù)會(huì)有更多的數(shù)據(jù)接入,元數(shù)據(jù)引擎需要能夠支持TB 級(jí)元數(shù)據(jù)的存儲(chǔ)并且能橫向擴(kuò)容。因此TiKV和官方的企業(yè)版元數(shù)據(jù)引擎成了我們的備選方案。
為了驗(yàn)證TiKV的性能,我們使用 go-ycsb做了一些性能測(cè)試。
機(jī)器 | CPU | Memory | Storage | Network |
Node1 | 2 Socket / 20 Core / 40 Thread | 128G | 1.9T SSD | bond0 25G |
Node2 | 2 Socket / 20 Core / 40 Thread | 128G | 960G SSD | bond0 25G |
Node3 | 2 Socket / 20 Core / 40 Thread | 128G | 1.2T SSD | bond0 25G |
測(cè)試結(jié)果:
1)Write 事務(wù)寫入操作,隨著客戶端線程數(shù)增加,TPS上升,峰值超過30000

2)Get事務(wù)讀取操作, 隨著客戶端線程數(shù)增加,QPS上升,單節(jié)點(diǎn)峰值接近70000

從測(cè)試結(jié)果看,TiKV有較高的讀寫吞吐量,并且單次操作的響應(yīng)時(shí)間P99<10ms,在冷數(shù)據(jù)場(chǎng)景中性能表現(xiàn)可滿足業(yè)務(wù)需求。
官方的企業(yè)版元數(shù)據(jù)引擎比TiKV有更好的性能表現(xiàn),但是考慮到冷數(shù)據(jù)存儲(chǔ)對(duì)性能要求并不苛刻,而且相比于對(duì)象存儲(chǔ)20~200ms的訪問速度,元數(shù)據(jù)引擎并不會(huì)明顯降低整個(gè)系統(tǒng)響應(yīng)的速度。為了減少技術(shù)黑箱,我們選擇了TiKV作為元數(shù)據(jù)引擎。
2.3 JuiceFS 整體POC測(cè)試
在交付生產(chǎn)之前,為了明確SLA指標(biāo)和最佳使用場(chǎng)景,我們使用mdtest對(duì)以TiKV為元數(shù)據(jù)引擎的JuiceFS進(jìn)行了整體POC 測(cè)試,部署使用如下架構(gòu):

1)單線程寫入,測(cè)試文件大小與吞吐量的關(guān)系

測(cè)試結(jié)果表明隨著文件大小增大,吞吐量也隨之增大。在單文件為 128MB~256MB 左右時(shí),原先的吞吐量與文件大小的增長(zhǎng)曲線明顯放緩。可以理解為當(dāng)文件較小時(shí),JuiceFS客戶端與元數(shù)據(jù)引擎和對(duì)象存儲(chǔ)的交互成本與有效數(shù)據(jù)傳輸成本相比,占比較高,限制了吞吐量;當(dāng)文件較大時(shí),交互成本占比降低,吞吐量上升。為了發(fā)揮充分JuiceFS的吞吐能力,建議存儲(chǔ)128MB以上的文件。
2)目錄深度與 JuiceFS IOPS 的關(guān)系

測(cè)試結(jié)果表明目錄深度與 JuiceFS IOPS 沒有明顯關(guān)系。研究JuiceFS代碼可知,雖然深度越深,文件路徑變長(zhǎng),但 JuiceFS在創(chuàng)建文件/目錄時(shí)在TiKV里的Key是父目錄 inode + 新條目的名字,所以目錄深度不影響TiKV里的鍵值對(duì)大小,就不影響TiKV的查詢性能,符合測(cè)試結(jié)果。
3)目錄大小與 ls速度的關(guān)系
單目錄下文件個(gè)數(shù) | ls耗時(shí)(ms) |
1025 | 25 |
24269 | 31 |
測(cè)試結(jié)果表明目錄下文件個(gè)數(shù)對(duì)ls幾乎沒有影響。
2.4 元數(shù)據(jù)引擎故障測(cè)試
理論上TiKV 節(jié)點(diǎn)中 Region 通過 Raft 保證一致性,即非 Leader Region 故障完全不影響上層應(yīng)用,Leader Region 故障則會(huì)在該 Region 的副本中重新選舉出一個(gè) Leader Region,選舉需要時(shí)間,并且需要上報(bào) PD 節(jié)點(diǎn)進(jìn)行處理,因此會(huì)影響到上層應(yīng)用的部分請(qǐng)求。
PD 集群用來管理 TiKV 集群,PD 的非 Leader 節(jié)點(diǎn)故障完全不影響上層應(yīng)用,Leader 節(jié)點(diǎn)故障則需要重新選舉新 PD Leader,選舉過程 JuiceFS 客戶端的請(qǐng)求無(wú)法得到響應(yīng),新 Leader 節(jié)點(diǎn)確立后 JuiceFS 重新建立連接也需要一定耗時(shí),該時(shí)間段內(nèi)會(huì)對(duì)上層應(yīng)用的請(qǐng)求產(chǎn)生影響。
據(jù)此我們模擬節(jié)點(diǎn)故障的場(chǎng)景,測(cè)試實(shí)際應(yīng)用過程中元數(shù)據(jù)引擎故障后恢復(fù)所需時(shí)間,計(jì)算正常場(chǎng)景中讀寫一定數(shù)量文件與異常情況下的耗時(shí)差異。結(jié)果表明故障影響時(shí)間可以控制在秒級(jí)。
1)TiKV 故障
File size/count | 正常 | 異常 | Diff(ms) |
單線程寫 4MiB/1024 | 237035.52 | 249333.76 | 12298.24 |
單線程讀 4MiB/1024 | 360222.72 | 362577.92 | 2355.2 |
2)PD 故障
File size/count | 正常 | 異常 | Diff(ms) |
單線程寫4MiB/1024 | 237035.52 | 247531.52 | 10496 |
單線程讀 4MiB/1024 | 362332.16 | 362577.92 | 245.76 |
三、JuiceFS原理解析
3.1 文件寫入
JuiceFS 接收到寫請(qǐng)求會(huì)先將數(shù)據(jù)寫入 Buffer,并按照 Chunk、Slice、Block 的規(guī)則進(jìn)行數(shù)據(jù)塊管理,最后以 Slice 為維度Flush到對(duì)象存儲(chǔ)。一次 Flush 實(shí)質(zhì)上是對(duì) Slice 中的每個(gè) Block 進(jìn)行 PUT 操作,將數(shù)據(jù)寫到對(duì)象存儲(chǔ),并完成元數(shù)據(jù)修改。如下圖:

- 大文件先經(jīng)過 FUSE 處理成 128K 的塊,在JuiceFS內(nèi)部拼成一個(gè)個(gè)4M大小的Block,Slice 管理的Block不斷增加,直到 Slice 達(dá)到 64M(即一個(gè) Chunk 的大小),觸發(fā)一次 flush操作。Chunk、Slice、Block 的拼裝使用的是內(nèi)存buffer,其大小受JuiceFS啟動(dòng)參數(shù)buffer-size 的限制。
- 小文件由新的 Slice 單獨(dú)管理,在文件寫入完成時(shí)被上傳到對(duì)象存儲(chǔ)。
- 如果客戶端設(shè)置 writeback 模式,JuiceFS 不會(huì)直接寫數(shù)據(jù)到 Object Storage,而是寫到 JuiceFS 所在機(jī)器的本地磁盤,后續(xù)異步寫到對(duì)象存儲(chǔ)。這種方式存在丟數(shù)據(jù)的風(fēng)險(xiǎn),但是可以提升數(shù)據(jù)寫入速度。
3.2 文件讀取
讀取流程數(shù)據(jù)處理方式與寫入流程類似,讀取請(qǐng)求被 JuiceFS 進(jìn)程接收到后會(huì)先訪問元數(shù)據(jù)引擎,找到需要讀取的 Block,向?qū)ο蟠鎯?chǔ)并發(fā)發(fā)出 GET 請(qǐng)求。由于 Block 數(shù)據(jù)不變性,讀取出的 4M 的 Block 會(huì)寫到本地的緩存目錄中。讀取過程中按照 4M(Block) 的方式實(shí)現(xiàn)了一定程度的預(yù)讀,可以通過調(diào)整 prefetch 參數(shù),將預(yù)讀窗口設(shè)置的更大,默認(rèn) prefetch = 1。如下圖:

- 大文件順序讀場(chǎng)景下,會(huì)讀取對(duì)象存儲(chǔ)中4M 大小的對(duì)象,經(jīng)過 FUSE 處理成 128K 的塊返回給用戶。此場(chǎng)景中緩存命中率會(huì)很高,由于預(yù)讀和本地Block緩存,吞吐性能較好。
- 大文件隨機(jī)讀場(chǎng)景下流程和順序讀一致,該場(chǎng)景下的預(yù)讀、緩存被命中的概率很低,這些邏輯反而可能影響讀取性能(需要將讀取到的數(shù)據(jù)寫入本地緩存目錄),可以通過設(shè)置 cache-size = 0 關(guān)閉緩存。
- 小文件(例如 4K)讀取場(chǎng)景下,會(huì)讀取當(dāng)前文件的 Block,經(jīng)過 FUSE 后響應(yīng)給用戶程序。獲取到的數(shù)據(jù)也會(huì)寫到本地緩存目錄中。
四、故障處理與性能優(yōu)化
4.1 TiKV CPU使用率過高,導(dǎo)致拒絕服務(wù)
現(xiàn)象:TiKV kv_scan請(qǐng)求數(shù)突然上升,unified_read_po 線程池CPU使用率被打滿
分析:客戶端運(yùn)行cleanTrash任務(wù)導(dǎo)致的。Beta 1版本的客戶端會(huì)同時(shí)進(jìn)行該任務(wù),當(dāng)同一個(gè)volume掛載的客戶端較多,并且trash中的數(shù)據(jù)量非常多的時(shí)候,該任務(wù)會(huì)對(duì)元數(shù)據(jù)引擎造成突發(fā)的壓力。
解決方案:
- 增加客戶端對(duì)元數(shù)據(jù)引擎各個(gè)接口的調(diào)用量監(jiān)控,便于快速診斷是哪些客戶端導(dǎo)致的問題;
- 將后臺(tái)任務(wù)從客戶端中剝離,客戶端只需要執(zhí)行用戶的請(qǐng)求,cleanTrash這樣的后臺(tái)任務(wù)交給單獨(dú)的組件執(zhí)行,便于JuiceFS管理員控制;
- 升級(jí)客戶端,Beta3開始增加了分布式鎖,并且增加了no-bgjob啟動(dòng)參數(shù)。
4.2 TiKV 數(shù)據(jù)泄露
現(xiàn)象:文件數(shù)目和OSS中的數(shù)據(jù)量沒有增加的情況下,region數(shù)目不斷增加,store size不斷增加
分析:通過tikv-ctl查看TiKV里的數(shù)據(jù),發(fā)現(xiàn)MVCC的修改和刪除記錄沒有被清除。完整的TiDB部署會(huì)10min觸發(fā)一次數(shù)據(jù)GC。但是單獨(dú)部署TiKV,數(shù)據(jù)GC需要由其他程序觸發(fā)。另一方面5.0.1版本的TiKV有bug,數(shù)據(jù)GC沒有清理刪除記錄,相關(guān)issue。
解決方案:
- 參考https://github.com/tikv/client-go/blob/v2.0.0/examples/gcworker/gcworker.go單獨(dú)實(shí)現(xiàn)一個(gè)組件,定期調(diào)用GC功能
- 升級(jí)TiKV到5.0.6
4.3 CSI 掛載場(chǎng)景中,PV 清理后數(shù)據(jù) OSS 中數(shù)據(jù)無(wú)法回收
現(xiàn)象:k8s中的ElasticSearch 所有Pod、PVC、PV 下線一天后 OSS 數(shù)據(jù)仍沒被清理。
分析:PV 被清理時(shí) CSI 執(zhí)行了 JuiceFS rmr 指令,將 volume 數(shù)據(jù)全部放到回收站,根據(jù)默認(rèn)配置 trash-day=1,即一天后開始回收數(shù)據(jù)。由于環(huán)境中的 JuiceFS mount Pod 已經(jīng)全部下線,即沒有 JuiceFS 進(jìn)程掛載了 CSI 的 volume,于是出現(xiàn)了沒有清理回收站的情況。
解決方案:由于 CSI 模式使用 JuiceFS 是模擬了 subdir 的過程,即整個(gè) CSI 管理 Pod 掛載的 volume 是同一個(gè),通過寫到子目錄的方式進(jìn)行數(shù)據(jù)隔離。我們停止了mount pod的所有后臺(tái)任務(wù),另外找了一臺(tái)機(jī)器掛載該 volume來完成自動(dòng)清理回收站數(shù)據(jù)等后臺(tái)任務(wù),該方法也消除了后臺(tái)任務(wù)帶來的客戶端性能抖動(dòng)。
4.4 客戶端使用內(nèi)存過高
現(xiàn)象:部分使用 JuiceFS 的機(jī)器占用內(nèi)存過高,達(dá)到了 20GB+。
分析:
- 通過 cat /proc/$pid/smaps 查看,發(fā)現(xiàn)占用的內(nèi)存都是 Private_Dirty,說明是被 JuiceFS 進(jìn)程長(zhǎng)期持有,不是 Page Cache 緩存占用導(dǎo)致。
- 通過使用 pprof 工具分析 heap 占用情況,可推測(cè)出是 dump meta(backup)導(dǎo)致的異常。

解決方案:將客戶端的啟動(dòng)參數(shù)backup-meta默認(rèn)值改為0,元數(shù)據(jù)備份參考官方的實(shí)現(xiàn)思路通過另外的組件統(tǒng)一實(shí)現(xiàn),客戶端不執(zhí)行元數(shù)據(jù)備份任務(wù)。
4.5 優(yōu)化后架構(gòu)
生產(chǎn)實(shí)踐過程中涉及數(shù)PB級(jí)的數(shù)據(jù),業(yè)務(wù)場(chǎng)景相差巨大,經(jīng)過規(guī)劃與調(diào)優(yōu),最終演進(jìn)成如下架構(gòu)。

- 量級(jí)較小的業(yè)務(wù)由用戶掛載的JuiceFS client治理session、trash等數(shù)據(jù)。
- 量級(jí)較大的業(yè)務(wù)(PB級(jí)、數(shù)百client級(jí))掛載的client不處理session、trash等數(shù)據(jù)的清理(開啟no-bgjob參數(shù)),由admin 提供一個(gè)client單獨(dú)處理,并提供清理加速的能力。
- 提供了一個(gè)client統(tǒng)一做同一tikv集群內(nèi)所有volume的backup-meta操作。
- 提供訪問OSS和TIKV集群的限流能力,通過命令下發(fā)修改client的限流能力,用于在必要情況下保護(hù)專線帶寬、元數(shù)據(jù)庫(kù),實(shí)現(xiàn)服務(wù)降級(jí)。
- 使用多套元數(shù)據(jù)集群用來隔離行為差異較大的業(yè)務(wù)。
- 提供服務(wù)觸發(fā)TiKV的GC。
五、總結(jié)與展望
通過 JuiceFS 將冷數(shù)據(jù)上公有云, Elasticsearch 實(shí)現(xiàn)了一定程度的存算分離,去除了副本帶來的內(nèi)存需求,提升整體集群數(shù)據(jù)存儲(chǔ)能力。DBA 備份數(shù)據(jù)從 GlusterFS 遷移到 JuiceFS 后 ls 等行為的性能大幅提高,不僅運(yùn)維人員不再需要投入精力進(jìn)行磁盤擴(kuò)容維修,大大降低了運(yùn)維成本,而且用戶還能夠按照保留時(shí)間快速地控制存儲(chǔ)成本。
目前已有2PB 來自 ElasticSearch、DBA 備份的數(shù)據(jù)存儲(chǔ)到JuiceFS,后續(xù)我們會(huì)推動(dòng)更多的數(shù)據(jù)上JuiceFS,當(dāng)然后續(xù)也很多需要進(jìn)一步探索和優(yōu)化的地方,例如:
- 進(jìn)一步優(yōu)化元數(shù)據(jù)引擎 TiKV 的性能與提升 JuiceFS 的穩(wěn)定性,以應(yīng)對(duì)10PB+的數(shù)據(jù)量
- 探索JuiceFS在ClickHouse冷數(shù)據(jù)存儲(chǔ)上的使用方法
- 公有云場(chǎng)景下使用JuiceFS替換HDFS,以降低云上的存儲(chǔ)成本


































