Hypertable應用實踐:比肩HBase
文 / 楊棟
Hypertable是一個開源、高性能、可伸縮的數據庫,采用與Google的BigTable相似的模型。BigTable讓用戶可以通過一些主鍵來組織海量數據,并實現高效的查詢。Hypertable和HBase分別是BigTable的兩個開源實現:HBase主要使用Java語言開發,而Hypertable使用Boost C++,另外在一些細節的設計理念上也有所不同。
Hypertable系統主要包括Hyperspace、Master和Range Server三大組件(如圖1所示)。Hyperspace是一個鎖服務,地位相當于Google的Chubby,主要用于同步、檢測節點是否發生故障和存放頂層位置信息;Master主要用于完成任務分配,未來會有負載均衡以及災后重建(Range Server失效后自動恢復服務)等其他作用;Range Server是Hypertable的實際工作者,主要負責對一個Range中的數據提供服務,此外它還肩負起災后重建的責任,即重放本地日志恢復自身故障前狀態;另外,還有訪問Hypertable的客戶端Client等組件。

圖1 Hypertable原有架構示意圖
業務應用
非死book在SIGMOD 2011會議上介紹了基于Hadoop/HBase的三種應用系統:Titan(非死book Messages)、Puma(非死book Insights)和ODS(非死book Internal Metrics)。Titan主要用于用戶數據存儲,Puma用于MapReduce分布式計算,ODS用于存儲公司內部監控數據,非死book基于HBase的應用方式與國內幾大互聯網公司類似。
和ODS類似,對于一些硬件或軟件的運行數據,我們會保存監控數據到數據庫中,供軟件工程師或者運維工程師查詢。這里的查詢可能是大批量的,也可能是個別條目;可能是延遲查詢,也可能是即時查詢。將此類業務的需求總結如下。
- 要求存儲容量非常大,往往達到10~100TB,10億~100億條記錄。
- 需要支持自動擴容,因為數據的增長模式不易估計,可能出現短時間的爆炸性增長。
- 寫吞吐的壓力較大,每秒超過1萬次的插入。
- 近期導入數據能夠快速檢索。
- 需要支持掃描早期的大量數據,例如支持周期性的檢查或回滾。
這里可選的一個方案是使用傳統的DBMS(如MySQL)。但它存在如下弊端:首先MySQL單機存儲有上限,一般超過1.5GB性能就會有波動;不過即使MySQL支持拆表,也并非完全分布式的,由于表的大小限制,對于不規則的數據增長模式,分布式MySQL也并不能很好地應對,如果抖動頻率較大,需要引入較多的人工操作來進行數據遷移;再者MySQL也不支持表的Schema動態改變。另一個可選方式是使用Hadoop。不過MapReduce并非實時計算,并且HDFS不支持隨機寫,隨機讀性能也很差。
綜上分析,我們選擇BigTable類型的系統來支持業務需求,即使用Hypertable+Hadoop的方式(如圖2所示)。

圖2 監控數據收集與查詢示意圖
高可用改進
元數據集中化
挑戰:在Hypertable或其他類似BigTable的系統中,元數據一般采用一種兩級的類B+樹結構,這主要是出于規模的考慮:采用這種結構理論上可以支持存放并索引2EB的用戶數據。若要索引這么多用戶數據,所需的元數據就高達16TB,一臺機器是存不下的,因此在類BigTable系統中,元數據也是分布在不同節點上進行管理的,集群中任意一個節點既可能包含用戶Range也可能包含元數據Range。
雖然這種做法可以解決規模問題,但在管理上帶來了一些困難,特別是進行故障恢復時,由于用戶表的Range恢復過程中需要讀取元數據,所以必須先恢復METADATA表中的Range,再恢復用戶表中的Range。如果有多臺Range Server同時故障,這種跨節點的依賴性處理起來非常困難,其他一些維護性操作同樣具有類似問題。此外,由于一條METADATA實際上覆蓋了一個200MB的Range,所以任何一臺包含METADATA的Range Server發生故障,都可能導致這部分METADATA所涵蓋的一大批數據不可訪問。將METADATA分布到多個不同的Range Server上,無異于給系統增加了很多單點,降低了系統可靠性。
解決:本著簡單原則,我們認為將元數據與用戶數據分離,放在專用的Meta Range Server上更具有可操作性。元數據集中化的唯一缺點是,由于受Meta Range Server內存限制,32GB物理內存所能存放的元數據理論上只能支持上PB的用戶數據。但考慮一般機房所能容納的機器規模,PB級的數據規模完全可以滿足大多數公司的需要。

圖3 Hypertable高可用改進架構示意圖
圖3給出了Hypertable元數據集中管理的整體結構。目前的實現將Hypertable中的數據服務器(Range Server)分為兩種:Meta Range Server和User Range Server。Meta Range Server只管理Root表和METADATA表的Range,User Range Server只管理用戶表的Range。由于Master的負載較輕,因此一般將Meta Range Server與Master放在同一個節點上。
系統啟動時,每個Range Server從配置文件得知自己的類型,并在注冊時匯報自己的類型。Master記錄每臺Range Server的信息。當Master需要將Range分配給Range Server時(例如表格創建和Range分裂),會根據Range所在表格的類型來選擇合適的Range Server,元數據Range分配到Meta Range Server,用戶Range則分配到User Range Server。
數據與日志存儲分離
挑戰:Hypertable集群中某些Range Server發生故障(Range Server進程故障退出)時,需要重新啟動該Range Server并恢復服務,這依賴于Range Server記錄的操作日志(CommitLog和SplitLog等)。BigTable系統(Hypertable/HBase)最重要的功能之一是自動恢復,自動恢復依賴操作日志(Commit Log)能夠真正寫入HDFS(Sync),故障發生后,系統通過重放日志構建故障前的一致性狀態。
在我們早期使用Hypertable和Hadoop系統時,Hadoop 0.18版本尚不支持Append Sync功能。即使當前版本的Hadoop支持了Append Sync功能,頻繁使用Sync也會影響系統的寫吞吐能力。另外,Hadoop的穩定性在當時還不能得到保證,存在寫入失敗的情況。如果Hadoop出現問題,那么Hypertable剛寫入的數據可能丟失。如果是日志,那么重啟時無法恢復系統狀態。
解決:一般情況下,Hypertable系統的存儲基于Hadoop文件系統,數據和日志都寫入HDFS。而在改進后的Hypertable系統中我們采用了不同的存儲方式:數據寫HDFS,日志寫Local FS。
較之本地文件系統Ext2等,HDFS的穩定性還是略遜一些,在Hypertable的實際運維過程中,我們也遇到過Hypertable向Hadoop寫入數據失敗的情況。鑒于日志的重要性,我們選擇將日志寫入可靠性更高的本地文件系統,這樣即使Hadoop寫文件時出現問題,也可以通過重放本地日志來恢復Hypertable系統狀態。
改進后的Hypertable集群發生故障時,有以下幾種處理場景。
- 寫日志故障:Range Server在寫日志時(CommitLog等)發生錯誤,可能是本地磁盤故障。此時日志的完整性不能得到保證,需要在Range Server寫日志的相關操作上附加額外的例外處理。日志寫例外將觸發Range Server執行一次OFFLINE操作,即在日志完整性不能保證的前提下,盡快保證數據的完整性和一致性,之后再人工參與后續的恢復處理。
- 寫數據故障:Range Server故障非日志操作引起的,可能由系統Bug導致,也可能是Hadoop寫數據文件失敗。此時日志的完整性有保障,可以直接執行SHUTDOWN操作,關閉各個Range Server。待Bug解決或Hadoop恢復后,重啟Hypertable重放日志即可恢復集群狀態和數據。
以上提到半自動容錯機制的兩條路線分別保證了“日志- | 數據+”和“日志+ | 數據-”兩種故障情況下集群數據的完整性和一致性。那么有沒有“日志- | 數據-”的情況,極端情況下可能出現Hadoop寫數據文件失敗和某Data Node(Range Server)硬盤故障同時發生,此時系統將不可避免地丟失數據,我們只能通過上層應用回滾重放的方式來恢復系統數據。
分裂日志策略
挑戰:Hypertable系統涉及的日志為CommitLog和SplitLog等,日志寫本地文件系統的策略約束了SplitLog的故障恢復。
Hypertable系統設計SplitLog的初衷在于保證導入數據的速率。Range Server上的Range在分裂時,數據可以無阻塞地寫入SplitLog(它必須寫到分布式文件系統上,因為它保存的是實際數據),Range分裂完成后SplitLog文件可能被其他的Range Server重放。CommitLog中記錄了SplitLog的位置,系統恢復時日志重放會涉及SplitLog日志的重放,如果SplitLog寫在本地,那么故障恢復時就無法讀取該日志。
HBase系統中并未涉及SplitLog機制,在Range分裂時數據不能繼續導入。
解決:解決方案有兩種,一種是本著穩定性和可靠性優先于性能的原則,為了保證日志的可靠性和使得自動恢復機制更簡單,取消SplitLog機制,修改后的Hypertable系統在Range分裂過程不涉及SplitLog相關操作;另一種是將SplitLog寫入更加可靠的共享存儲中,能夠讓Range Server遠程訪問,這相當于引入了第三方系統。
安全停機策略
挑戰:kill/run操作可以完成任意時刻Hypertable系統的關閉和啟動,無論當前是否正在導入數據,因為Range Server啟動后會重放日志。但由于當時的Hypertable缺乏自動遷移(負載均衡)機制,這組操作并不適用于集群的變更,例如更替或添加節點。
解決:offline/online操作方式的提出是為了輔助kill/run操作,增加Hypertable集群的可擴展性。執行這組操作,可以保證offline執行時內存數據都寫入文件系統,online執行時Range能夠均勻分布加載,易于集群節點更換。系統管理員通過Hypertable命令行工具執行offline向各個Range Server發出命令,Range Server進程收到offline命令后,等待其上執行的Maintenance任務執行完成,并卸載其上加載的Range后退出。Range卸載成功時,所有系統數據被成功寫入分布式文件系統,本地文件系統的日志被刪除;卸載失敗時,日志保留。系統管理員通過Hypertable命令行工具執行online命令,Master收到online命令后,將METADATA記錄的Ranges均勻分配給各個Range Server加載,這就做到了半自動的負載均衡。
性能優化
內存優化
挑戰:在Hypertable系統的運維中,我們發現,Hypertable在內存使用效率上存在嚴重問題。在數據插入過程中,Range Server內存用量一直飆升,而且持久不下,很容易造成內存溢出并最終崩潰,嚴重威脅Hypertable的穩定性。
為了定位內存占用過量問題,我們使用valgrind和TCMalloc庫的Heap Profiling工具對Hypertable進行了測試,發現Hypertable內存飆升的原因是Cell Cache代碼中存在頻繁分配、釋放小片內存(從十幾字節到幾千字節不等)的情況,從而產生了大量內存碎片,致使內存效率存在嚴重問題。如圖4所示,Range Server中的大量內存分配集中于Cell Cache為

圖4 改進前Range Server內存使用情況統計
解決:我們決定對Cell Cache相關的內存實施獨立管理,即采用自定義的內存分配回收方式管理
圖5顯示了Hypertable數據服務器上的數據更新過程。Client向Range Server發送數據(

圖5 Hypertable插入數據時Cell Cache內存分配示意圖
我們修改了Cell Cache的分配策略,利用簡化的內存池思想,將內存分配策略改為統一分配。每個Cell Cache使用1個內存池(MemPool),每個MemPool初始時包含1個4MB(默認設置)的緩沖區(MemBuf),所有的
這種內存池分配方式最終也被合入到Hypertable官方版本之中。
圖6給出了Google Heap Profiling工具檢測的Cell Cache內存使用情況,對比圖4中的數據,改進后版本Range Server的主要內存使用集中于CellCachePool::get_memory,即Cell Cache的內存使用,這和原始版本中主要使用內存的地方是一致的。這說明如果我們的內存管理機制有效,就能大量減小Hypertable的內存占用量。

圖6 改進后Range Server內存使用情況統計
圖7給出了Range Server的Cell Cache在使用普通new/delete、TC Malloc、Pool Malloc(with Map)以及Pool Malloc(without Map)四種內存分配方式下,插入數據過程及之后的內存占用量對比。圖7中的藍、綠、黃、紅四種顏色分別對應上述的四種分配方式。可以看出,普通分配方式的內存占用量最不理想,并且最終不能降低,最終內存占用約6.4GB;TC Malloc方式較前者略好,內存占用增長方式也與之相似,也是最終內存占用很大,約4.4GB;后兩種內存池方式在整個過程中的內存占用變化趨勢很一致,區別在于對Cell Map使用內存池分配方式的曲線最終能夠降到很低(30MB左右),而對Map使用默認(STL庫)內存分配方式的曲線下降的幅度并沒有那么大,最終的內存占用大約為929MB。

圖7 各種分配策略下的Range Server內存使用情況對比
隨機訪問
挑戰:Hypertable支持順序讀和隨機讀,相比順序讀,隨機讀的性能并不好。由于隨機讀(非批量)性能較低,基于Hypertable的某些應用功能也很難實現,因此優化隨機性能對支持更多應用以及提升系統整體性能都非常有好處。
如圖8所示,使用IOzone對一些常見機型的機器磁盤做隨機讀測試,可以看到,如果訪問落到磁盤,性能會非常差,最好吞吐也是小于2MB/s。

圖8 各種機型磁盤隨機讀寫吞吐對比
解決:從磁盤分級、內存模式和Cache支持三個方面進行解決。
(1)磁盤分級向Hypertable系統導入470GB的原始數據,導入后經壓縮實際占用360GB×3副本≈1.1TB磁盤空間,大約分裂為2600多個Range,平均每臺服務器負責近300個。以下測試進行了3輪,每輪都分別進行單進程和多進程隨機查詢,每個進程共完成1000次查詢。相對于第一輪,第二輪進行了兩項優化:對row key進行了反轉,例如1234→4321,從而使之分布更均勻;調整每個range的cell store個數上限到5(默認是10),第三輪則把cell store個數進一步縮小到1(通過發命令強制做major compaction)。測試結果如圖9所示。

圖9 cell store文件數配置不同時導入性能對比
此測試最大的特點是數據量遠大于內存總數,因此存在較多隨機磁盤訪問。以第二輪16進程查詢為例,平均每個Range有4.4個Cell Store文件,因此每秒需要進行4.4×216≈950次HDFS文件隨機訪問。每讀一次HDFS中的文件實際至少需要訪問兩個文件:一個blk文件和一個meta文件,因此每秒至少需要950×2=1900次隨機磁盤訪問,這還不算dentry cache miss和超時重試。觀察發現,實際測試過程中最繁忙的節點每秒的磁盤隨機讀取次數達500多次,磁盤I/O利用率達到100%。第三輪測試同樣有類似的規律。因此我們可以得出結論,數據量較大時,Hypertable的瓶頸在于磁盤隨機I/O次數。
我們使用分層的方式來提升磁盤隨機訪問性能。固化存儲分級為SSD/SATA/SAS,隨機讀性能要求高的應用數據存儲到SSD,依次類推。測試發現,使用SSD,隨機讀性能提升60%以上,不過隨機寫性能會有部分下降,而且SSD的更新壽命約為1萬個操作。
(2)內存模式
對于那些頻繁訪問的數據,我們可以將其設置為in memory方式,這些數據將一直駐留內存(直接用一個C++ std::map結構存起來的,本質上相當于使用了紅黑樹索引),因此隨機查詢時不用從文件里讀,效率很高。
如果只用一臺Range Server,使用1個進程查詢同一行數據(共約600字節數據),速度可達4650次/s,若用16個進程并行查詢,每秒總查詢次數達到12700次,40進程時達到峰值16000次/s,相當于約10MB/s;如果每次查詢50行數據(40進程并行查詢),每秒查詢次數下降到1300左右,但聚合帶寬達到40MB/s。此過程Range Server的CPU sys時間較高(30%~40%),但user和iowait時間都比較低,因此認為瓶頸在網絡RPC上。
但in memory這種模式非常耗費內存,原因有以下兩點。
- 由于Hypertable設計時為了支持稀疏表,每個value是單獨存的,而不是按行存的,因此每個value都需要存一份key (包括row key、column family、column qualifier和timestamp,最小開銷16字節),再加上map數據結構的開銷24字節,一個value至少有40字節額外開銷,一個帖子就是40×13=520字節,比帖子的實際內容(平均300多字節)還多。
- 為了支持高并發,Hypertable采用了MVCC(Multi-version Concurrency Control)模式存儲
,也就是說,刪改一個value時只是追加了一個補丁,而不是在原值基礎上修改,多余的版本只有當Cell Cache大小達到一定程度時才會清理。
(3)Cache支持
當前版本的Hypertable依據當時的負載狀況,動態調整分配給每個子系統的內存。對于讀密集型的負載,Hypertable分配大部分內存給Block Cache;而HBase則固定分配20%的Java Heap作為Block Cache。此外,Hypertable還提供Query Cache機制,緩存查詢結果,使得其隨機訪問性能超過了HBase,如圖10所示。當然,Bloomfilter機制對HBase和Hypertable都支持,能夠避免大量的無效訪問。

圖10 Hypertable vs. HBase隨機讀吞吐量測試
小結
HBase在非死book的應用非常成功,后端平臺的實時改進提高了其前端的業務水平。而Hypertable尚未在業界大規模使用,但我依然非常看好它,看好其精細的架構和高質量的代碼實現。相信未來將會有更多的開發者來使用和改進Hypertable系統。
作者楊棟,百度分布式高級研發工程師,從事Hypertable、Hadoop及流式計算的研究和開發。