學習軟件有三個境界,第一個境界是會使用它,第二個境界是懂得背后的原理,明白它的架構體系,第三個境界學習他的所長,為我所用。研究HBase/BigTable架構和源碼一段時間后,我總結了一些東西可以供我們在設計分布式系統借鑒使用。
1. 使用可信任的分布式組件來搭建自己的分布式系統。
設 計一個可靠,健壯的分布式系統是比較困難的。我們知道,為了防止SPOF(Single Point Of Failure)問題,我們要分散風險,把數據放在多個nodes上面去,但是這樣帶來了是數據的同步問題和版本問題,解決這個問題需要運用復雜的 Paxos協議,系統的復雜度自然就升高了。另外一個需要解決的問題是分布式鎖和事件通知機制,以及全局信息共享,設計這些都需要大量的精力和仔細的研 究。
HBase就不用考慮這些問題,它把數據的同步和冗余問題交給了Hadoop,把鎖機制和全局共享交給了Zookeeper,這大大簡化了HBase的設計。
所以我們設計系統的時候,也要盡量利用這些可靠,穩定的組件。目前比較流行和穩定的有:
分布式文件系統 - HDFS
分布式鎖和目錄 - Zookeeper
緩存 - MemCached
消息隊列 - ActiveMQ
2.避免單點問題(SPOF)
設計分布式系統要時刻考慮到失敗,不單是軟件可能失敗,硬件也可能掛掉,所以我們系統里面就不能有不可替代的角色。
HBase 使用Master Server來監控所有的Region Server,一旦其中的一臺出現問題,在其上的Region將會被轉移到其他的Region Server,避免了服務中斷。而Master Server也可以多臺備選,一臺掛掉之后,其他的備胎則會”繼承遺志“,從而讓整個系統得以生存。
那 HBase如何做到這個呢,一個是使用”心跳機制”,即Region Server要主動定期向Master匯報狀況,另外一個是利用zookeeper里面的”生命節點“,每個server在啟動后要在ZK里面注冊,一旦 這個server掛掉,它在ZK里面的節點就會消失,監聽這個節點的server就會得到通知。
3.利用不變性提高系統的吞吐量
我們知道,很多進程/線程修改同一個東西的時候,我們就需要鎖機制來避免沖突。但是鎖帶來的問題是系統性能下降。如果對于一個只讀的對象,就不需要鎖了。
HBase 在設計存儲的時候考慮到這一點,最新的數據是放在memory里面,以提高性能。但是memory是有限的,我們不可能讓數據一直放在memory里面, 所以我們需要定時把這些數據寫到HDFS/磁盤上面。一種設計是寫到一個可修改的大文件中去,這樣對這個文件的讀寫就需要加鎖了。HBase是每次都寫到 一個新的文件中,一旦文件創建后,這個文件將不能被修改,就是所謂的create-one-read-many。當然這樣也有一個問題,就是時間長了,會 有很多的小文件,每次查找,需要查找這所有的文件,降低了系統的性能,HBase會定時的合并這些小文件生成一個大文件。
4.利用索引塊提高文件的查詢速度
HBase的存儲文件(HFile)是用來存儲很多排序后的Key-Value的,如何設計一種支持快速隨機查詢和壓縮的文件是一個有意思的話題。
HFile 在文件的尾部增加了索引塊,但是不可能對任何一個rowkey都做索引,這樣的話索引塊會很大,而且也不利于壓縮。HFile的做法是定義一個Data Block的大小,這樣就把數據劃分了一個一個的Block,索引只針對這些block做,Block是可以被壓縮的。當查詢一個rowkey的時候,如 果沒有cache的話,首先使用二分法定位到具體的block,然后再解壓,遍歷查詢具體的key。
HFile這樣的設計兼顧了速度和文件大小的平衡。
5.自定義RPC機制提供更大的靈活性
HBase/Hadoop 都沒有利用標準的Java遠程調用規范RMI,而是自己搞了一套。這樣做的好處有幾點,一是減少網絡流量,我們知道,java RMI使用了java serlizable來傳遞參數,java序列化有很多無關的類信息,都占用不少的空間,而且這會帶來對java版本的依賴。二是帶來更大的靈活性,你可 以在其中加入版本檢查,權限認證等。
那 HBase是怎么設計這個RPC呢?首先它定義了一個writable接口,來代替java序列化,實現這個接口就等于告訴HBase,怎么把這個對象寫 到RPC流中去。使用RPC的時候,需要先寫一個服務器端和客戶端共用的interface,這個interface必須繼承 VersionedProtocol來處理版本問題 。HBase利用Java的動態反射機制(Proxy.newProxyInstance)來生成代 理對象,這樣當Client調用代理對象的時候,Client就會把參數打包,發送到服務器端,然后等待返回結果。服務器會根據interface查找到 具體的實現的對象,調用該對象的方法來執行遠程調用。詳細的做法可以參考HBase/Hadoop的源碼。
6.內嵌Web Server增強系統的透明度
當一個后臺進程啟動之后,我們如何了解這個進程的內部狀態呢?傳統方法是通過進程管理器或者Debug log來看進程的情況,但是這些信息很有限。
HBase利用jetty在進程內部啟動了一個web server,就可以即時的顯示一些系統內部的信息,非常的方便。
利 用Jetty支持jsp非常的容易,下面是一個示例的代碼。注意的是,需要把jasper-runtime-5.5.12.jar,jasper- compiler-5.5.12.jar,jasper-compiler-jdt-5.5.12.jar,jsp-2.1.jar,jsp-api- 2.1.jar等jar包放在classpath里面,否則會出現頁面解析錯誤。
server = new Server(port);
server.setSendServerVersion(false);
server.setSendDateHeader(false);
server.setStopAtShutdown(true);
WebAppContext wac = new WebAppContext();
wac.setContextPath("/");
wac.setWar("./webapps/job");
server.setHandler(wac);
server.setStopAtShutdown(true);