基於超大規模集群的在地存儲系統優化

尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️

加入LINE好友

關注獲得更多內容

點擊「原文鏈接」查看大會詳情。

劉洪通,京東大數據平台基礎架構技術專家。

7年分布式存儲系統研發經驗,精通Linux存儲系統、Ext4文件系統,Hadoop、ceph、sheepdog等開源項目contributor。目前專注於Hadoop HDFS、Linux Kernel開發。

京東大數據平台部一直致力於優化基礎架構,為用戶提供穩定、高可靠、高性能、高利用率的超大規模Hadoop集群。本文與大家分享大規模分布式存儲集群的基石——本地存儲系統優化的點點滴滴。在介紹主要內容前,先熟悉一下高可用Hadoop分布式文件系統HDFS的核心架構,如下圖:

基於超大規模集群的本地存儲系統優化

HDFS將大文件切分為多個數據塊(Block)存儲到多個DataNode(以下簡稱DN)。NameNode(以下簡稱NN)主要用於儲存分布式文件系統的元數據,元數據包括文件系統目錄樹、文件與數據塊的對應關係、數據塊與DN 的對應關係。NN除了存儲元數據外,還需要管理大量的DN,同時要對外提供元數據的服務接口。DN 是用於存儲數據塊的節點,HDFS上的所有文件的數據都存儲在DN上。HDFS上文件的訪問文件數據的流程,簡單來說,是Client先從NN獲取到文件數據所在DN位置,然後與DN通信訪問實際的數據。為了保證NN的高可用,衍生出Active NN和Standby NN,依托ZooKeeper(以下簡稱ZK)做到NN的狀態切換。ActiveNN對外提供服務,Standby NN在Active NN發生故障時切換為Active NN繼續對外服務。Active NN響應Client的修改元數據請求,需要記錄元數據的操作日志(以下稱為EditLog),為了提升EditLog的一致性和可靠性,HDFS設計了JournalNode(以下簡稱JN)集群,每一次元數據的修改都要同步保存到JN中。Standby NN是Active NN的後備,他從JN持續拉取EditLog,並將其合併到本地的元數據結構中,隨時待命準備接收Active NN的工作。本地存儲系統優化是在分析HDFS的不同核心組件和組件之間的I/O模型的前提下,為達到高吞吐或高ops的需求,而提出針對性的優化方案。共分為四個部分:

  • DN本地文件系統元數據與數據的緩存分離,為大家介紹一種更適合單機海量存儲的文件系統緩存方案
  • 本地存儲優化案例分析,囊括了我們在實踐中遇到的幾個經典案例
  • 本地存儲系統性能實時監控,工欲善其事必先利其器,只有iostat是不夠的
  • 磁盤故障監控與自動化運維

>>>> DN本地文件系統元數據與數據的緩存分離
>>>> 地存儲架構

在介紹緩存分離的方案之前,以傳統機械硬碟為例,大致介紹本地存儲系統的架構,如下圖:

基於超大規模集群的本地存儲系統優化

Java應用程序,通過文件相關接口調用Java Native Interface(圖中簡稱JNI),再調用C Library暴露的系統調用接口,觸發軟中斷嵌入到Kernel Space,由Kernel代替應用程序進程執行VFS相應的系統調用處理函數;VFS是虛擬文件系統,是不同類型文件系統的抽象,我們常用的文件系統如Ext4、xfs等。為了加速文件系統的性能,Linux提供了Page Cache機制,這也是本部分的主角了。

再之下是block層,我們看到的磁盤比如/dev/sda,就是block層呈現的;大家耳熟能詳的電梯調度算法,就是各類I/O Scheduler的鼻祖。我們聽說過的硬碟接口比如SATA、SAS等都受SCSI框架的統一管理,不同的廠商向Kernel社區貢獻了自有的磁盤驅動。Hardware只需了解一些特性。

言歸正傳,本節的主角是Page Cache

>>>> PageCache相信很多人會有這樣的經歷,在定位緊急問題時打開一個很大的日志文件往往是一個漫長的過程,很是讓人著急;但是在第二次打開時基本是秒開。這要歸功於Linux kernel提供的Page Cache機制。Page Cache一般又分為兩部分:Buffer緩存文件元數據,Cache緩存文件數據。此處的元數據是一個統稱,包括了文件系統的元數據、文件的索引節點inode、目錄項Dentry等。inode保存了文件長度、屬主、創建日期等關鍵信息,但不包括文件名。目錄是一種特殊的文件,其內容即為Dentry,用來保存目錄內的子目錄和文件inode與文件名稱對應關係。訪問一個文件是一個很耗時的過程,涉及到多次從磁盤讀取元數據和數據。

從下圖中我們可以看出,Buffer和Cache總共占用約50G記憶體,正符合Linux的一個設計原則:你用或者不用,它都在那里,不用白不用。在應用需要這部分記憶體空間時,Page Cache會根據精心設計的記憶體回收算法釋放記憶體頁。

基於超大規模集群的本地存儲系統優化

>>>> 技術痛點

在超大規模集群中,單個DN多則承擔千萬個Block,在這樣的極端場景下,Buffer和Cache的加速效果越來越差。主要原因是記憶體太小,磁盤嫌少。從容量比來看,記憶體幾十GB/元數據總量幾十GB/數據總量幾十TB,記憶體大小和數據總量對比約為1:1000,在多租戶隨機讀寫情況下,數據熱度極度分散,Page Cache的命中率比較低。

再加上DataNode的掃盤/錯誤檢查/du等操作需要訪問千萬級文件和目錄的元數據,元數據多數為小塊I/O,常見的是4KiB,大部分元數據因無法命中Buffer而需要從磁盤讀取,搶占磁盤IOPS,嚴重影響DN對外服務。再加上文件系統元數據和數據一般非連續存儲,因此元數據讀寫加劇了磁頭的抖動,破壞了數據讀寫的連續性,最終導致磁盤吞吐遠低於預期。從下圖中,可以看出磁盤繁忙度持續在100%,本來有100+MB/s帶寬的硬碟實際上只發揮出不足4MB/S。

基於超大規模集群的本地存儲系統優化

那如何來緩解這樣的問題呢,我們最初想到將元數據和數據從物理上分離開,即把元數據放到更快更強的非易失閃存,但是業界還沒有足夠成熟穩定的開源文件系統,因為文件系統是數據的根基,要十分謹慎小心。後來我們注意到記憶體容量和元數據總量旗鼓相當,那麼我們能不能用Page Cache盡可能多的緩存元數據,這樣我們不會觸及文件系統的任何修改,應用也無感知。

Linux Kernel並不提供機制讓系統管理員來控制Buffer和Cache的比例,而且前面的示例中也體現出Buffer和Cache的差距,那麼想要達到盡可能多的緩存元數據的目的,只能從Page Cache的原理入手,修改Buffer和Cache的機制。

Cache與Buffer實際上共用File LRU鏈表,使用相同的記憶體回收策略。這也是為什麼Buffer總是比Cache小很多的原因,因為搶不過嘛。

>>>> 優化方案

要單獨控制Buffer,需要將Buffer從File LRU鏈表中獨立,新建Buffer LRU鏈表及回收策略:

基於超大規模集群的本地存儲系統優化

1、優先回收Cache

2、按比例回收Cache和Buffer

如果Cache不滿足回收條件,則按比例同時強制回收Buffer和Cache,可以配置100%回收Cache,不回收Buffer。

3、限制Buffer的記憶體占比

不回收Buffer易造成其記憶體占用量的持續增加,影響記憶體分配效率,因此需限制Buffer占總記憶體的比例,需要合理規劃應用程序和Buffer的占比,防止OOM。如果Buffer超出閾值,則強制回收一部分。

>>>> 優化效果

優化前,Cache平均占用32G,Buffer平均占用8G,總占用約41G;

基於超大規模集群的本地存儲系統優化

優化後,Cache平均占用13G,Buffer平均占用29G,總占用約42G;

基於超大規模集群的本地存儲系統優化

對比優化前後,說明在不擠壓應用程序可用記憶體的情況下,Page Cache總體做到了我們的優化目標–更多的緩存元數據。

下圖橫坐標代表讀請求的大小,單位為KB,縱坐標為讀次數。在1分鐘統計時間內,4KB小塊讀的次數下降了27倍。

基於超大規模集群的本地存儲系統優化

優化後,在DataNode的掃盤/錯誤檢查/du等操作期間,磁盤繁忙度未出現突出變化,磁盤讀吞吐保持平穩,在磁盤不繁忙的情況下,保持在10MB/s。

>>>> 本地存儲優化案例分析>>>> 優化Edit log同步

如有必要,請先回顧文章開始對HDFS架構的描述,此處不再贅述。

1、問題描述

NameNode在響應Client修改文件元數據的請求時,為保證元數據的原子性,需同步地向JournalNodes傳輸Editlog(小文件),JournalNode在接收到Editlog後同步地寫入本地文件系統,同時NameNode也需要將Editlog寫入本地文件系統。為了保證每一條Editlog寫入磁盤,NameNode和JournalNode在寫入Editlog時調用flushAndSync(),如下圖所示,此過程消耗了大部分時間。雖然EditLog可以批量傳輸、批量寫入文件系統,但是如果這個過程耗時過長,還是會嚴重影響NameNode的OPS。

基於超大規模集群的本地存儲系統優化

NameNode有相應的指標來衡量同步EditLog的平均延遲時間,下圖是優化前的記錄,Syncs時間普遍大於20ms,而且波動很大,對QoS會產生嚴重的影響。

基於超大規模集群的本地存儲系統優化

2、問題分析

從時延預估角度來看,在不考慮網路延遲的情況下,文件系統一次寫請求的延遲時間大於20ms,是不符合預期的。從Java到kernel一步步來追蹤問題的根本原因,我們發現是fdatasync系統調用耗費了大部分時間。

基於超大規模集群的本地存儲系統優化

  1. # strace -f -p <pid> -T -e trace=fdatasync
  2. [pid 18493] fdatasync(267) = 0 <0.043851>
  3. [pid 18495] fdatasync(267) = 0 <0.036754>
  4. [pid 18496] fdatasync(267) = 0 <0.043374>

3、文件系統性能測試

為了驗證文件系統的IOPS,通過fio測試文件系統4k sync write的性能,IOPS=25,換算為平均延遲時間約為40ms。

  1. file1: (g=0): rw=randwrite, bs=(R) 4096B-4096B, (W)4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1
  2. fio-3.1
  3. Jobs: 1 (f=1): [w(1)][9.3%][r=0KiB/s,w=100KiB/s][r=0,w=25IOPS][eta 01h:07m:36s]

4、分析IO流程

通過blktrace分析I/O具體流程,某次flush請求執行時間約42ms,與fdatasync的時延吻合。

  1. 8,160 29 531 1.052751598 1156 AFWFS 5859838192 + 8 <- (8,161) 5859836144
  2. 8,161 29 532 1.052751978 1156 QFWFS 5859838192 + 8 [jbd2/sdk1-8]
  3. 8,161 29 533 1.052752704 1156 GFWFS 5859838192 + 8 [jbd2/sdk1-8]
  4. 8,161 29 534 1.052753334 1156 IFWFS 5859838192 + 8 [jbd2/sdk1-8]
  5. 8,161 29 535 1.052754684 1156 DFWFS 5859838192 + 8 [jbd2/sdk1-8]
  6. 8,161 28 422 1.094201746 1156 CFWFS 5859838192 + 8 [0]

FWFS類型的I/O操作一般由Flush引發,Flush一般由文件系統的barrier功能觸發,檢查文件系統掛載參數,未指定關閉barrier,也就是說barrier默認是開啟的。

  1. /dev/sdk1 on /data10 type ext4(rw,relatime,data=ordered

5、barrier為何物

文件系統為了在內核崩潰、異常斷電等異常情況下保證完整性,必須保證寫數據時將數據、元數據、日志寫入磁盤介質。然而一般情況下,即使是同步寫,數據也不會立即寫入到磁盤中,而是先寫入到磁盤自身的緩存中。barrier是保護文件系統完整性的安全特性。

barrier會觸發flush操作,將磁盤緩存內的臟數據刷回磁盤介質。在flush操作完成前,磁盤無法處理後來的I/O請求;同時現代磁盤緩存越來越大,導致flush變成一個漫長的過程。總之,barrier以犧牲文件系統性能為代價,換取文件系統完整性。

6、barrier可以關閉嗎?

若RAID卡或磁盤本身支持掉電保護,則可關閉barrier功能;

若RAID卡和磁盤不支持或關閉寫緩存,也可關閉barrier功能

7、優化方案

文件系統禁用barrier,掛載參數添加nobarrier

  1. /dev/sdl1 on /data11 type ext4 (rw,relatime,nobarrier,data=ordered)

關閉磁盤寫緩存

hdparm -W 0 /dev/sdX

關閉RAID卡寫緩存

通過RAID卡管理工具關閉寫緩存

8、優化效果

通過fio測試文件系統4k sync write的性能, IOPS提升至180左右

  1. file1: (g=0): rw=randwrite, bs=(R) 4096B-4096B, (W)4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1
  2. fio-3.1
  3. bs: 1 (f=1): [w(1)][11.2%][r=0KiB/s,w=748KiB/s][r=0,w=187IOPS][eta 02m:39s]

NameNode的Syncs指標下降到10ms以下。

基於超大規模集群的本地存儲系統優化

>>>> DataNode目錄結構優化

1、龐大的目錄規模

HDFS Block保存在DataNode,由Block id計算出最終block保存在哪個子目錄。

基於超大規模集群的本地存儲系統優化

單個DataNode的目錄數估算:256 x 256 x NS個數 x Disk個數

以10個Namespace,12個磁盤為例,目錄總個數約為:

256 x 256x 10 x 12 = 7864320 786

2、易導致I/O瓶頸

DataNode的Block report、DU、DirectoryScanner等過程會掃描所有subdir目錄,因目錄數量龐大,操作系統的Buffer Cache不足以緩存所有目錄項及索引節點,因此需要從磁盤讀取。因掃描過程持續時間較長,磁盤壓力過大,嚴重影響DataNode響應Client的IO請求。

3、優化方案

目錄結構從256 x 256升級為32 x 32

以10個Namespace,12個磁盤為例,目錄總個數約為:

32x 32 x 10 x 12 = 122880 12

4、優化效果

以DataNode全量匯報Block時間為例,時間由接近1小時,下降為78秒。

NS*Disk

10*12

10*12

目錄結構

256×256

32×32

總目錄數

7864320

122880

report耗時

0:57:54

0:01:18

>>>> 本地存儲系統性能實時監控

我們在遇到很多案例之後發現Linux kernel沒有很好地提供我們想要的工具或者接口來實時的監控存儲系統的性能,這使我們在分析I/O問題時感覺非常麻煩。

為了更方便、直觀、實時地監控存儲系統的I/O行為和性能表現,需要在kernel中開發相應的功能接口,暴露給用戶。

不同類型的I/O size實時統計:

基於超大規模集群的本地存儲系統優化

不同類型的I/O Latency實時統計:

基於超大規模集群的本地存儲系統優化

I/O Stage Latency實時統計:

基於超大規模集群的本地存儲系統優化

queue: I/O 到達block層

getrq:I/O分配到request

sleep:request不足,I/O需等待

merge:I/O合併到已分配的request

insert:request插入到request隊列

issue:request發送到設備驅動

complete:request完成

基於超大規模集群的本地存儲系統優化

S2G:如果出現多次,說明請求隊列過小或並發度過高

Q2M:多次I/O合併為一次,有益於提升磁盤性能

D2C:設備驅動及以下協議棧處理I/O的效率

Q2C:整個I/O協議棧的處理效率

>>>> 磁盤故障監控與自動化運維

在超大規模集群中,磁盤和raid卡的硬件故障是很頻繁的,會嚴重的影響QoS,因此需要及時發現、及時報警、及時報修。Kernel層及驅動層是感知磁盤故障的第一線。

故障類型與自動化運維策略:

故障類型

自動踢盤

自動報警

自動報修

磁盤介質損壞

磁盤鏈接錯誤

磁盤IO響應慢

磁盤reset超時

raid卡IO響應慢

raid卡頻繁reset

raid卡故障

轉載自:京東技術。

轉載:意向文章下方留言。