解密 雲HBase 冷熱分離技術原理

前言

HBase是當下流行的一款海量數據存儲的分佈式數據庫。每每海量數據存儲會涉及到一個成本問題,如何下降成本。常見的方案就是經過冷熱分離來治理數據。冷數據能夠用更高的壓縮比算法(ZSTD),更低副本數算法(Erasure Coding),更便宜存儲設備(HDD,高密集型存儲機型)。算法

HBase冷熱分離常看法決方案

1.主備集羣

備(冷)集羣用更廉價的硬件,主集羣設置TTL,這樣當數據熱度退去,冷數據天然只在冷集羣有。數據庫

優勢:方案簡單,現成內核版本都能搞
缺點:維護開銷大,冷集羣CPU存在浪費數據結構

1.x版本的HBase在不改內核狀況下,基本只能有這種方案。架構

2.HDFS Archival Storage + HBase CF-level Storage Policy

須要在2.x以後的版本才能使用。結合HDFS分層存儲能力 + 在Table層面指定數據存儲策略,實現同集羣下,不一樣表數據的冷熱分離。分佈式

優勢:同一集羣冷熱分離,維護開銷少,更靈活的配置不一樣業務表的策略
缺點:磁盤配比是個很大的問題,不一樣業務冷熱配比是不同的,比較難整合在一塊兒,一旦業務變更,集羣硬件配置是無法跟着變的。oop

雲HBase冷熱分離解決方案

上述2套方案都不是最好的方案,對於雲上來講。第一套方案就不說了,客戶搞2個集羣,對於數據量不大的客戶其實根本降不了成本。第二套方案,雲上客戶千千萬,業務各有各樣,磁盤配置是很難定製到合適的狀態。性能

雲上要作 cloud native 的方案,必須知足同集羣下,極致的彈性伸縮,才能真正意義上作到產品化。雲上低成本,彈性存儲,只有OSS了。因此很天然的想到以下架構:測試

實現這樣的架構,最直接的想法是直接改HBase內核:1)增長冷表數據標記 2)根據標記增長寫OSS的IO路徑。優化

這樣作的缺陷很是明顯,你的外部系統(如:備份恢復,數據導入導出)很難兼容這些改動,他們須要感知哪些是冷文件得去OSS哪一個位置讀,哪些是熱文件得去部署在雲盤上的HDFS上讀。這些本質上都是一些重複的工做,因此從架構設計角度來看必須抽象出一層。這一層能讀寫HDFS文件,讀寫OSS文件,感知冷熱文件。這一層也就是我最後設計出的ApsaraDB FileSystem,實現了Hadoop FileSystem API。對於HBase,備份恢復,數據導入導出等系統只要替換原先FileSystem的實現便可得到冷熱分離的功能。spa

下面將詳細闡述,這套FileSystem設計的細節與難點。

ApsaraDB FileSystem 設計

核心難點A

1.OSS並不是文件系統

OSS並非一個真正意義上的文件系統,它僅僅是兩級映射 bucket/object,因此它是對象存儲。你在OSS上看到相似這樣一個文件:
/root/user/gzh/file。你會覺得有3層目錄+1個文件。實際上只有一個對象,這個對象的key包含了/字符罷了。

這麼帶來的一個問題是,你要想在其上模擬出文件系統,你必須先能建立目錄。很天然想到的是用/結尾的特殊對象表明目錄對象。Hadoop社區開源的OssFileSystem就是這麼搞的。有了這個方法,就能判斷到底存不存在某個目錄,能不能建立文件,否則會憑空建立出一個文件,而這個文件沒有父目錄。

固然你這麼作依然會有問題。除了開銷比較大(建立深層目錄屢次HTTP請求OSS),最嚴重的是正確性的問題。試想一下下面這個場景:
把目錄/root/user/source rename 成 /root/user/target。這個過程除了該目錄,它底下的子目錄,子目錄裏的子文件都會跟着變。相似這樣:/root/user/source/file => /root/user/target/file。這很好理解,文件系統就是一顆樹,你rename目錄,實際是把某顆子樹移動到另外一個節點下。這個在NameNode裏的實現也很簡單,改變下樹結構便可。

可是若是是在OSS上,你要作rename,那你不得不遞歸遍歷/root/user/source把其下全部目錄對象,文件對象都rename。由於你無法經過移動子樹這樣一個簡單操做一步到位。這裏帶來的問題就是,假設你遞歸遍歷到一半,掛了。那麼就可能會出現一半目錄或文件到了目標位置,一半沒過去。這樣rename這個操做就不是原子的了,原本你要麼rename成功,整個目錄下的內容到新的地方,要麼沒成功就在原地。因此正確性會存在問題,像HBase這樣依賴rename操做將臨時數據目錄移動到正式目錄來作數據commit,就會面臨風險。

2.OSS rename實則是數據拷貝

前面咱們提到了rename,在正常文件系統中應該是一個輕量級的,數據結構修改操做。可是OSS並無rename這個操做實際上,rename得經過 CopyObject + DeleteObject 兩個操做完成。首先是copy成目標名字,而後delete掉原先的Object。這裏有2個明顯的問題,一個是copy是深度拷貝開銷很大,直接會影響HBase的性能。另外一個是rename拆分紅2個操做,這2個操做是無法在一個事物裏的,也就是說:可能存在copy成功,沒delete掉的狀況,此時你須要回滾,你須要delete掉copy出來的對象,可是delete依然可能不成功。因此rename操做自己實現上,正確性就難以保證了。

解決核心難點A

解決上面2個問題,須要本身作元數據管理,即至關於本身維護一個文件系統樹,OSS上只放數據文件。而由於咱們環境中仍然有HDFS存儲在(爲了放熱數據),因此直接複用NameNode代碼,讓NodeNode幫助管理元數據。因此總體架構就出來了:

ApsaraDB FileSystem(如下簡稱ADB FS)將雲端存儲分爲:主存(PrimaryStorageFileSystem)和 冷存(ColdStorageFileSystem)。由ApsaraDistributedFileSystem類(如下簡稱ADFS)負責管理這兩類存儲文件系統,而且由ADFS負責感知冷熱文件。

ApsaraDistributedFileSystem: 總入口,負責管理冷存和主存,數據該從哪裏讀,該寫入哪裏(ADFS)。
主存:PrimaryStorageFileSystem 默認實現是 DistributedFileSystem(HDFS)
冷存:ColdStorageFileSystem 默認實現是 HBaseOssFileSystem(HOFS) ,基於OSS實現的Hadoop API文件系統,能夠模擬目錄對象單獨使用,也能夠只做爲冷存讀寫數據,相比社區版本有針對性優化,後面會講。

具體,NameNode如何幫助管理冷存上的元數據,很簡單。ADFS在主存上建立同名索引文件,文件內容是索引指向冷存中對應的文件。實際數據在冷存中,因此冷存中的文件有沒有目錄結構無所謂,只有一級文件就行。咱們再看下一rename操做過程,就明白了:

rename目錄的場景也同理,直接在NameNode中rename就行。對於熱文件,至關於所有代理HDFS操做便可,冷文件要在HDFS上建立索引文件,而後寫數據文件到OSS,而後關聯起來。

核心難點B

引入元數據管理解決方案,又會遇到新的問題:是索引文件和冷存中數據文件一致性問題。
咱們可能會遇到以下場景:

  • 主存索引文件存在,冷存數據文件不存在
  • 冷存數據文件存在,主存索引文件不存住
  • 主存索引文件信息不完整,沒法定位冷存數據文件

先排除BUG或者人爲刪除數據文件因素,上訴3種狀況都會因爲程序crash產生。也就是說咱們要想把法,把生成索引文件,寫入並生成冷數據文件,關聯,這3個操做放在一個事物裏。這樣才能具有原子性,才能保證要麼建立冷文件成功,那麼索引信息是完整的,也指向一個存在的數據文件。要麼建立冷文件失敗(包括中途程序crash),永遠也見不到這個冷文件。

解決核心難點B

核心思想是利用主存的rename操做,由於主存的rename是具有原子性的。咱們先在主存的臨時目錄中生產索引文件,此時索引文件內容已經指向冷存中的一個路徑(可是實際上這個路徑的數據文件還沒開始寫入)。在冷存完成寫入,正確close後,那麼此時咱們已經有完整且正確的索引文件&數據文件。而後經過rename一把將索引文件改到用戶實際須要寫入到目標路徑,便可。

若是中途進程crash,索引文件要麼已經rename成功,要麼索引文件還在臨時目錄。在臨時目錄咱們認爲寫入沒有完成,是失敗的。而後咱們經過清理線程,按期清理掉N天之前臨時目錄的文件便可。因此一旦rename成功,那目標路徑上的索引文件必定是完整的,必定會指向一個寫好的數據文件。

爲何咱們須要先寫好路徑信息在索引文件裏?由於若是先寫數據文件,在這個過程當中crash了,那咱們是沒有索引信息指向這個數據文件的,從而形成相似「內存泄漏」的問題。

冷熱文件標記

對於主存,須要實現給文件冷熱標記的功能,經過標記判斷要打開怎樣的數據讀寫流。這點NameNode能夠經過給文件設置StoragePolicy實現。這個過程就很簡單了,不詳細贅述,下面說HBaseOssFileSystem寫入優化設計。

HBaseOssFileSystem 寫入優化

在說HOFS寫設計以前,咱們先要理解Hadoop社區版本的OssFileSystem設計(這也是社區用戶能直接使用的版本)。

社區版本寫入設計

Write -> OutputStream -> disk buffer(128M) -> FileInputStream -> OSS

這個過程就是先寫入磁盤,磁盤滿128M後,將這128M的block包裝成FileInputStream再提交給OSS。這個設計主要是考慮了OSS請求成本,OSS每次請求都是要收費的,可是內網流量不計費。若是你1KB寫一次,費用就很高了,因此必須大塊寫。並且OSS大文件寫入,設計最多讓你提交10000個block(OSS中叫MultipartUpload),若是block過小,那麼你能支持的最大文件大小也是受限

因此要攢大buffer,另一個因素是Hadoop FS API提供的是OutputStream讓你不斷write。OSS提供的是InputStream,讓你提供你要寫入內容,它本身不斷讀取。這樣你必然要經過一個buffer去轉換。

這裏會有比較大的一個問題,就是性能慢。寫入磁盤,再讀取磁盤,多了這麼兩輪會比較慢,雖然有PageCache存在,讀取過程不必定有IO。那你確定想,用內存當buffer不就行了。內存當buffer的問題就是前面說的,有費用,因此buffer不能過小。因此你每一個文件要開128M內存,是不可能的。更況且當你提交給OSS的時候,你要保證能繼續寫入新數據,你得有2塊128M內存滾動,這個開銷幾乎不能接受。

HBaseOssFileSystem 寫入設計

咱們既要解決費用問題,也要解決性能問題,同時要保證開銷很低,看似不可能,那麼怎麼作呢?

這裏要利用的就是這個InputStream,OSS讓你提供InputStream,並從中讀取你要寫入的內容。那麼咱們能夠設計一個流式寫入,當我傳入這個InputStream給OSS的時候,流中並不必定得有數據。此時OSS調read讀取數據會block在read調用上。等用戶真的寫入數據,InputStream中才會有數據,這時候OSS就能順利讀到數據。當OSS讀了超過128M數據時候,InputStream會自動截斷,返回EOF,這樣OSS會覺得流已經結束了,那麼這塊數據就算提交完成。

因此咱們本質只要開發這麼一個特殊的InputStream便可。用戶向Hadoop API提供的OutputStream中寫入數據,數據每填滿一個page(2M)就發給InputStream讓其可讀取。OuputStream至關於生產者,InputStream至關於消費者。這裏的內存開銷會很是低,由於生產者速度和消費者速度相近時,也就2個page的開銷。最後將這整套實現封裝成OSSOutputStream類,當用戶要寫入冷文件時,實際提供的是OSSOutputStream,這裏面就包含了這個特殊InputStream的控制過程。

固然實際生產中,咱們會對page進行控制,每一個文件設置最多4個page。而且這4個page循環利用,減小對GC對影響。因此最後咱們獲得下面一個環形緩衝的寫入模式:

性能對比1:社區版本 vs 雲HBase版

由於不用寫磁盤,因此寫入吞吐能夠比社區的高不少,下圖爲HBase1.0上測試結果。在一些大KV,寫入壓力更大的場景,實測能夠接近1倍。這個比較是經過替換ADFS冷存的實現(用社區版本和雲HBase版本),都避免了rename深拷貝問題。若是直接裸用社區版本而不用ADFS那性能會差數倍。

性能對比2:熱表 vs 冷表

熱表數據在雲盤,冷表數據在OSS。

得益於上述優化,加上冷表WAL也是放HDFS的,而且OSS相對HBase來講是大集羣(吞吐上限高),冷表的HDFS只用抗WAL寫入壓力。因此冷表吞吐反而會比熱表略高一點點。

無論怎麼說,冷表的寫入性能和熱表至關了,這樣的表現已經至關不錯了。基本是不會影響用戶灌數據,不然使用冷存後,吞吐掉不少,那意味着要更多機器,那這功能就沒什麼意義了。

總結

這大約是1年多前落地的項目,已經穩定運行好久。以前也有出去分享過,可是沒有今天這麼細緻。如今寫出來主要是自我總結下。當設計一個服務去解決某個問題的時候,上下游的關係,可能存在的問題得考慮清楚。在作這個項目最初的時候,想把HBase直接架設在社區版本OssFileSytem上,發現性能不行外,正確性也存在很大風險。不斷思考,考慮各類狀況後纔有了今天這套方案。



本文做者:郭澤暉

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索