Hypertable應用實踐:比肩HBase

作者: baiyuzhong分類:雲計算, 選題策劃, 高端視點  閱讀:15,525 次添加評論

文 / 楊棟

Hypertable是一個開源、高性能、可伸縮的數據庫,採用與GoogleBigTable相似的模型。BigTable讓用戶可以通過一些主鍵來組織海量數據,並實現高效的查詢。HypertableHBase分別是BigTable的兩個開源實現:HBase主要使用Java語言開發,而Hypertable使用Boost C++,另外在一些細節的設計理念上也有所不同。

Hypertable系統主要包括HyperspaceMasterRange Server三大組件(如圖1所示)。Hyperspace是一個鎖服務,地位相當於GoogleChubby,主要用於同步、檢測節點是否發生故障和存放頂層位置信息;Master主要用於完成任務分配,未來會有負載均衡以及災後重建(Range Server失效後自動恢復服務)等其他作用;Range ServerHypertable的實際工作者,主要負責對一個Range中的數據提供服務,此外它還肩負起災後重建的責任,即重放本地日誌恢復自身故障前狀態;另外,還有訪問Hypertable的客戶端Client等組件。

圖1 Hypertable原有架構示意圖

業務應用

FacebookSIGMOD 2011會議上介紹了基於Hadoop/HBase的三種應用系統:TitanFacebook Messages)、PumaFacebook Insights)和ODSFacebook Internal Metrics)。Titan主要用於用戶數據存儲,Puma用於MapReduce分佈式計算,ODS用於存儲公司內部監控數據,Facebook基於HBase的應用方式與國內幾大互聯網公司類似。

ODS類似,對於一些硬件或軟件的運行數據,我們會保存監控數據到數據庫中,供軟件工程師或者運維工程師查詢。這裏的查詢可能是大批量的,也可能是個別條目;可能是延遲查詢,也可能是即時查詢。將此類業務的需求總結如下。

  •  要求存儲容量非常大,往往達到10100TB10億~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實際上覆蓋了一個200MBRange,所以任何一臺包含METADATARange Server發生故障,都可能導致這部分METADATA所涵蓋的一大批數據不可訪問。將METADATA分佈到多個不同的Range Server上,無異於給系統增加了很多單點,降低了系統可靠性。

解決:本着簡單原則,我們認爲將元數據與用戶數據分離,放在專用的Meta Range Server上更具有可操作性。元數據集中化的唯一缺點是,由於受Meta Range Server內存限制,32GB物理內存所能存放的元數據理論上只能支持上PB的用戶數據。但考慮一般機房所能容納的機器規模,PB級的數據規模完全可以滿足大多數公司的需要。

圖3 Hypertable高可用改進架構示意圖

3給出了Hypertable元數據集中管理的整體結構。目前的實現將Hypertable中的數據服務器(Range Server)分爲兩種:Meta Range ServerUser Range ServerMeta Range Server只管理Root表和METADATA表的RangeUser Range Server只管理用戶表的Range。由於Master的負載較輕,因此一般將Meta Range ServerMaster放在同一個節點上。

系統啓動時,每個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記錄的操作日誌(CommitLogSplitLog等)。BigTable系統(Hypertable/HBase)最重要的功能之一是自動恢復,自動恢復依賴操作日誌(Commit Log)能夠真正寫入HDFSSync),故障發生後,系統通過重放日誌構建故障前的一致性狀態。

在我們早期使用HypertableHadoop系統時,Hadoop 0.18版本尚不支持Append Sync功能。即使當前版本的Hadoop支持了Append Sync功能,頻繁使用Sync也會影響系統的寫吞吐能力。另外,Hadoop的穩定性在當時還不能得到保證,存在寫入失敗的情況。如果Hadoop出現問題,那麼Hypertable剛寫入的數據可能丟失。如果是日誌,那麼重啓時無法恢復系統狀態。

解決:一般情況下,Hypertable系統的存儲基於Hadoop文件系統,數據和日誌都寫入HDFS。而在改進後的Hypertable系統中我們採用了不同的存儲方式:數據寫HDFS,日誌寫Local FS

較之本地文件系統Ext2等,HDFS的穩定性還是略遜一些,在Hypertable的實際運維過程中,我們也遇到過HypertableHadoop寫入數據失敗的情況。鑑於日誌的重要性,我們選擇將日誌寫入可靠性更高的本地文件系統,這樣即使Hadoop寫文件時出現問題,也可以通過重放本地日誌來恢復Hypertable系統狀態。

改進後的Hypertable集羣發生故障時,有以下幾種處理場景。

  •  寫日誌故障:Range Server在寫日誌時(CommitLog等)發生錯誤,可能是本地磁盤故障。此時日誌的完整性不能得到保證,需要在Range Server寫日誌的相關操作上附加額外的例外處理。日誌寫例外將觸發Range Server執行一次OFFLINE操作,即在日誌完整性不能保證的前提下,儘快保證數據的完整性和一致性,之後再人工參與後續的恢復處理。
  •  寫數據故障:Range Server故障非日誌操作引起的,可能由系統Bug導致,也可能是Hadoop寫數據文件失敗。此時日誌的完整性有保障,可以直接執行SHUTDOWN操作,關閉各個Range Server。待Bug解決或Hadoop恢復後,重啓Hypertable重放日誌即可恢復集羣狀態和數據。

以上提到半自動容錯機制的兩條路線分別保證了“日誌- | 數據+”和“日誌+ | 數據-”兩種故障情況下集羣數據的完整性和一致性。那麼有沒有“日誌- | 數據-”的情況,極端情況下可能出現Hadoop寫數據文件失敗和某Data NodeRange Server)硬盤故障同時發生,此時系統將不可避免地丟失數據,我們只能通過上層應用回滾重放的方式來恢復系統數據。

分裂日誌策略

挑戰:Hypertable系統涉及的日誌爲CommitLogSplitLog等,日誌寫本地文件系統的策略約束了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的穩定性。

爲了定位內存佔用過量問題,我們使用valgrindTCMalloc庫的Heap Profiling工具對Hypertable進行了測試,發現Hypertable內存飆升的原因是Cell Cache代碼中存在頻繁分配、釋放小片內存(從十幾字節到幾千字節不等)的情況,從而產生了大量內存碎片,致使內存效率存在嚴重問題。如圖4所示,Range Server中的大量內存分配集中於Cell Cache<key, value>Cell Map進行空間分配的時候。

圖4 改進前Range Server內存使用情況統計

解決:我們決定對Cell Cache相關的內存實施獨立管理,即採用自定義的內存分配回收方式管理<key, value>Cell Map,使其產生的內存碎片最小化。

5顯示了Hypertable數據服務器上的數據更新過程。ClientRange Server發送數據(<key, value>形式),Range Server首先將數據緩存在Cell Cache中,並使用Cell Map結構建立樹形索引。當需要進行Compaction時,會新開一個Cell Cache,並把當前Cell Cache凍結,新寫入的數據會進入新開的Cell Cache,而凍結的Cell Cache則在後臺寫到文件系統中形成Cell Store文件,Compaction完成後,凍結了的Cell Cache會被統一釋放。此過程中,Cell Cache涉及的內存分配釋放操作主要有:分配空間(new)容納要寫入的key/value;分配空間維護Cell Map(本質上是一個std::map,使用默認的STL allocator分配空間)索引結構;釋放數據和索引佔用的全部空間。可見,問題主要出在內存分配太過細碎。

圖5 Hypertable插入數據時Cell Cache內存分配示意圖

我們修改了Cell Cache的分配策略,利用簡化的內存池思想,將內存分配策略改爲統一分配。每個Cell Cache使用1個內存池(MemPool),每個MemPool初始時包含14MB(默認設置)的緩衝區(MemBuf),所有的<key, value>Cell Map結構佔用的空間都在MemBuf內部分配。當MemBuf滿了之後,再分配一片新的MemBuf,釋放時也是大片釋放,這樣就防止了頻繁的new/delete操作。此外,<key, value>Cell Map結構佔用的內存是分別從MemBuf的兩端分配的,這樣做的目的是保證Cell Map內存對齊,減少因爲內存非對齊訪問帶來的效率下降。當一個緩衝區用滿後,內存池會自動擴充一個新的緩衝區,內存釋放只是針對整個內存池。

這種內存池分配方式最終也被合入到Hypertable官方版本之中。

6給出了Google Heap Profiling工具檢測的Cell Cache內存使用情況,對比圖4中的數據,改進後版本Range Server的主要內存使用集中於CellCachePool::get_memory,即Cell Cache的內存使用,這和原始版本中主要使用內存的地方是一致的。這說明如果我們的內存管理機制有效,就能大量減小Hypertable的內存佔用量。

圖6 改進後Range Server內存使用情況統計

7給出了Range ServerCell Cache在使用普通new/deleteTC MallocPool Mallocwith Map)以及Pool Mallocwithout Map)四種內存分配方式下,插入數據過程及之後的內存佔用量對比。圖7中的藍、綠、黃、紅四種顏色分別對應上述的四種分配方式。可以看出,普通分配方式的內存佔用量最不理想,並且最終不能降低,最終內存佔用約6.4GBTC 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進行了反轉,例如12344321,從而使之分佈更均勻;調整每個rangecell store個數上限到5(默認是10),第三輪則把cell store個數進一步縮小到1(通過發命令強制做major compaction)。測試結果如圖9所示。

圖9 cell store文件數配置不同時導入性能對比

此測試最大的特點是數據量遠大於內存總數,因此存在較多隨機磁盤訪問。以第二輪16進程查詢爲例,平均每個Range4.4Cell Store文件,因此每秒需要進行4.4×216≈950HDFS文件隨機訪問。每讀一次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 ServerCPU sys時間較高(30%~40%),但useriowait時間都比較低,因此認爲瓶頸在網絡RPC上。

in memory這種模式非常耗費內存,原因有以下兩點。

  •  由於Hypertable設計時爲了支持稀疏表,每個value是單獨存的,而不是按行存的,因此每個value都需要存一份key (包括row keycolumn familycolumn qualifiertimestamp,最小開銷16字節),再加上map數據結構的開銷24字節,一個value至少有40字節額外開銷,一個帖子就是40×13=520字節,比帖子的實際內容(平均300多字節)還多。
  • 爲了支持高併發,Hypertable採用了MVCCMulti-version Concurrency Control)模式存儲<key, value>,也就是說,刪改一個value時只是追加了一個補丁,而不是在原值基礎上修改,多餘的版本只有當Cell Cache大小達到一定程度時纔會清理。

(3)Cache支持

當前版本的Hypertable依據當時的負載狀況,動態調整分配給每個子系統的內存。對於讀密集型的負載,Hypertable分配大部分內存給Block Cache;而HBase則固定分配20%Java Heap作爲Block Cache。此外,Hypertable還提供Query Cache機制,緩存查詢結果,使得其隨機訪問性能超過了HBase,如圖10所示。當然,Bloomfilter機制對HBaseHypertable都支持,能夠避免大量的無效訪問。

圖10 Hypertable vs. HBase隨機讀吞吐量測試

小結

HBaseFacebook的應用非常成功,後端平臺的實時改進提高了其前端的業務水平。而Hypertable尚未在業界大規模使用,但我依然非常看好它,看好其精細的架構和高質量的代碼實現。相信未來將會有更多的開發者來使用和改進Hypertable系統。 

作者楊棟,百度分佈式高級研發工程師,從事Hypertable、Hadoop及流式計算的研究和開發。

本文選自《程序員》雜誌2012年02期,更多精彩內容敬請關注02期雜誌


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章