作者 | 黃冬發(fā)
背景介紹
在一個(gè)典型的分布式文件系統(tǒng)中,目錄文件元數(shù)據(jù)操作(包括創(chuàng)建目錄或文件,重命名,修改權(quán)限等)在整個(gè)文件系統(tǒng)操作中占很大比例,因此元數(shù)據(jù)服務(wù)在整個(gè)文件系統(tǒng)中扮演著重要的角色,隨著大規(guī)模機(jī)器學(xué)習(xí)、大數(shù)據(jù)分析和企業(yè)級(jí)數(shù)據(jù)湖等應(yīng)用,分布式文件系統(tǒng)數(shù)據(jù)規(guī)模已經(jīng)從 PB 級(jí)到 EB 級(jí),當(dāng)前多數(shù)分布式文件系統(tǒng)(如 HDFS 等)面臨著元數(shù)據(jù)擴(kuò)展性的挑戰(zhàn)。
以 Google、Facebook 和 Microsoft 等為代表的公司基本實(shí)現(xiàn)了能夠管理 EB 級(jí)數(shù)據(jù)規(guī)模的分布式文件系統(tǒng),這些系統(tǒng)的共同架構(gòu)特征是依賴于底層分布式數(shù)據(jù)庫(kù)能力來(lái)實(shí)現(xiàn)元數(shù)據(jù)性能的水平擴(kuò)展,如 Google Colossus 基于 BigTable,F(xiàn)acebook 基于 ZippyDB,Microsoft ADLSv2 基于 Table Storage,還有一些開源文件系統(tǒng)包括 CephFS 和 HopsFS 等也基本實(shí)現(xiàn)了水平擴(kuò)展的能力。
這些文件系統(tǒng)實(shí)現(xiàn)由于對(duì)底層分布式數(shù)據(jù)庫(kù)的依賴,對(duì)文件系統(tǒng)的語(yǔ)義支持程度也各有不同,如大多數(shù)基于分布式文件系統(tǒng)的計(jì)算分析框架依賴底層目錄原子 Rename 操作來(lái)提供數(shù)據(jù)的原子更新,而 Tectonic 和 Colossus 因?yàn)榈讓訑?shù)據(jù)庫(kù)不支持跨分區(qū)事務(wù)所以不保證跨目錄 Rename 的原子性,而 ADLSv2 支持對(duì)任意目錄的原子 Rename。
DanceNN 是公司自研的一個(gè)目錄樹元信息存儲(chǔ)系統(tǒng),致力于解決所有分布式存儲(chǔ)系統(tǒng)的目錄樹需求(包括不限于 HDFS,NAS 等),極大簡(jiǎn)化上層存儲(chǔ)系統(tǒng)依賴的目錄樹操作復(fù)雜性,包括不限于原子 Rename、遞歸刪除等。解決超大規(guī)模目錄樹存儲(chǔ)場(chǎng)景下的擴(kuò)展性、性能、異構(gòu)系統(tǒng)間的全局統(tǒng)一命名空間等問題,打造全球領(lǐng)先的通用分布式目錄樹服務(wù)。
當(dāng)前 DanceNN 已經(jīng)為公司在線 ByteNAS,離線 HDFS 兩大分布式文件系統(tǒng)提供目錄樹元數(shù)據(jù)服務(wù)。
(本篇主要介紹在離線大數(shù)據(jù)場(chǎng)景 HDFS 文件系統(tǒng)下 DanceNN 的應(yīng)用,考慮篇幅,DanceNN 在 ByteNAS 的應(yīng)用會(huì)在后續(xù)系列文章介紹,敬請(qǐng)期待)
元數(shù)據(jù)演進(jìn)
字節(jié) HDFS 元數(shù)據(jù)系統(tǒng)分三個(gè)階段演進(jìn):
NameNode
最開始公司使用 HDFS 原生 NameNode,雖然進(jìn)行了大量?jī)?yōu)化,依然面臨下列問題:
- 元數(shù)據(jù)(包括目錄樹,文件和 Block 副本等)全內(nèi)存存儲(chǔ),單機(jī)承載能力有限
- 基于 Java 語(yǔ)言實(shí)現(xiàn),在大內(nèi)存場(chǎng)景 GC 停頓時(shí)間比較長(zhǎng),嚴(yán)重影響 SLA
- 使用全局一把讀寫鎖,讀寫吞吐性能較差
- 隨著集群數(shù)據(jù)規(guī)模增加,重啟恢復(fù)時(shí)間達(dá)到小時(shí)級(jí)別
DanceNN v1
DanceNN v1 的設(shè)計(jì)目標(biāo)是為了解決上述 NameNode 遇到的問題。
主要設(shè)計(jì)點(diǎn)包括:
- 重新實(shí)現(xiàn) HDFS 協(xié)議層,將目錄樹文件相關(guān)元數(shù)據(jù)存儲(chǔ)到 RocksDB 存儲(chǔ)引擎,提供 10 倍元數(shù)據(jù)承載
- 使用 C++ 實(shí)現(xiàn),避免 GC 問題,同時(shí)使用高效數(shù)據(jù)結(jié)構(gòu)組織內(nèi)存 Block 信息,減少內(nèi)存使用
- 實(shí)現(xiàn)一套細(xì)粒度目錄鎖機(jī)制,極大提升不同目錄文件操作間的并發(fā)
- 請(qǐng)求路徑全異步化,支持請(qǐng)求優(yōu)先級(jí)處理
- 重點(diǎn)優(yōu)化塊匯報(bào)和重啟加載流程,降低不可用時(shí)間
DanceNNv1 最終在 2019 年完成全量上線,線上效果基本達(dá)到設(shè)計(jì)目標(biāo)。
下面是一個(gè)十幾億文件數(shù)規(guī)模集群,切換后大致性能對(duì)比:
DanceNN v1 開發(fā)中遇到很多技術(shù)挑戰(zhàn),如為了保證上線過程對(duì)業(yè)務(wù)無(wú)感知,支持現(xiàn)有多種 HDFS 客戶端訪問,后端需要完全兼容原有的 Hadoop HDFS 協(xié)議。
Distributed DanceNN
一直以來(lái) HDFS 都是使用 Federation 方式來(lái)管理目錄樹,將全局 Namespace 按 path 映射到多組元數(shù)據(jù)獨(dú)立的 DanceNN v1 集群,單組 DanceNN v1 集群有單機(jī)瓶頸,能處理的吞吐和容量有限,隨著公司業(yè)務(wù)數(shù)據(jù)的增長(zhǎng),單組 DanceNN v1 集群達(dá)到性能極限,就需要在兩個(gè)集群之間頻繁遷移數(shù)據(jù),為了保證數(shù)據(jù)一致性需要在遷移過程中上層業(yè)務(wù)停寫,對(duì)業(yè)務(wù)影響比較大,并且當(dāng)數(shù)據(jù)量大的情況下遷移比較慢,這些問題給整個(gè)系統(tǒng)帶來(lái)非常大的運(yùn)維壓力,降低服務(wù)的穩(wěn)定性。
Distributed 版本主要設(shè)計(jì)目標(biāo):
- 通用目錄樹服務(wù),支持多協(xié)議包括 HDFS,POSIX 等
- 單一全局 Namespace
- 容量、吞吐支持水平擴(kuò)展
- 高可用,故障恢復(fù)時(shí)間在秒級(jí)內(nèi)
- 包括跨目錄 Rename 等寫操作支持事務(wù)
- 高性能,基于 C++ 實(shí)現(xiàn),依賴 Brpc 等高性能框架
Distributed DanceNN 目前已經(jīng)在 HDFS 部分集群上線,正在進(jìn)行存量集群的平滑遷移。
文件系統(tǒng)概覽
分層架構(gòu)
最新 HDFS 分布式文件系統(tǒng)實(shí)現(xiàn)采用分層架構(gòu),主要包括三層:
數(shù)據(jù)層:用于存儲(chǔ)文件內(nèi)容, 處理 Block 級(jí)別的 IO 請(qǐng)求
- 由 DataNode 節(jié)點(diǎn)提供服務(wù)
Namespace 層:負(fù)責(zé)目錄樹相關(guān)元數(shù)據(jù),處理目錄和文件創(chuàng)建、刪除、Rename 和鑒權(quán)等請(qǐng)求
- 由 Distributed DanceNN 集群提供服務(wù)
文件塊層:負(fù)責(zé)文件相關(guān)的元數(shù)據(jù)、文件與 Block 的映射以及 Block 副本位置信息,處理文件創(chuàng)建刪除,文件 Block 的添加等請(qǐng)求
- 一個(gè) BSGroup 負(fù)責(zé)管理集群部分文件塊元數(shù)據(jù),由多臺(tái)的 DanceBS 組成提供高可用服務(wù)
- 通過 BSGroup 動(dòng)態(tài)擴(kuò)容來(lái)適應(yīng)集群負(fù)載,當(dāng)某個(gè) BSGroup 快達(dá)到性能極限后可以控制寫入
DanceProxy
C++ 實(shí)現(xiàn),基于高性能框架 Brpc 實(shí)現(xiàn)了 Hadoop RPC 協(xié)議,支持高吞吐,無(wú)縫對(duì)接現(xiàn)有 HDFS Client。
主要負(fù)責(zé)對(duì) HDFS Client 請(qǐng)求的解析,拆分處理后,將 Namespace 相關(guān)的請(qǐng)求發(fā)送到 DanceNN 集群,文件塊相關(guān)的請(qǐng)求路由到對(duì)應(yīng)的 BSGroup 處理,當(dāng)所有后端請(qǐng)求回復(fù)后生成最終客戶端的響應(yīng)。
DanceProxy 通過一定的請(qǐng)求路由策略來(lái)實(shí)現(xiàn)多組 BSGroup 負(fù)載均衡。
DanceNN 接口
Distributed DanceNN 為文件系統(tǒng)提供主要接口如下:
class DanceNNClient {
public:
DanceNNClient() = default;
virtual ~DanceNNClient() = default;
// ...
// Create directories recursively, eg: MkDir /home/tiger.
ErrorCode MkDir(const MkDirReq& req);
// Delete a directory, eg: RmDir /home/tiger.
ErrorCode RmDir(const RmDirReq& req);
// Change the name or location of a file or directory,
// eg: Rename /tmp/foobar.txt /home/tiger/foobar.txt.
ErrorCode Rename(const RenameReq& req);
// Create a file, eg: Create /tmp/foobar.txt.
ErrorCode Create(const CreateReq& req, CreateRsp* rsp);
// Delete a file, eg: Unlink /tmp/foobar.txt.
ErrorCode Unlink(const UnlinkReq& req, UnlinkRsp* rsp);
// Summarize a file or directory, eg: Du /home/tiger.
ErrorCode Du(const DuReq& req, DuRsp* rsp);
// Get status of a file or directory, eg: Stat /home/tiger/foobar.txt.
ErrorCode Stat(const StatReq& req, StatRsp* rsp);
// List directory contents, eg: Ls /home/tiger.
ErrorCode Ls(const LsReq& req, LsRsp* rsp);
// Create a symbolic link named link_path which contains the string target.
// eg: Symlink /home/foo.txt /home/bar.txt
ErrorCode Symlink(const SymlinkReq& req);
// Read value of a symbolic link.
ErrorCode ReadLink(const ReadLinkReq& req, ReadLinkRsp* rsp);
// Change permissions of a file or directory.
ErrorCode ChMod(const ChModReq& req);
// Change ownership of a file or directory.
ErrorCode ChOwn(const ChOwnReq& req);
// Change file last access and modification times.
ErrorCode UTimeNs(const UTimeNsReq& req, UTimeNsRsp* rsp);
// Set an extended attribute value.
ErrorCode SetXAttr(const SetXAttrReq& req, SetXAttrRsp* rsp);
// List extended attribute names.
ErrorCode GetXAttrs(const GetXAttrsReq& req, GetXAttrsRsp* rsp);
// remove an extended attribute.
ErrorCode RemoveXAttr(const RemoveXAttrReq& req,
RemoveXAttrRsp* rsp);
// ...
};
DanceNN 架構(gòu)
功能介紹
Distributed DanceNN 基于底層分布式事務(wù) KV 存儲(chǔ)來(lái)構(gòu)建,實(shí)現(xiàn)容量和吞吐水平擴(kuò)展,主要功能:
- HDFS 等協(xié)議層的高效實(shí)現(xiàn)
- 服務(wù)無(wú)狀態(tài)化,支持高可用
- 服務(wù)節(jié)點(diǎn)的快速擴(kuò)縮容
- 提供高性能低延遲的訪問
- 對(duì) Namespace 進(jìn)行子樹劃分,充分利用子樹 Cache Locality
- 集群根據(jù)負(fù)載均衡策略對(duì)子樹進(jìn)行調(diào)度
模塊劃分
SDK
緩存集群子樹、NameServer 位置等信息,解析用戶請(qǐng)求并路由到后端服務(wù)節(jié)點(diǎn)上,如果服務(wù)節(jié)點(diǎn)響應(yīng)請(qǐng)求不合法,可能強(qiáng)制 SDK 刷新相應(yīng)的集群緩存。
NameServer
- 作為服務(wù)節(jié)點(diǎn),無(wú)狀態(tài),支持橫向擴(kuò)展
- HDFS/POSIX Protocol Layer:處理客戶端請(qǐng)求,實(shí)現(xiàn)了 HDFS 等協(xié)議層語(yǔ)義,包括路徑解析,權(quán)限校驗(yàn),刪除進(jìn)入回收站等
- Subtree Manager:管理分配給當(dāng)前節(jié)點(diǎn)的子樹,負(fù)責(zé)用戶請(qǐng)求檢查,子樹遷移處理等
- Heartbeater:進(jìn)程啟動(dòng)后會(huì)自動(dòng)注冊(cè)到集群,定期向 NameMaster 更新心跳和負(fù)載信息等
- DistributedLock Manager:基于 LockTable,對(duì)跨目錄 Rename 請(qǐng)求進(jìn)行并發(fā)控制
- Latch Manager:對(duì)所有路徑讀寫請(qǐng)求進(jìn)行加鎖處理,降低底層事務(wù)沖突,支持 Cache 的并發(fā)訪問
- Strong Consistent Cache:維護(hù)了當(dāng)前節(jié)點(diǎn)子樹的 dentry 和 inode 強(qiáng)一致 Cache
- Data Acess Layer:對(duì)底層 KV 存儲(chǔ)的訪問接口的抽象,上層讀寫操作都會(huì)映射到底層 KV 存儲(chǔ)請(qǐng)求
NameMaster
- 作為管理節(jié)點(diǎn),無(wú)狀態(tài),多臺(tái),通過選主實(shí)現(xiàn),由主節(jié)點(diǎn)提供服務(wù)
- AdminTask Scheduler:后臺(tái)管理相關(guān)任務(wù)調(diào)度執(zhí)行,包括子樹切分,擴(kuò)容等
- Load Balancer:根據(jù)集群 NameServer 負(fù)載狀態(tài),通過自動(dòng)子樹遷移來(lái)完成負(fù)載均衡
- NameServer Manager:監(jiān)控 NameServer 健康狀態(tài),進(jìn)行相應(yīng)的宕機(jī)處理
- Statistics:通過消費(fèi)集群變更日志,實(shí)時(shí)收集統(tǒng)計(jì)信息并展示
Distributed Transactional KV Store
- 數(shù)據(jù)存儲(chǔ)層,使用自研的強(qiáng)一致 KV 存儲(chǔ)系統(tǒng) ByteKV
- 提供水平伸縮能力
- 支持分布式事務(wù),提供 Snapshot 隔離級(jí)別
- 支持多機(jī)房數(shù)據(jù)災(zāi)備
BinLog Store
BinLog 存儲(chǔ),使用自研的低延遲分布式日志系統(tǒng) ByteJournal,支持 Exactly Once 語(yǔ)義
從底層 KV 存儲(chǔ)系統(tǒng)中實(shí)時(shí)抽取數(shù)據(jù)變更日志,主要用于 PITR 和其他組件的實(shí)時(shí)消費(fèi)等
GC(Garbage collector)
從 BinLog Store 實(shí)時(shí)消費(fèi)變更日志,讀到文件刪除記錄后,向文件塊服務(wù)下發(fā)刪除命令,及時(shí)清理用戶數(shù)據(jù)
Quota
對(duì)用戶認(rèn)領(lǐng)的目錄,會(huì)周期性全量、實(shí)時(shí)增量的統(tǒng)計(jì)文件總數(shù)和空間總量,容量超限后限制用戶寫
關(guān)鍵設(shè)計(jì)
存儲(chǔ)格式
一般基于分布式存儲(chǔ)的元數(shù)據(jù)格式有兩種方案:
方案一類似 Google Colossus,以全路徑作為 key,元數(shù)據(jù)作為 value 存儲(chǔ),優(yōu)點(diǎn)有:
- 路徑解析非常高效,直接通過用戶請(qǐng)求的 path 從底層的 KV 存儲(chǔ)讀取對(duì)應(yīng) inode 的元數(shù)據(jù)即可
- 掃描目錄可以通過前綴對(duì) KV 存儲(chǔ)進(jìn)行掃描
但是有下列缺點(diǎn):
- 跨目錄 Rename 代價(jià)大,需要對(duì)目錄下的所有文件和目錄進(jìn)行移動(dòng)
- Key 占用的空間相對(duì)比較大
另外一種類似 Facebook Tectonic 和開源的 HopsFS,以父目錄 inode id + 目錄或文件名作為 key,元數(shù)據(jù)作為 value 存儲(chǔ),這種優(yōu)點(diǎn)有:
跨目錄 Rename 非常輕量,只需要修改源和目標(biāo)節(jié)點(diǎn)以及它們的父節(jié)點(diǎn)
掃描目錄同樣可以用父目錄 inode id 作為前綴進(jìn)行掃描
缺點(diǎn)有:
路徑解析網(wǎng)絡(luò)延遲高,需要從 Root 依次遞歸讀取相關(guān)節(jié)點(diǎn)元數(shù)據(jù)直到目標(biāo)節(jié)點(diǎn)
例如:MkDir /tmp/foo/bar.txt,有四次元數(shù)據(jù)網(wǎng)絡(luò)訪問:/、/tmp、/tmp/foo 和 /tmp/foo/bar.txt
層級(jí)越小,訪問熱點(diǎn)越明顯,從而導(dǎo)致底層存儲(chǔ)負(fù)載嚴(yán)重不均衡
例如:每個(gè)請(qǐng)求都要讀取一次根目錄/的元數(shù)據(jù)
考慮到跨目錄 Rename 請(qǐng)求在線上集群占比較高的比例,并且對(duì)于大目錄 Rename 延遲不可控,DanceNN 主要采用第二種方案,方案二的兩個(gè)缺點(diǎn)通過下面的子樹分區(qū)來(lái)解決。
子樹分區(qū)
DanceNN 通過將全局 Namespace 進(jìn)行子樹分區(qū),子樹被指定一個(gè) NameServer 實(shí)例維護(hù)子樹緩存。
子樹緩存
- 維護(hù)這個(gè)子樹下所有目錄和文件元數(shù)據(jù)的強(qiáng)一致緩存
- 緩存項(xiàng)有一定淘汰策略包括 LRU,TTL 等
- 所有請(qǐng)求路徑在這個(gè)子樹下的可以直接訪問本地緩存,未命中需要從底層 KV 存儲(chǔ)進(jìn)行加載并填充緩存
- 通過對(duì)緩存項(xiàng)添加版本的方法來(lái)指定某個(gè)目錄下所有元數(shù)據(jù)的緩存過期,有利于子樹快速遷移清理
利用子樹本地緩存,路徑解析和讀請(qǐng)求基本能夠命中緩存,降低整體延遲,也避免了靠近根節(jié)點(diǎn)訪問的熱點(diǎn)問題。
路徑凍結(jié)
在子樹遷移、跨子樹 Rename 等操作過程中,為了避免請(qǐng)求讀取過期的子樹緩存,需要將相關(guān)的路徑進(jìn)行凍結(jié),凍結(jié)期間該路徑下的所有操作會(huì)被阻塞,由 SDK 負(fù)責(zé)重試,整個(gè)流程在亞秒級(jí)內(nèi)完成
路徑凍結(jié)后會(huì)將該目錄下的所有緩存項(xiàng)設(shè)置為過期
凍結(jié)的路徑信息會(huì)被持久化到底層的 KV 存儲(chǔ),重啟后會(huì)重新加載刷新
子樹管理
子樹管理主要由 NameMaster 負(fù)責(zé):
- 支持通過管理員命令進(jìn)行手動(dòng)子樹分裂和子樹遷移
- 定期監(jiān)控集群節(jié)點(diǎn)的負(fù)載狀態(tài),動(dòng)態(tài)調(diào)整子樹在集群分布
- 定期統(tǒng)計(jì)子樹的訪問吞吐,提供子樹分裂建議,未來(lái)支持啟發(fā)式算法選擇子樹完成分裂
舉個(gè)例子,如下圖:
目錄 / 調(diào)度到 NameServer #1,目錄 /b 調(diào)度到 NameServer #2,目錄 /b/d 調(diào)度到 NameServer #3
- MkDir /a 請(qǐng)求發(fā)送到 NameServer #1,發(fā)送到其他 NameServer 會(huì)校驗(yàn)失敗,返回重定向錯(cuò)誤,讓 SDK 刷新緩存重試
- Stat /b/d 請(qǐng)求將會(huì)發(fā)送到 NameServer #3,直接讀取本地緩存即可
- ChMod /b 請(qǐng)求將會(huì)發(fā)送到 NameServer #2,更新 b 目錄的權(quán)限信息并持久化,對(duì) NameServer #2 和 NameServer #3 進(jìn)行 Cache 刷新,最后回復(fù)客戶端
并發(fā)控制
底層 KV 存儲(chǔ)系統(tǒng) ByteKV 支持單條記錄的 Put、Delete 和 Get 語(yǔ)義,其中 Put 支持 CAS 語(yǔ)義,還提供多條記錄的原子性寫入接口 WriteBatch。
客戶端寫操作一般會(huì)涉及多個(gè)文件或目錄的更新,例如 Create /tmp/foobar.txt 會(huì)更新 /tmp 的 mtime 記錄、創(chuàng)建 foobar.txt 記錄等,DanceNN 會(huì)將多條記錄的更新轉(zhuǎn)換成 ByteKV WriteBatch 請(qǐng)求,保證了整個(gè)操作的原子性。
分布式鎖管理
雖然 ByteKV 提供事務(wù)的 ACID 屬性且支持 Snapshot 隔離級(jí)別,但是對(duì)于多個(gè)并發(fā)寫操作如果涉及底層數(shù)據(jù)變更之間沒有 Overlap 的話,仍然會(huì)有 Write Skew 異常,這可能導(dǎo)致元數(shù)據(jù)完整性被破壞。
其中一個(gè)例子是并發(fā) Rename 異常,如下圖:
單個(gè) Rename /a /b/d/e 操作或者單個(gè) Rename /b/d /a/c 操作都符合預(yù)期,但是如果兩者并發(fā)執(zhí)行(且都能成功),可以導(dǎo)致目錄 a,c,d,e 的元數(shù)據(jù)出現(xiàn)環(huán),破壞了目錄樹結(jié)構(gòu)的完整性。
我們選擇使用分布式鎖機(jī)制來(lái)解決,對(duì)于可能導(dǎo)致異常的并發(fā)請(qǐng)求進(jìn)行串行處理,基于底層 KV 存儲(chǔ)設(shè)計(jì)了 Lock Table,支持對(duì)于元數(shù)據(jù)記錄進(jìn)行加鎖,提供持久性、水平擴(kuò)展、讀寫鎖、鎖超時(shí)清理和冪等功能。
Latch 管理
為了支持對(duì)子樹內(nèi)部緩存的并發(fā)訪問和更新,維護(hù)緩存的強(qiáng)一致,會(huì)對(duì)操作涉及的緩存項(xiàng)進(jìn)行加鎖(Latch),例如:Create /home/tiger/foobar.txt,會(huì)先對(duì) tiger 和 foobar.txt 對(duì)應(yīng)的緩存項(xiàng)加寫 Latch,再進(jìn)行更新操作;Stat /home/tiger 會(huì)對(duì) tiger 緩存項(xiàng)加讀 Latch,再進(jìn)行讀取。
為了提升服務(wù)的整體性能做了非常多的優(yōu)化,下面列兩個(gè)重要優(yōu)化:
熱點(diǎn)目錄下大量創(chuàng)建和刪除文件
例如:有些業(yè)務(wù)像大型 MapReduce 任務(wù)會(huì)在相同目錄一下子創(chuàng)建幾千個(gè)目錄或文件。
一般來(lái)說根據(jù)文件系統(tǒng)語(yǔ)義創(chuàng)建文件或目錄都會(huì)更新父目錄相關(guān)的元數(shù)據(jù)(如 HDFS 協(xié)議更新父目錄的 mtime,POSIX 要求更新父目錄 mtime,nlink 等),這就導(dǎo)致同目錄下創(chuàng)建文件操作對(duì)父目錄元數(shù)據(jù)的更新產(chǎn)生嚴(yán)重的事務(wù)沖突,另外底層 KV 存儲(chǔ)系統(tǒng)是多機(jī)房部署,機(jī)房延遲更高,進(jìn)一步降低了這些操作的并發(fā)度。
DanceNN 對(duì)于熱點(diǎn)目錄下的創(chuàng)建刪除等操作只加讀 latch,之后放到一個(gè) ExecutionQueue 中, 由一個(gè)的輕量 Bthread 協(xié)程進(jìn)行后臺(tái)異步串行處理,將這些請(qǐng)求組合成一定大小的 Batch 發(fā)送給底層的 KV 存儲(chǔ),這樣避免了底層事務(wù)沖突,提升幾十倍吞吐。
請(qǐng)求間的相互阻塞
有些場(chǎng)景可能會(huì)導(dǎo)致目錄的更新請(qǐng)求阻塞了這個(gè)目錄下的其他請(qǐng)求,例如:
SetXAttr /home/tiger 和 Stat /home/tiger/foobar.txt 無(wú)法并發(fā)執(zhí)行,因?yàn)榈谝粋€(gè)對(duì) tiger 緩存項(xiàng)加寫 Latch,后面請(qǐng)求讀 tiger 元數(shù)據(jù)緩存項(xiàng)會(huì)被阻塞。
DanceNN 使用類似 Read-Write-Commit Lock 實(shí)現(xiàn)對(duì) Latch 進(jìn)行管理,每個(gè) Latch 有 Read、Write 和 Commit 三種類型,其中 Read-Read、Read-Write 請(qǐng)求可以并發(fā),Write-Write、Any-Commit 請(qǐng)求互斥。
基于這種實(shí)現(xiàn),上述兩個(gè)請(qǐng)求能夠在保證數(shù)據(jù)一致性的情況下并發(fā)執(zhí)行。
請(qǐng)求冪等
當(dāng)客戶端因?yàn)槌瑫r(shí)或網(wǎng)絡(luò)故障而失敗時(shí),進(jìn)行重試會(huì)導(dǎo)致同一個(gè)請(qǐng)求到達(dá) Server 多次。有些請(qǐng)求如 Create 或者 Unlink 是非冪等的請(qǐng)求,對(duì)于這樣的操作,需要在 Server 端識(shí)別以保證只處理一次。
在單機(jī)場(chǎng)景中,我們通常使用一個(gè)內(nèi)存的 Hash 表來(lái)處理重試請(qǐng)求,Hash 表的 key 為 {ClientId, CallId},value 為 {State, Response},當(dāng)請(qǐng)求 A 到來(lái)之后,我們會(huì)插入 {Inprocess State} 到 Hash 表;這之后,如果重試請(qǐng)求 B 到來(lái),會(huì)直接阻塞住請(qǐng)求 B,等待第請(qǐng)求 A 執(zhí)行成功后喚醒 B。當(dāng) A 執(zhí)行成功之后,我們會(huì)將 {Finished State, Response} 寫到 Hash 表并喚醒 B,B 會(huì)看到更新的 Finished 狀態(tài)后響應(yīng)客戶端。
類似的 DanceNN 寫請(qǐng)求會(huì)在底層的 WriteBatch 請(qǐng)求里加一條 Request 記錄,這樣可以保證后續(xù)的重試請(qǐng)求操作一定會(huì)在底層出現(xiàn)事務(wù) CAS 失敗,上層發(fā)現(xiàn)后會(huì)讀取該 Request 記錄直接響應(yīng)客戶端。另外,何時(shí)刪除 Request 記錄呢,我們會(huì)給記錄設(shè)置一個(gè)相對(duì)較長(zhǎng)時(shí)間的 TTL,可以保證該記錄在 TTL 結(jié)束之后一定已經(jīng)處理完成了。
性能測(cè)試
壓測(cè)環(huán)境:
DanceNN 使用 1 臺(tái) NameServer,分布式 KV 存儲(chǔ)系統(tǒng)使用 100+臺(tái)數(shù)據(jù)節(jié)點(diǎn),三機(jī)房五副本部署(2 + 2 + 1),跨機(jī)房延遲 2-3ms 左右,客戶端通過 NNThroughputBenchmark 元數(shù)據(jù)壓測(cè)腳本分別使用單線程和 6K 線程并發(fā)進(jìn)行壓測(cè)。
截取部分延遲和吞吐數(shù)據(jù)如下:
測(cè)試結(jié)果表明:
- 讀吞吐:單臺(tái) NameServer 支持讀請(qǐng)求 500K,隨著 NameServer 數(shù)量的增加吞吐基本能夠線性增長(zhǎng);
- 寫吞吐:目前依賴底層 KV 存儲(chǔ)的寫事務(wù)性能,隨著底層 KV 節(jié)點(diǎn)數(shù)據(jù)量的增加也能夠?qū)崿F(xiàn)線性增長(zhǎng)。