當前位置:首頁 >  科技 >  IT業(yè)界 >  正文

UCloud優(yōu)刻得針對對象存儲US3的用戶態(tài)文件系統(tǒng)設(shè)計提升70%讀寫性能

 2021-05-14 13:28  來源: 互聯(lián)網(wǎng)   我來投稿 撤稿糾錯

  域名預(yù)訂/競價,好“米”不錯過

為了解決在數(shù)據(jù)備份場景中的可靠性、容量、成本問題,越來越多的用戶傾向于使用對象存儲來進行備份。開源的項目例如s3fs和goofys在大文件的讀寫場景下各有優(yōu)劣,相比之下,UCloud優(yōu)刻得對象存儲US3團隊自主研發(fā)的 US3FS 無論是讀還是寫都有更好的性能,而且和UCloud優(yōu)刻得US3的適配性更強,更易于拓展。在移動需求場景下,特別是大文件居多的場景,通過US3FS能提升超過100倍的性能。

比如在數(shù)據(jù)庫備份場景下,如果直接使用對象存儲備份,可能需要先把數(shù)據(jù)庫通過mysqldump做邏輯備份,或者采用xtrabackup做物理備份到本地,然后使用基于對象存儲的SDK的工具把備份文件上傳到對象存儲,備份過程繁瑣。再例如服務(wù)的日志歸檔備份,為降低成本可以將日志存儲到UCloud優(yōu)刻得對象存儲US3中,通過SDK或者工具來操作,不僅需要編寫備份代碼,而且管理復(fù)雜。如果能提供一種以POSIX接口遠程訪問對象存儲的方式,就可以很好地解決上述問題。

開源方案實踐

已經(jīng)有一些開源的項目將對象存儲中的bucket映射為文件系統(tǒng),如s3fs和goofys等,在使用這些開源方案的時候,我們發(fā)現(xiàn)了一些問題。

s3fs

s3fs通過FUSE將s3和支持s3協(xié)議的對象存儲的bucket掛載到本地(FUSE的介紹詳見下文)。通過對s3fs進行測試后,我們發(fā)現(xiàn)其在大文件的寫入方面性能特別差,研究其實現(xiàn)過程后,我們發(fā)現(xiàn)s3fs在寫入時會優(yōu)先寫入本地臨時文件,然后以分片上傳的方式將并發(fā)的將數(shù)據(jù)寫入到對象存儲。如果空間不足,則會以同步的方式將分片上傳,代碼如下:

ssize_t FdEntity::Write(const char* bytes, off_t start, size_t size){ // no enough disk space if(0 != (result = NoCachePreMultipartPost())){ S3FS_PRN_ERR("failed to switch multipart uploading with no cache(errno=%d)", result); return static_cast(result); } // start multipart uploading if(0 != (result = NoCacheLoadAndPost(0, start))){ S3FS_PRN_ERR("failed to load uninitialized area and multipart uploading it(errno=%d)", result); return static_cast(result); }}

由于我們的主要使用場景為大文件的備份,基于云主機硬盤成本等方面的考慮,我們決定放棄這一方案。

goofys

goofys是用go實現(xiàn)的將s3以及部分非s3協(xié)議的對象存儲掛載到linux的文件系統(tǒng),測試后,我們發(fā)現(xiàn)goofys主要有三個問題:

> 寫入沒有進行并發(fā)控制。在大文件的寫入場景下,goofys同樣將文件進行分片,然后每個分片開一個協(xié)程寫入到后端存儲。對象存儲一般通過HTTP協(xié)議進行通信,由于請求是同步的方式,在不限制并發(fā)數(shù)的情況下會有大量的連接,消耗大量的內(nèi)存等資源。

> 讀取采用同步方式,性能很差。FUSE有兩種讀取模式async和sync,通過掛載時的設(shè)置去選擇,goofys強制使用了sync模式,并且預(yù)讀的實現(xiàn)為亂序讀取超過三次后停止預(yù)讀,代碼如下:

if !fs.flags.Cheap && fh.seqReadAmount >= uint64(READAHEAD_CHUNK) && fh.numOOORead < 3 { ... err = fh.readAhead(uint64(offset), len(buf)) ...}

fh.numOOORead為亂序讀取的次數(shù),F(xiàn)USE模塊會對超過128k的IO進行拆分,以128k對齊。簡單介紹一下FUSE的同步讀取和異步讀取模式的區(qū)別。內(nèi)核的讀取一般入口是在底層文件系統(tǒng)的read_iter函數(shù),然后調(diào)用VFS層的generic_file_read_iter,該函數(shù)內(nèi)部實現(xiàn)會通過調(diào)用readpages進行預(yù)讀。如果預(yù)讀后沒有對應(yīng)的page則會調(diào)用readpage讀取單頁。由于goofys不支持該設(shè)置,我們通過對s3fs設(shè)置不同的配置來測試,然后抓取讀取時的調(diào)用棧對比其中的區(qū)別。設(shè)置了異步讀取模式的讀堆棧如下所示:

fuse_readpages+0x5/0x110 [fuse]read_pages+0x6b/0x190__do_page_cache_readahead+0x1c1/0x1e0ondemand_readahead+0x1f9/0x2c0? pagecache_get_page+0x30/0x2d0generic_file_buffered_read+0x5a50xb10? mem_cgroup_try_charge+0x8b/0x1a0? mem_cgroup_throttle_swaprate+0x17/0x10efuse_file_read_iter+0x10d/0x130 [fuse]? __handle_mm_fault+0x662/0x6a0new_sync_read+0x121/0x170vfs_read+0x91/0x140

其中vfs_read是系統(tǒng)調(diào)用到vfs層的入口函數(shù)。之后會調(diào)用到readpages進行多頁的讀取。fuse_readpages將讀請求發(fā)給用戶態(tài)文件系統(tǒng),進而完整整個讀取流程。同步讀取模式的堆棧如下所示:

fuse_readpage+0x5/0x60 [fuse] generic_file_buffered_read+0x61a/0xb10 ? mem_cgroup_try_charge+0x8b/0x1a0 ? mem_cgroup_throttle_swaprate+0x17/0x10e fuse_file_read_iter+0x10d/0x130 [fuse] ? __handle_mm_fault+0x662/0x6a0 new_sync_read+0x121/0x170vfs_read+0x91/0x140

和異步流程相同,依然是在generic_file_read_iter中進行讀取,當讀取之后沒有對應(yīng)的頁,會嘗試讀取單頁。相關(guān)代碼如下,內(nèi)核版本基于4.14:

no_cached_page: /* * Ok, it wasn't cached, so we need to create a new * page.. */ page = page_cache_alloc_cold(mapping); if (!page) { error = -ENOMEM; goto out; } error = add_to_page_cache_lru(page, mapping, index, mapping_gfp_constraint(mapping, GFP_KERNEL)); if (error) { put_page(page); if (error == -EEXIST) { error = 0; goto find_page; } goto out; } goto readpage;

如果設(shè)置了同步方式進行讀取,F(xiàn)USE模塊會無效內(nèi)核的預(yù)讀,轉(zhuǎn)而進入到no_cached_page讀取單頁。所以同步模式下落到用戶態(tài)文件系統(tǒng)的讀IO有大塊的readpagesIO和readpage的4K單頁IO,由于offset存在相同,goofys會判斷為亂序的讀取,超過3次后停止預(yù)讀,由于每次和US3的交互都是4K的GET請求,性能會比較差,難以滿足用戶的需求。

> 分片上傳的大小不固定,無法適配US3 。US3目前的分片大小固定為4M,而goofys的分片大小需要動態(tài)的去計算,并手動修改進行適配,代碼如下:

func (fh *FileHandle) partSize() uint64 { var size uint64

if fh.lastPartId < 1000 { size = 5 * 1024 * 1024 } else if fh.lastPartId < 2000 { size = 25 * 1024 * 1024 } else { size = 125 * 1024 * 1024 }

...

}

同時,s3協(xié)議本身沒有rename的的接口,s3fs和goofys的rename都是通過將源文件內(nèi)容復(fù)制到目標文件,然后刪除源文件實現(xiàn)的。

而US3內(nèi)部支持直接修改文件名,US3FS通過使用相關(guān)的接口實現(xiàn)rename操作,相比s3fs和goofys性能更好。同時s3fs和goofys掛載US3的bucket都需要走代理進行協(xié)議的轉(zhuǎn)換,使用US3FS則減少了這一IO路徑,性能上更有優(yōu)勢。

通過對s3fs和goofys的實踐,我們發(fā)現(xiàn)兩者在US3的備份場景上的性能有一些問題,同時適配的工作量也比較大,基于此,我們決定開發(fā)一款能夠滿足用戶在數(shù)據(jù)備份場景需求的,依托對象存儲作為后端的文件系統(tǒng)。

UCloud優(yōu)刻得US3FS設(shè)計概述

US3FS通過FUSE實現(xiàn)部分POSIX API。在介紹US3FS實現(xiàn)之前,先簡單介紹一下Linux的VFS機制和FUSE實現(xiàn)(有這部分基礎(chǔ)的朋友可直接跳過)。

VFS

VFS,全稱Virtual File System,是linux內(nèi)核中一個承上啟下的虛擬層,隸屬于IO子系統(tǒng)。對上,為用戶態(tài)應(yīng)用提供了文件系統(tǒng)接口;對下,將具體的實現(xiàn)抽象為同一個函數(shù)指針供底層文件系統(tǒng)實現(xiàn)。

linux文件系統(tǒng)中的元數(shù)據(jù)分為dentry(directory entry)和inode,我們知道,文件名并不屬于文件的元數(shù)據(jù),為了優(yōu)化查詢,vfs在內(nèi)存中建立dentry以緩存文件名和inode的映射以及目錄樹的實現(xiàn)。單機文件系統(tǒng)的實現(xiàn),dentry只存在于內(nèi)存中,不會落盤,當查找某個文件時內(nèi)存沒有對應(yīng)的dentry,vfs會調(diào)用具體的文件系統(tǒng)實現(xiàn)來查找對應(yīng)的文件,并建立起對應(yīng)的數(shù)據(jù)結(jié)構(gòu)。inode緩存了一個文件的元數(shù)據(jù),如大小,修改時間等,會持久化到硬盤中,數(shù)據(jù)的讀寫通過地址空間找到對應(yīng)的page和block device進行讀寫。

FUSE

FUSE,全稱Filesystem in Userspace,用戶態(tài)文件系統(tǒng),我們知道,一般直接在內(nèi)核態(tài)實現(xiàn)某個特性是比較痛苦的事情,通常內(nèi)核的debug比較困難,而且稍不注意就會陷入到內(nèi)核的各種細節(jié)而無法自拔。FUSE就是為了簡化程序員的工作,將內(nèi)核的細節(jié)隱藏起來,提供一套用戶態(tài)的接口用于實現(xiàn)自己的文件系統(tǒng),用戶只需要實現(xiàn)對應(yīng)的接口即可。內(nèi)核態(tài)的FUSE模塊和用戶態(tài)的FUSE庫的交互通過/dev/fuse進行通信,然后調(diào)用用戶自己的實現(xiàn)。當然,缺點就是增加了IO路徑以及內(nèi)核態(tài)/用戶態(tài)的切換,對性能有一定影響。

UCloud元數(shù)據(jù)設(shè)計

US3FS通過實現(xiàn)FUSE的接口,將US3中bucket的對象映射為文件,和分布式文件系統(tǒng)不同,沒有mds(metadata server)維護文件元數(shù)據(jù),需要通過HTTP向us3獲取。當文件較多時,大量的請求會瞬間發(fā)出,性能很差。為了解決這一點,US3FS在內(nèi)存中維護了bucket的目錄樹,并設(shè)置文件元數(shù)據(jù)的有效時間,避免頻繁和US3交互。

這也帶來了一致性的問題,當多個client修改同一bucket中的文件,其中的緩存一致性無法保證,需要用戶自己取舍。為了提升檢索的性能,文件并沒有像對象存儲以平鋪的方式放在整個目錄中,而是采用了傳統(tǒng)文件系統(tǒng)類似的方式,為每一個目錄構(gòu)建相關(guān)數(shù)據(jù)結(jié)構(gòu)來保存其中的文件,同時inode的設(shè)計也盡量簡潔,只保存必要字段,減少內(nèi)存的占用。

目前Inode中保存的字段有uid,gid,size,mtime等,通過US3的元數(shù)據(jù)功能在對象中持久化。例如下圖所示,在US3的bucket中有一個名為"a/b/c/f1"的對象,在文件系統(tǒng)中,會將每一個“/"劃分的前綴映射為目錄,從而實現(xiàn)左邊的目錄樹。

UCloud IO流程設(shè)計

對于數(shù)據(jù)的寫入,US3支持大文件的分片上傳。利用這一特性,US3FS通過將數(shù)據(jù)寫入cache,在后臺將數(shù)據(jù)以分片上傳的方式,將數(shù)據(jù)以4MB的chunk寫入到后端存儲中。分片上傳的流程如下圖所示,通過令牌桶限制整個系統(tǒng)的寫入并發(fā)數(shù)。每個分片寫入的線程都會獲取令牌后寫入,通過當文件close時寫入最后一個分片,完成整個上傳流程。

文件的讀取通過在US3FS的cache實現(xiàn)預(yù)讀來提升性能。kernel-fuse自身對數(shù)據(jù)的讀寫進行了分片,在不修改內(nèi)核的情況下,IO最大為128K。而大文件的讀取場景一般為連續(xù)的大IO,這種場景下IO會被切成128K的片,不做預(yù)讀的話,無法很好的利用網(wǎng)絡(luò)帶寬。US3FS的預(yù)讀算法如下所示:

如圖所示,第一次同步讀取完成后,會往后進行當前長度的預(yù)讀,并將預(yù)讀的中點設(shè)置為下次觸發(fā)預(yù)讀的trigger。之后的讀取如果不連續(xù),則清空之前的狀態(tài),進行新的預(yù)讀,如果連續(xù),則判斷當前讀取的結(jié)束位置是否不小于觸發(fā)預(yù)讀的偏移,如果觸發(fā)預(yù)讀,則將預(yù)讀窗口的大小擴大為2倍,直到達到設(shè)定的閾值。之后以新的窗口進行預(yù)讀。如果未觸發(fā),則不進行預(yù)讀。預(yù)讀對順序讀的性能有很大提升。鑒于US3FS使用場景多為大文件的場景,US3FS本身不對數(shù)據(jù)進行任何緩存。在US3FS之上有內(nèi)核的pagecache,當用戶重復(fù)讀取同一文件時pagecache能夠很好的起作用。

UCloud數(shù)據(jù)一致性

由于對象存儲的實現(xiàn)機制原因,當前大文件的寫入,在完成所有的分片上傳之前,數(shù)據(jù)是不可見的,所以對于US3FS的寫入,在close之前,寫入的數(shù)據(jù)都是不可讀的,當close后,US3FS會發(fā)送結(jié)束分片的請求,結(jié)束整個寫入流程,此時數(shù)據(jù)對用戶可見。

對比測試

在并發(fā)度為64,IO大小為4M測試模型下,40G文件的順序?qū)懞晚樞蜃x進行多次測試,平均結(jié)果如下:

測試過程中,goofys的內(nèi)存占用比較高,峰值約3.3G,而US3FS比較平穩(wěn),峰值約305M,節(jié)省了90%內(nèi)存空間。s3fs表現(xiàn)相對較好,因為使用本地臨時文件做緩存,所以內(nèi)存占用比較少,但是寫入文件比較大,硬盤空間不足時,性能會下降到表格中的數(shù)據(jù)。

在順序讀的測試中,測試結(jié)果可以驗證我們的分析,goofys由于本身設(shè)計的原因,在這種場景下性能無法滿足我們的要求。另外在測試移動1G文件的場景中,對比結(jié)果如下:

可見在移動需求場景下,特別是大文件居多的場景,通過US3FS能提升上百倍的性能。

總結(jié)

總而言之,s3fs和goofys在大文件的讀寫場景下各有優(yōu)劣,相比之下,UCloud優(yōu)刻得US3自研的 US3FS 無論是讀還是寫都有更好的性能,而且和UCloud優(yōu)刻得US3的適配性更強,更易于拓展。

申請創(chuàng)業(yè)報道,分享創(chuàng)業(yè)好點子。點擊此處,共同探討創(chuàng)業(yè)新機遇!

相關(guān)標簽
數(shù)據(jù)庫
ucloud

相關(guān)文章

熱門排行

信息推薦