HBase深入分析之RegionServer
所有的用戶數據以及元數據的請求,在經過Region的定位,最終會落在RegionServer上,并由RegionServer實現數據的讀寫操作。本小節將重點介紹RegionServer的代碼結構和功能,從實現細節上深入理解RegionServer對于數據的操作流程。
1 RegionServer概述
RegionServer是HBase集群運行在每個工作節點上的服務。它是整個HBase系統的關鍵所在,一方面它維護了Region的狀態,提供了對于Region的管理和服務;另一方面,它與Master交互,上傳Region的負載信息上傳,參與Master的分布式協調管理。具體如圖(1)所示。
HRegionServer與HMaster以及Client之間采用RPC協議進行通信。HRegionServer向HMaster定期匯報節點的負載狀況,包括RS內存使用狀態、在線狀態的Region等信息,在該過程中RS扮演了RPC客戶端的角色,而HMaster扮演了RPC服務器端的角色。RS內置的RpcServer實現了數據更新、讀取、刪除的操作,以及Region涉及到Flush、Compaction、Open、Close、Load文件等功能性操作。此時,RS扮演了RPC服務的服務端的角色。RS與Client之間的RPC是HBase最為核心的操作,其服務狀況的好壞,直接反映了RS內部、以及它所依賴的HDFS服務質量的好壞,因此,該過程的RPC經常成為分析讀寫性能異常的突破口。
從RegionServer實現的功能上而言,除了與HMaster和Client之間的RPC通信之外,還包括如下幾個重要的模塊:
(1)依托ZookeeperWatcher進行的分布式信息共享與任務協調的工作。
MasterAddressTracker:捕獲Master服務節點的變化。
HBase使用多Master來解決Master單點故障的問題,主Master服務故障時,它與ZooKeeper的心跳延遲超過閾值,ZooKeeeper路徑下的數據被清理,備Master上的ActiveMaserManager服務會競爭該Master路徑,成為主Master。
MasterAddresTracker是RS內部監聽Master節點變化的追蹤器。
([yù zhí]閾值,閾值又叫臨界值,是指一個效應能夠產生的最低值或最高值。閾值又稱閾強度,是指釋放一個行為反應所需要的最小刺激強度。低于閾值的刺激不能導致行為釋放。)
ClusterStatusTracker:HBase集群狀態追蹤器。
該選項可以標識當前集群的狀態,及它的啟動時間。
該設置選項有利于集群中的各個工作節點(RS)統一執行啟動和退出操作。
CatalogTracker:跟蹤-ROOT-、.META.表的Region的狀態。在HBase支持的-ROOT-、.META.、以及User Region三層樹級目錄結構中,-ROOT-、.META.表用來定位Region的位置,追蹤-ROOT-表和.META.表對應Region的變化,可以時刻保證整個層次目錄樹的完整性。
SplitLogWorker:基于Region的HLog文件切分器。在RS宕機之后,RS上的保存的HLog文件,需要按照Region進行切分。HMaster會把這些文件作為任務放置到Zookeeper的splitlog路徑下,RS上SplitLogWorker會嘗試獲取任務,對獲取到的HLog文件按照Region進行分組,處理的結果保存到相應Region的recovered.edits目錄下。
(2)Region的管理。
Region是HBase數據存儲和管理的基本單位。Client從.META.表的查找RowKey對應的Region的位置,每個Region只能被一個RS提供服務,RS可以同時服務多個Region,來自不同RS上的Region組合成表格的整體邏輯視圖。
RS內涉及到提供的有關Region維護的服務組件有:
1) MemStoreFlusher,控制RS的內存使用,有選擇性地將Region的MemStore數據寫入文件。該組件可以有效地控制RS的內存使用,flush文件的速度在一定程度上可以反應HBase寫服務的繁忙狀況。
2) CompactSplitThread,合并文件清理不需要的數據,控制Region的規模。在Store內的文件個數超過閾值時,觸發Compact合并文件操作,一是清理被刪除的數據,二是多余版本的清理。在Region內的Store文件大小超過閾值,會觸發Region的Split操作,一個Region被切分成兩個Region。這兩個操作都是在CompactSplitThread的各自的線程池中被觸發。
3) CompactionChecker,周期性檢查RS上的Region是否需要進行Compaction操作,確認需要進行Compaction操作的Region,提交給CompactSplitThread執行請求。
RS的內存的使用分為MemStore和BlockCache。其中MemStore提供寫操作的緩存,而BlockCache是提供的讀請求緩存。它們詳細的內容會在后續章節中介紹。
(3)WAL的管理。
HBase對于數據的更新和刪除操作默認先Append到HLog文件,然后再更新到RS對應的Region上,因此,由HLog文件在RS的處理方式,被稱為Write-Ahead-Log。多個Region的更新刪除操作會被相繼寫入同一個文件,出于以下的原因,HLog文件會被截斷,然后創建新HLog文件繼續當前的Append操作。
1) Append操作失敗,避免因底層文件系統的文件異常,阻塞數據的操作。
2) 降低存儲空間的開銷。當HLog上記錄的數據完全從MemStore寫入HDFS,此時如果多個HLog文件,有利于篩選冗余的HLog文件,提高存儲空間的效率。
3) 提高分布式HLog文件切分操作(Distributed Log Split)的效率。多個HLog文件就對應同樣數目的LogSplit子任務,從而可以借助多個RS的SplitLogWorker組件快速完成HLog文件的切分,盡快恢復Region的服務。
在RS內,LogRoller定期刷新出一個新的HLog文件。
(4)Metrics
Metrics對外提供了衡量HBase內部服務狀況的參數。RegionServer內Metrics包含了內存使用、Region服務狀況、Compaction、blockCache等一系列標識服務狀況的參數。HBase Metrics繼承Hadoop Metrics的實現,目前支持文件、Ganglia、以及數據流等多種輸出方式,可以針對輸出的Metrics信息靈活構建監控系統。
(5)HttpServer
RS內置了一個Jetty Web Server,用來對外提供RS的訪問頁面。訪問頁面目前支持實時Metrics信息查詢、日志查詢、線程的Dump、修改日志級別等操作。
2 RegionServer的啟動過程分析
RegionServer服務由org.apache.hadoop.hbase.regionserver.HRegionServer類提供。該類實現了四個接口,分別是HRegionInterface,RegionServerServices,HBaseRPCErrorHandler和Runnable。其中,
HRegionInterface定義了RS對外提供的RPC訪問接口,通過RPCServer內置的Handler來處理請求;
RegionServerServices定義了基于RS內部的服務信息接口,例如onlineRegions增、刪、查接口,以及獲取HLog、文件系統等接口;
HBaseRPCErrorHandler定義了RPCServer異常狀態檢測處理接口;
Runnable是Java庫中的線程接口,實現該接口意味著RegionServer生命周期會運行在run()的函數體內。
RegionServer是一個獨立的服務,有一個main函數在啟動時被調用,main函數內通過HRegionServerCommandLine的反射機制在JVM內動態加載RegionServer實現類,并按照args解析參數情況,決定啟動或者關閉RS服務。
publicclassHRegionServerimplementsHRegionInterface,HBaseRPCErrorHandler,Runnable,RegionServerServices{...//成員變量定義和成員函數實現publicstaticvoidmain(String[]args)throwsException{...Configurationconf=HBaseConfiguration.create();@SuppressWarnings("unchecked")ClassregionServerClass=(Class)conf.getClass(HConstants.REGION_SERVER_IMPL, HRegionServer.class);//獲取RegionServer對應的類newHRegionServerCommandLine(regionServerClass).doMain(args);//創建RegionServer實例并啟動}...}
初始化與執行過程包括:
(1)構造HRegionServer實例,初始化變量和對象。這涉及到以下重要變量初始化:
protected volatile boolean stopped = false;//關閉Server的標識,關閉過程中會置成ture
private boolean stopping = false;//關閉Region過程的標識,是進入stopped之前的狀態
protected volatile boolean fsOk;//文件系統狀態標識,false表示文件系統不可用
private final ConcurrentSkipListMap regionsInTransitionInRS =
new ConcurrentSkipListMap(Bytes.BYTES_COMPARATOR);//RS內處于遷移過程中的Region,其中true表示在open,false表示在close
protected final Map onlineRegions =
new ConcurrentHashMap();//RS內正在服務的Region
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();//修改onlineRegions對象的讀寫鎖
protected final int threadWakeFrequency;//工作線程服務周期間隔
private final int msgInterval;//向Master匯報心跳,收集Metrics間隔
private final long maxScannerResultSize;//Scanner執行next返回的數據量閾值,默認設置是Long.MAX_VALUE
private int webuiport = -1;//webServer的端口號
private final long startcode;//HRegiongServer初始化的時間,取自系統時間
private ServerName serverNameFromMasterPOV;//標識Server的名字
private final int rpcTimeout;//定義到HMaster之間的rpc超時時間
在RS上重要的對象列表,如表1所示。
表1RegionServer重要對象的解釋
(2)監聽服務組件的初始化與執行。
這個過程初始化以ZooKeeperWatcher為基礎的服務,例如監聽Master服務節點的MasterAddressManager,標識HBase集群狀態的ClusterStatusTracker,以及元數據(-ROOT-, .META.)變化的監聽器。啟動這些服務可以保證整個集群信息協調一致。
(3)RS服務組件的初始化與執行。
這個過程是初始化compactSplitThread,cacheFlusher,compactionChecker,以及Leases。
(4)嘗試連接HMaster,注冊RS到HMaster。
(5)周期性收集Metrics和向Master發送心跳。
3 Store相關
Region是RS上的基本數據服務單位,用戶表格由1個或者多個Region組成,根據Table的Schema定義,在Region內每個ColumnFamily的數據組成一個Store。每個Store內包括一個MemStore和若干個StoreFile(HFile)組成。如圖(3)所示。本小節將介紹Store內的MemStore、StoreFile(HFile)的內部結構與實現。
3.1 MemStore原理與實現分析
MemStore是一個內存區域,用以緩存Store內最近一批數據的更新操作。對于Region指定的ColumnFamily下的更新操作(Put、Delete),首先根據是否寫WriteAheadLog,決定是否append到HLog文件,然后更新到Store的MemStore中。顯然,MemStore的容量不會一直增長下去,因此,在每次執行更新操作時,都會判斷RS上所有的MemStore的內存容量是否超過閾值,如果超過閾值,通過一定的算法,選擇Region上的MemStore上的數據Flush到文件系統。更詳細的處理流程圖如圖(4)。
MemStore類內的重要的成員變量:
volatileKeyValueSkipListSet kvset;//內存中存放更新的KV的數據結構volatileKeyValueSkipListSet snapshot;//Flush操作時的KV暫存區域finalReentrantReadWriteLock lock=newReentrantReadWriteLock();//Flush操作與kvset之間的可重入讀寫鎖finalAtomicLong size;//跟蹤記錄MemStore的占用的Heap內存大小TimeRangeTracker timeRangeTracker;//跟蹤記錄kvset的最小和最大時間戳TimeRangeTracker snapshotTimeRangeTracker;//跟蹤記錄snapshot的最小和最大時間戳MemStoreLAB allocator;//實際內存分配器
注意:
KeyValueSkipListSet是對于jdk提供的ConcurrentSkipListMap的封裝,Map結構是的形式。Concurrent表示線程安全。SkipList是一種可以代替平衡樹的數據結構,默認是按照Key值升序的。對于ConcurrentSkipListMap的操作的時間復雜度平均在O(logn),設置KeyValue.
KVComparator比較KeyValue中Key的順序。
寫入MemStore中的KV,被記錄在kvset中。根據JVM內存的垃圾回收策略,在如下條件會觸發Full GC。
? 內存滿或者觸發閾值。
? 內存碎片過多,造成新的分配找不到合適的內存空間。
RS上服務多個Region,如果不對KV的分配空間進行控制的話,由于訪問的無序性以及KV長度的不同,每個Region上的KV會無規律地分散在內存上。Region執行了MemStore的Flush操作,再經過JVM GC之后就會出現零散的內存碎片現象,而進一步數據大量寫入,就會觸發Full-GC。圖(5)顯示這種假設場景的內存分配過程。
為了解決因為內存碎片造成的Full-GC的現象,RegionServer引入了MSLAB(HBASE-3455)。MSLAB全稱是MemStore-Local Allocation Buffers。它通過預先分配連續的內存塊,把零散的內存申請合并,有效改善了過多內存碎片導致的Full GC問題。
MSLAB的工作原理如下:
? 在MemStore初始化時,創建MemStoreLAB對象allocator。
? 創建一個2M大小的Chunk數組,偏移量起始設置為0。Chunk的大小可以通過參數hbase.hregion.memstore.mslab.chunksize調整。
? 當MemStore有KeyValue加入時,maybeCloneWithAllocator(KeyValue)函數調用allocator為其查找KeyValue.getBuffer()大小的空間,若KeyValue的大小低于默認的256K,會嘗試在當前Chunk下查找空間,如果空間不夠,MemStoreLAB重新申請新的Chunk。選中Chunk之后,會修改offset=原偏移量+KeyValue.getBuffer().length。chunk內控制每個KeyValue大小由hbase.hregion.memstore.mslab.max.allocation配置。
? 空間檢查通過的KeyValue,會拷貝到Chunk的數據塊中。此時,原KeyValue由于不再被MemStore引用,會在接下來的JVM的Minor GC被清理。
注意:
設置chunk的默認大小以及對于KeyValue大小控制的原因在于,MSLAB雖然會降低內存碎片造成的Full-GC的風險,但是它的使用會降低內存的利用率。如果超過一定大小的KeyValue,此時該KeyValue空間被回收之后,碎片現象不明顯。因此,MSLAB只解決小KV的聚合。
MSLAB解決了因為碎片造成Full GC的問題,然而在MemStore被Flush到文件系統時,沒有reference的chunk,需要GC來進行回收,因此,在更新操作頻繁發生時,會造成較多的Young GC。
針對該問題,HBASE-8163提出了MemStoreChunkPool的解決方案,方案已經被HBase-0.95版本接收。它的實現思路:
? 創建chunk池來管理沒有被引用的chunk,不再依靠JVM的GC回收。
? 當一個chunk沒有引用時,會被放入chunk池。
? chunk池設置閾值,如果超過了,則會放棄放入新的chunk到chunk池。
? 如果當需要新的chunk時,首先從chunk池中獲取。
根據patch的測試顯示,配置MemStoreChunkPool之后,YGC降低了40%,寫性能有5%的提升。如果是0.95以下版本的用戶,可以參考HBASE-8163給出patch。
思考
通過MemStore提供的MSLAB和MemStoreChunkPool給出的解決方案,可以看出在涉及到大規模內存的Java應用中,如何有效地管理內存空間,降低JVM
GC對于系統性能造成的影響,成為了一個研究熱點。整體上來說,一是設置與應用相適應的JVM啟動參數,打印GC相關的信息,實時監控GC對于服務的影響;二是從應用程序設計層面,盡可能地友好地利用內存,來降低GC的影響。
在ChunkPool就是幫助JVM維護了chunk信息,并把那些已經不再MemStore中的數據的chunk重新投入使用。這樣就可以避免大量的YGC。
3.2 MemStore參數控制原理與調優
對于任何一個HBase集群而言,都需要根據應用特點對其系統參數進行配置,以達到更好的使用效果。MemStore作為更新數據的緩存,它的大小及處理方式的調整,會極大地影響到寫數據的性能、以及隨之而來的Flush、Compaction等功能。這種影響的原因在于以下兩個方面。
? RS全局的MemStore的大小與Region規模以及Region寫數據頻度之間的關系。
? 過于頻繁的Flush操作對于讀數據的影響。
這其中涉及到的可調整的參數如下表。
表MemStore相關的配置參數
參數名稱參數含義默認值
hbase.regionserver.global.memstore.upperLimitRS內所有MemStore的總和的上限/Heap Size的比例,超過該值,阻塞update,強制執行Flush操作。0.4
hbase.regionserver.global.memstore.lowerLimit執行Flush操作釋放內存空間,需要達到的比例。0.35
hbase.hregion.memstore.flush.size每個MemStore占用空間的最大值,超過該值會執行Flush操作。128MB
hbase.hregion.memstore.block.multiplierHRegion的更新被阻塞的MemStore容量的倍數。2
hbase.hregion.preclose.flush.size關閉Region之前需要執行Flush操作的MemStore容量閾值。5MB
對于上述參數理解:
(1)RS控制內存使用量的穩定。
例如,假設我們的RS的內存設置為10GB,按照以上參數的默認值,RS用以MemStore的上限為4GB,超出之后,會阻塞整個RS的所有Reigon的請求,直到全局的MemStore總量回落到正常范圍之內。
以上涉及到cacheFlusher在MemStore總量使用超過上限時,選擇Region進行Flush的算法,由MemStoreFlusher.flushOneForGlobalPressure()算法實現。算法的處理流程如下。
關鍵的數據結構:
SortedMapregionsBySize=server.getCopyOfOnlineRegionsSortedBySize();//從RS上獲取在線的Region,以及它們在MemStore上使用量,并按照MemStore使用量作為Key,降序。SetexcludedRegions=newHashSet();//記錄嘗試執行Flush操作失敗的Region…HRegion bestFlushableRegion=getBiggestMemstoreRegion(regionsBySize, excludedRegions,true);//選出storefile個數不超標、當前MemStore使用量最大的RegionHRegion bestAnyRegion=getBiggestMemstoreRegion(regionsBySize, excludedRegions,false);//選出當前MemStore使用量最大的Region
步驟1:RS上在線的Region,按照當前MemStore的使用量進行排序,并存儲在regionsBySize中。
步驟2:選出Region下的Store中的StoreFile的個數未達到hbase.hstore.blockingStoreFiles,并且MemStore使用量最大的Region,存儲到bestFlushableRegion。
步驟3:選出Region下的MemStore使用量最大的Region,存儲到bestAnyRegion對象。
步驟4:如果bestAnyRegion的memstore使用量超出了bestFlushableRegion的兩倍,這從另外一個角度說明,雖然當前bestAnyRegion有超過blockingStoreFiles個數的文件,但是考慮到RS內存的壓力,冒著被執行Compaction的風險,也選擇這個Region作為regionToFlush,因為收益大。否則,直接選擇bestFlushableRegion作為regionToFlush。
步驟5:對regionToFlush執行flush操作。如果操作失敗,regionToFlush放入excludedRegions,避免該Region下次再次被選中,然后返回步驟2執行,否則程序退出。
(2)設置兩個limit,盡可能減少因為控制內存造成數據更新流程的阻塞。
當RS的MemStore使用總量超過(Heap*hbase.regionserver.global.memstore.lowerLimit)的大小時,同樣會向cacheFlusher提交一個Flush請求,并以(1)中Region選擇算法,對其進行Flush操作。與(1)不同,這個過程中RS不會阻塞RS的寫請求。
因此,在生產環境中,我們肯定不希望更新操作被block,一般會配置(upperLimit –lowerlimit)的值在[0.5,0.75]之間,如果是應用寫負載較重,可以設置區間內較大的值。
3.3 StoreFile—HFile
該節請參考:HFile文件格式與HBase讀寫
3.4 Compaction對于服務的影響
該小節請參考:深入分析HBase Compaction機制