JuiceFS 在攜程海量冷數(shù)據(jù)場景下的實踐
作者|妙成,攜程云原生研發(fā)工程師,主要從事Elasticsearch、JuiceFS的研發(fā)運維,關(guān)注分布式數(shù)據(jù)庫、NoSQL。小峰, 攜程云原生研發(fā)工程師,主要專注于數(shù)據(jù)庫容器化領(lǐng)域,對分布式存儲有濃厚興趣。
一、摘要
攜程的冷數(shù)據(jù)規(guī)模在 10PB+,包括備份數(shù)據(jù)、圖片語音訓練數(shù)據(jù)和日志數(shù)據(jù)等,存儲方案主要是本地磁盤和GlusterFS。在實際使用中這些方案遇到了不少痛點:
- GlusterFS 在單目錄下文件眾多時,ls命令速度很慢;
- 受疫情期間機器采購周期的制約,無法靈活地根據(jù)實際需求彈性擴縮容,存儲成本控制困難;
- 磁盤損壞等故障帶來的機器替換和擴縮容操作,使得運維成本居高不下。
隨著云計算技術(shù)的發(fā)展,公有云廠商為混合云客戶提供了海量冷數(shù)據(jù)的廉價存儲方案,經(jīng)過嚴謹?shù)某杀居嬎悖覀儼l(fā)現(xiàn)使用公有云的對象存儲可以顯著降低存儲和運維成本。為了減少遷移成本,我們一直在尋找后端存儲能支持各類公有云對象存儲、高性能的文件系統(tǒng),直到JuiceFS 出現(xiàn)在我們的視野中。JuiceFS有以下優(yōu)勢:
- POSIX 接口,對應(yīng)用無侵入
- 強一致性,文件修改立刻可見,為同一個 volume 被多臺機器掛載的場景提供 了close-to-open 保證
- 支持了主流的公有云對象存儲,支持開源軟件作為元數(shù)據(jù)引擎(Redis、TiKV)等
- 支持云原生,能夠?qū)olume以 CSI 的方式掛載到Pod上
- 社區(qū)活躍,代碼更新快
經(jīng)過大半年的測試和使用,我們已經(jīng)對接了數(shù)據(jù)庫備份和 ElasticSearch 冷數(shù)據(jù)存儲,將2PB+的數(shù)據(jù)遷移到了JuiceFS,預(yù)計后續(xù)還會有10PB+的數(shù)據(jù)接入。目前JuiceFS系統(tǒng)穩(wěn)定,在降低運維成本和存儲成本方面取得了良好的效果。本文將對JuiceFS原理以及我們在使用中所遇到的問題和采取的優(yōu)化方案進行簡單介紹。
二、JuiceFS 架構(gòu)與POC 測試
2.1 架構(gòu)簡介
JuiceFS 將元數(shù)據(jù)信息和真實數(shù)據(jù)塊分開管理,通過 FUSE 實現(xiàn) POSIX 接口,允許用戶像本地文件系統(tǒng)一樣使用。用戶將文件寫入JuiceFS掛載的目錄后,文件數(shù)據(jù)會存儲到對象存儲,相應(yīng)的元數(shù)據(jù)(文件名、文件大小、權(quán)限組、創(chuàng)建修改時間和目錄結(jié)構(gòu)等)會存到元數(shù)據(jù)引擎中。在該架構(gòu)下,ls、數(shù)據(jù)刪除等操作只是對元數(shù)據(jù)引擎的操作,不受到對象存儲的速度限制,性能上會有較好的保證。
2.2 元數(shù)據(jù)引擎選型與測試
JuiceFS 的元數(shù)據(jù)引擎有很多選擇,包括開源的Redis、TiKV以及官方提供的閉源的企業(yè)版元數(shù)據(jù)引擎??紤]到攜程的數(shù)據(jù)規(guī)模較大并且后續(xù)會有更多的數(shù)據(jù)接入,元數(shù)據(jù)引擎需要能夠支持TB 級元數(shù)據(jù)的存儲并且能橫向擴容。因此TiKV和官方的企業(yè)版元數(shù)據(jù)引擎成了我們的備選方案。
為了驗證TiKV的性能,我們使用 go-ycsb做了一些性能測試。
機器 | 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 |
測試結(jié)果:
1)Write 事務(wù)寫入操作,隨著客戶端線程數(shù)增加,TPS上升,峰值超過30000
2)Get事務(wù)讀取操作, 隨著客戶端線程數(shù)增加,QPS上升,單節(jié)點峰值接近70000
從測試結(jié)果看,TiKV有較高的讀寫吞吐量,并且單次操作的響應(yīng)時間P99<10ms,在冷數(shù)據(jù)場景中性能表現(xiàn)可滿足業(yè)務(wù)需求。
官方的企業(yè)版元數(shù)據(jù)引擎比TiKV有更好的性能表現(xiàn),但是考慮到冷數(shù)據(jù)存儲對性能要求并不苛刻,而且相比于對象存儲20~200ms的訪問速度,元數(shù)據(jù)引擎并不會明顯降低整個系統(tǒng)響應(yīng)的速度。為了減少技術(shù)黑箱,我們選擇了TiKV作為元數(shù)據(jù)引擎。
2.3 JuiceFS 整體POC測試
在交付生產(chǎn)之前,為了明確SLA指標和最佳使用場景,我們使用mdtest對以TiKV為元數(shù)據(jù)引擎的JuiceFS進行了整體POC 測試,部署使用如下架構(gòu):
1)單線程寫入,測試文件大小與吞吐量的關(guān)系
測試結(jié)果表明隨著文件大小增大,吞吐量也隨之增大。在單文件為 128MB~256MB 左右時,原先的吞吐量與文件大小的增長曲線明顯放緩。可以理解為當文件較小時,JuiceFS客戶端與元數(shù)據(jù)引擎和對象存儲的交互成本與有效數(shù)據(jù)傳輸成本相比,占比較高,限制了吞吐量;當文件較大時,交互成本占比降低,吞吐量上升。為了發(fā)揮充分JuiceFS的吞吐能力,建議存儲128MB以上的文件。
2)目錄深度與 JuiceFS IOPS 的關(guān)系
測試結(jié)果表明目錄深度與 JuiceFS IOPS 沒有明顯關(guān)系。研究JuiceFS代碼可知,雖然深度越深,文件路徑變長,但 JuiceFS在創(chuàng)建文件/目錄時在TiKV里的Key是父目錄 inode + 新條目的名字,所以目錄深度不影響TiKV里的鍵值對大小,就不影響TiKV的查詢性能,符合測試結(jié)果。
3)目錄大小與 ls速度的關(guān)系
單目錄下文件個數(shù) | ls耗時(ms) |
1025 | 25 |
24269 | 31 |
測試結(jié)果表明目錄下文件個數(shù)對ls幾乎沒有影響。
2.4 元數(shù)據(jù)引擎故障測試
理論上TiKV 節(jié)點中 Region 通過 Raft 保證一致性,即非 Leader Region 故障完全不影響上層應(yīng)用,Leader Region 故障則會在該 Region 的副本中重新選舉出一個 Leader Region,選舉需要時間,并且需要上報 PD 節(jié)點進行處理,因此會影響到上層應(yīng)用的部分請求。
PD 集群用來管理 TiKV 集群,PD 的非 Leader 節(jié)點故障完全不影響上層應(yīng)用,Leader 節(jié)點故障則需要重新選舉新 PD Leader,選舉過程 JuiceFS 客戶端的請求無法得到響應(yīng),新 Leader 節(jié)點確立后 JuiceFS 重新建立連接也需要一定耗時,該時間段內(nèi)會對上層應(yīng)用的請求產(chǎn)生影響。
據(jù)此我們模擬節(jié)點故障的場景,測試實際應(yīng)用過程中元數(shù)據(jù)引擎故障后恢復(fù)所需時間,計算正常場景中讀寫一定數(shù)量文件與異常情況下的耗時差異。結(jié)果表明故障影響時間可以控制在秒級。
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 接收到寫請求會先將數(shù)據(jù)寫入 Buffer,并按照 Chunk、Slice、Block 的規(guī)則進行數(shù)據(jù)塊管理,最后以 Slice 為維度Flush到對象存儲。一次 Flush 實質(zhì)上是對 Slice 中的每個 Block 進行 PUT 操作,將數(shù)據(jù)寫到對象存儲,并完成元數(shù)據(jù)修改。如下圖:
- 大文件先經(jīng)過 FUSE 處理成 128K 的塊,在JuiceFS內(nèi)部拼成一個個4M大小的Block,Slice 管理的Block不斷增加,直到 Slice 達到 64M(即一個 Chunk 的大?。?,觸發(fā)一次 flush操作。Chunk、Slice、Block 的拼裝使用的是內(nèi)存buffer,其大小受JuiceFS啟動參數(shù)buffer-size 的限制。
- 小文件由新的 Slice 單獨管理,在文件寫入完成時被上傳到對象存儲。
- 如果客戶端設(shè)置 writeback 模式,JuiceFS 不會直接寫數(shù)據(jù)到 Object Storage,而是寫到 JuiceFS 所在機器的本地磁盤,后續(xù)異步寫到對象存儲。這種方式存在丟數(shù)據(jù)的風險,但是可以提升數(shù)據(jù)寫入速度。
3.2 文件讀取
讀取流程數(shù)據(jù)處理方式與寫入流程類似,讀取請求被 JuiceFS 進程接收到后會先訪問元數(shù)據(jù)引擎,找到需要讀取的 Block,向?qū)ο蟠鎯Σl(fā)發(fā)出 GET 請求。由于 Block 數(shù)據(jù)不變性,讀取出的 4M 的 Block 會寫到本地的緩存目錄中。讀取過程中按照 4M(Block) 的方式實現(xiàn)了一定程度的預(yù)讀,可以通過調(diào)整 prefetch 參數(shù),將預(yù)讀窗口設(shè)置的更大,默認 prefetch = 1。如下圖:
- 大文件順序讀場景下,會讀取對象存儲中4M 大小的對象,經(jīng)過 FUSE 處理成 128K 的塊返回給用戶。此場景中緩存命中率會很高,由于預(yù)讀和本地Block緩存,吞吐性能較好。
- 大文件隨機讀場景下流程和順序讀一致,該場景下的預(yù)讀、緩存被命中的概率很低,這些邏輯反而可能影響讀取性能(需要將讀取到的數(shù)據(jù)寫入本地緩存目錄),可以通過設(shè)置 cache-size = 0 關(guān)閉緩存。
- 小文件(例如 4K)讀取場景下,會讀取當前文件的 Block,經(jīng)過 FUSE 后響應(yīng)給用戶程序。獲取到的數(shù)據(jù)也會寫到本地緩存目錄中。
四、故障處理與性能優(yōu)化
4.1 TiKV CPU使用率過高,導(dǎo)致拒絕服務(wù)
現(xiàn)象:TiKV kv_scan請求數(shù)突然上升,unified_read_po 線程池CPU使用率被打滿
分析:客戶端運行cleanTrash任務(wù)導(dǎo)致的。Beta 1版本的客戶端會同時進行該任務(wù),當同一個volume掛載的客戶端較多,并且trash中的數(shù)據(jù)量非常多的時候,該任務(wù)會對元數(shù)據(jù)引擎造成突發(fā)的壓力。
解決方案:
- 增加客戶端對元數(shù)據(jù)引擎各個接口的調(diào)用量監(jiān)控,便于快速診斷是哪些客戶端導(dǎo)致的問題;
- 將后臺任務(wù)從客戶端中剝離,客戶端只需要執(zhí)行用戶的請求,cleanTrash這樣的后臺任務(wù)交給單獨的組件執(zhí)行,便于JuiceFS管理員控制;
- 升級客戶端,Beta3開始增加了分布式鎖,并且增加了no-bgjob啟動參數(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部署會10min觸發(fā)一次數(shù)據(jù)GC。但是單獨部署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單獨實現(xiàn)一個組件,定期調(diào)用GC功能
- 升級TiKV到5.0.6
4.3 CSI 掛載場景中,PV 清理后數(shù)據(jù) OSS 中數(shù)據(jù)無法回收
現(xiàn)象:k8s中的ElasticSearch 所有Pod、PVC、PV 下線一天后 OSS 數(shù)據(jù)仍沒被清理。
分析:PV 被清理時 CSI 執(zhí)行了 JuiceFS rmr 指令,將 volume 數(shù)據(jù)全部放到回收站,根據(jù)默認配置 trash-day=1,即一天后開始回收數(shù)據(jù)。由于環(huán)境中的 JuiceFS mount Pod 已經(jīng)全部下線,即沒有 JuiceFS 進程掛載了 CSI 的 volume,于是出現(xiàn)了沒有清理回收站的情況。
解決方案:由于 CSI 模式使用 JuiceFS 是模擬了 subdir 的過程,即整個 CSI 管理 Pod 掛載的 volume 是同一個,通過寫到子目錄的方式進行數(shù)據(jù)隔離。我們停止了mount pod的所有后臺任務(wù),另外找了一臺機器掛載該 volume來完成自動清理回收站數(shù)據(jù)等后臺任務(wù),該方法也消除了后臺任務(wù)帶來的客戶端性能抖動。
4.4 客戶端使用內(nèi)存過高
現(xiàn)象:部分使用 JuiceFS 的機器占用內(nèi)存過高,達到了 20GB+。
分析:
- 通過 cat /proc/$pid/smaps 查看,發(fā)現(xiàn)占用的內(nèi)存都是 Private_Dirty,說明是被 JuiceFS 進程長期持有,不是 Page Cache 緩存占用導(dǎo)致。
- 通過使用 pprof 工具分析 heap 占用情況,可推測出是 dump meta(backup)導(dǎo)致的異常。
解決方案:將客戶端的啟動參數(shù)backup-meta默認值改為0,元數(shù)據(jù)備份參考官方的實現(xiàn)思路通過另外的組件統(tǒng)一實現(xiàn),客戶端不執(zhí)行元數(shù)據(jù)備份任務(wù)。
4.5 優(yōu)化后架構(gòu)
生產(chǎn)實踐過程中涉及數(shù)PB級的數(shù)據(jù),業(yè)務(wù)場景相差巨大,經(jīng)過規(guī)劃與調(diào)優(yōu),最終演進成如下架構(gòu)。
- 量級較小的業(yè)務(wù)由用戶掛載的JuiceFS client治理session、trash等數(shù)據(jù)。
- 量級較大的業(yè)務(wù)(PB級、數(shù)百client級)掛載的client不處理session、trash等數(shù)據(jù)的清理(開啟no-bgjob參數(shù)),由admin 提供一個client單獨處理,并提供清理加速的能力。
- 提供了一個client統(tǒng)一做同一tikv集群內(nèi)所有volume的backup-meta操作。
- 提供訪問OSS和TIKV集群的限流能力,通過命令下發(fā)修改client的限流能力,用于在必要情況下保護專線帶寬、元數(shù)據(jù)庫,實現(xiàn)服務(wù)降級。
- 使用多套元數(shù)據(jù)集群用來隔離行為差異較大的業(yè)務(wù)。
- 提供服務(wù)觸發(fā)TiKV的GC。
五、總結(jié)與展望
通過 JuiceFS 將冷數(shù)據(jù)上公有云, Elasticsearch 實現(xiàn)了一定程度的存算分離,去除了副本帶來的內(nèi)存需求,提升整體集群數(shù)據(jù)存儲能力。DBA 備份數(shù)據(jù)從 GlusterFS 遷移到 JuiceFS 后 ls 等行為的性能大幅提高,不僅運維人員不再需要投入精力進行磁盤擴容維修,大大降低了運維成本,而且用戶還能夠按照保留時間快速地控制存儲成本。
目前已有2PB 來自 ElasticSearch、DBA 備份的數(shù)據(jù)存儲到JuiceFS,后續(xù)我們會推動更多的數(shù)據(jù)上JuiceFS,當然后續(xù)也很多需要進一步探索和優(yōu)化的地方,例如:
- 進一步優(yōu)化元數(shù)據(jù)引擎 TiKV 的性能與提升 JuiceFS 的穩(wěn)定性,以應(yīng)對10PB+的數(shù)據(jù)量
- 探索JuiceFS在ClickHouse冷數(shù)據(jù)存儲上的使用方法
- 公有云場景下使用JuiceFS替換HDFS,以降低云上的存儲成本