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

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

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

開源方案實(shí)踐

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

s3fs

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

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); }}

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

goofys

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

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

>讀取采用同步方式,性能很差。FUSE有兩種讀取模式async和sync,通過掛載時(shí)的設(shè)置去選擇,goofys強(qiáng)制使用了sync模式,并且預(yù)讀的實(shí)現(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模塊會(huì)對(duì)超過128k的IO進(jìn)行拆分,以128k對(duì)齊。簡單介紹一下FUSE的同步讀取和異步讀取模式的區(qū)別。內(nèi)核的讀取一般入口是在底層文件系統(tǒng)的read_iter函數(shù),然后調(diào)用VFS層的generic_file_read_iter,該函數(shù)內(nèi)部實(shí)現(xiàn)會(huì)通過調(diào)用readpages進(jìn)行預(yù)讀。如果預(yù)讀后沒有對(duì)應(yīng)的page則會(huì)調(diào)用readpage讀取單頁。由于goofys不支持該設(shè)置,我們通過對(duì)s3fs設(shè)置不同的配置來測(cè)試,然后抓取讀取時(shí)的調(diào)用棧對(duì)比其中的區(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ù)。之后會(huì)調(diào)用到readpages進(jìn)行多頁的讀取。fuse_readpages將讀請(qǐng)求發(fā)給用戶態(tài)文件系統(tǒng),進(jìn)而完整整個(gè)讀取流程。同步讀取模式的堆棧如下所示:

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中進(jìn)行讀取,當(dāng)讀取之后沒有對(duì)應(yīng)的頁,會(huì)嘗試讀取單頁。相關(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è)置了同步方式進(jìn)行讀取,F(xiàn)USE模塊會(huì)無效內(nèi)核的預(yù)讀,轉(zhuǎn)而進(jìn)入到no_cached_page讀取單頁。所以同步模式下落到用戶態(tài)文件系統(tǒng)的讀IO有大塊的readpagesIO和readpage的4K單頁IO,由于offset存在相同,goofys會(huì)判斷為亂序的讀取,超過3次后停止預(yù)讀,由于每次和US3的交互都是4K的GET請(qǐng)求,性能會(huì)比較差,難以滿足用戶的需求。

>分片上傳的大小不固定,無法適配US3 。US3目前的分片大小固定為4M,而goofys的分片大小需要?jiǎng)討B(tài)的去計(jì)算,并手動(dòng)修改進(jìn)行適配,代碼如下:

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 }

...

}

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

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

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

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

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

VFS

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

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

FUSE

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

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

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

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

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

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

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

UCloud IO流程設(shè)計(jì)

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

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

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

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

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

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

對(duì)比測(cè)試

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

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

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

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

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

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

總結(jié)

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

(免責(zé)聲明:本網(wǎng)站內(nèi)容主要來自原創(chuàng)、合作伙伴供稿和第三方自媒體作者投稿,凡在本網(wǎng)站出現(xiàn)的信息,均僅供參考。本網(wǎng)站將盡力確保所提供信息的準(zhǔn)確性及可靠性,但不保證有關(guān)資料的準(zhǔn)確性及可靠性,讀者在使用前請(qǐng)進(jìn)一步核實(shí),并對(duì)任何自主決定的行為負(fù)責(zé)。本網(wǎng)站對(duì)有關(guān)資料所引致的錯(cuò)誤、不確或遺漏,概不負(fù)任何法律責(zé)任。
任何單位或個(gè)人認(rèn)為本網(wǎng)站中的網(wǎng)頁或鏈接內(nèi)容可能涉嫌侵犯其知識(shí)產(chǎn)權(quán)或存在不實(shí)內(nèi)容時(shí),應(yīng)及時(shí)向本網(wǎng)站提出書面權(quán)利通知或不實(shí)情況說明,并提供身份證明、權(quán)屬證明及詳細(xì)侵權(quán)或不實(shí)情況證明。本網(wǎng)站在收到上述法律文件后,將會(huì)依法盡快聯(lián)系相關(guān)文章源頭核實(shí),溝通刪除相關(guān)內(nèi)容或斷開相關(guān)鏈接。 )