Facebook在談到其選擇HBase作為新的message體系的存儲時,提到了一點是hbase的高性能寫,那hbase的寫性能狀況到底如何了,還是需要靠測試來說明,在這篇blog中就以我們目前做的一些測試來看看hbase的寫速度,以及分享下我們對於測試中體現出來的數據的分析和測試時碰到的一些問題,還希望對hbase有研究或實踐的同學多加指正。
測試環境:
1、4個client,每client 25個寫線程,每個client寫入1kw數據,其他還有一些更長時間的測試,例如連續寫7個小時等;
2、12台region server(8 core 超線程 => 16 core, Intel Xeon E5620, 12塊1T的Sata盤,沒做raid, 24g, 12g heap);
3、HBase: 0.90.2, Hadoop: 0.20.2 CDH3U0。
說明:
1、由於我們的場景中要求數據寫入的絕對可靠,因此在HTable的基礎上增加了一個寫安全的方法:putAndCommit,代碼如下:
public void putAndCommit(final Put put) throws IOException{
long beginTime = System.currentTimeMillis();
try{
connection.getRegionServerWithRetries(new ServerCallable<Boolean>(connection,tableName,put.getRow()) {
public Boolean call() throws Exception {
setHostName(location.getServerAddress().getHostname());
server.put(location.getRegionInfo().getRegionName(),put);
return true;
}
});
}
catch(Exception e){
throw new IOException(e);
}
finally{
long rt = System.currentTimeMillis() - beginTime;
if( isInfoEnabled && putPrintThreshold > 0 & rt > putPrintThreshold){
LOG.info(df.format(new Date())+" Put rt is: "+rt+" ms, hostName is: "+hostName);
}
}
}
所有的測試場景均采用這個方法進行寫入。
2、寫入時的key均為120字節,value為1024字節,key和value均為[A-Z,0-9]的隨機組合;
3、由於YCSB寫的其實一般,於是我們自行寫了一個用於做hbase讀寫測試的類,可支持多table/多cf/多列,不同key/value size,不同讀寫線程比,不同讀cache命中率的測試。
根據測試的狀況,最終我們對寫速度進行了6種不同場景的測試,以判斷寫速度的影響因素以及波動的影響因素,最終形成了以下的兩個測試結果圖:
其中X軸代表程序的運行時間,Y軸代表平均響應時間,單位是ms。
第一個測試用例為默認參數的情況,它代表的為client以及server均為默認參數,從上面的圖中可以看出在這種測試場景下寫速度的波動范圍比較大,從平均的4ms到偶爾的500ms+都會存在,並且在長時間的測試下還會出現長達幾秒的狀況,對於online性質的業務而言,這顯然是不可接受的,在做這個測試的時候,還碰到了hbase的一個典型問題,就是寫空表時壓力在很長一段時間內都會集中在一台region server上,沒有辦法很好的發揮集群的能力,這種情況下可以選擇采用0.90.2以後支持的pre-sharding的方式,但挺難用的,我們目前采用的方法是自己增加了一個將table的region平均分散到各region server的管理功能,這樣可以通過手動的方式來充分發揮集群的能力,當然,這還是要求在寫的時候是分散的,如果寫連續key的話還是會導致在同一台,這是在設計hbase rowKey時要仔細考慮的。
因此需要想辦法避免寫速度的波動,下面的測試基本都是圍繞這個宗旨而進行的,根據之前對hbase client代碼的閱讀,client在寫時會進行多次的重試,重試的間隔采用的是指數避讓的方法,間隔時間為1秒為單位,在默認10次重試的情況下,就會是這樣的間隔時間:[1,1,1,2,2,4,4,8,16,32],從這可以看出當server出現異常時會導致client長時間的等待,因此決定將client的重試間隔時間調短為20ms(hbase.client.pause),重試次數調整為11次(hbase.client.retries.number),保證客戶端會在2s+的范圍內進行重試,順帶將hbase.ipc.client.tcpnodelay設置為true,同時將ipc.ping.interval也調整為3s,避免長時間的等待,這樣調整後測試的結果如上圖所示,可以看到這時寫速度仍然是波動的,但波動的范圍就好多了,基本會在100ms以下,也算是有所進步了。
結合client代碼來看,寫速度仍然波動的話從client部分已經很難有所調整了,因此要從server端想辦法了,根據之前對server代碼的分析以及上面兩測試時server的日志,初步判斷造成很大波動的主要原因是split,於是決定關閉split進行測試,我們將hbase.regionserver.regionSplitLimit設置為1來關閉split,根據上面圖中關閉split的測試結果,可以看到在不做split時,寫速度的波動頻率比以前小了很多,同時波動的范圍也減小了,但仍然在波動。
繼續分析server日志,看到的一個主要問題是隨著寫入數據越來越多,server會compact出越來越大的文件,於是compact的時候磁盤io很吃緊,會造成影響,因此決定再關閉compact,關閉compact的方法為將hbase.hstore.compactionThreshold調整為Integer.MAX_VALUE,在關閉compact的第一次測試中,發現波動范圍更大,查看日志發現會有偶爾的“Blocking updates for ”和“delaying flush up to ”情況,結合region server代碼發現Memstore在flush region時會先檢查store中store file是不是太多了,太多了則會先進行compact,並將flush推遲,而flush推遲的過程中,會導致memstore中寫了很多的數據,在memstore的大小大於了其兩倍的空間(默認情況下也就是128m)時,會阻塞住此時的寫/刪除請求,等到memstore大小降下去後才會恢復,於是相應的對這兩個參數進行了調整,hbase.hstore.blockingStoreFiles調整為了Integer.MAX_VALUE,hbase.hregion.memstore.block.multiplier調整為了8,調整完畢後進行關閉compact/split的測試才得到了上面圖中的測試結果,從圖中的測試結果來看,寫的波動頻率比僅關閉split時又降低了,波動范圍基本在20ms以下,到此算是不錯了。
題外話:在另外一個一台region server上很多region的測試下,發現即使關閉了compact/split,調整了上面的參數後,仍然會有很大波動的情況下,分析後發現主要是因為region太多,導致memstore加起來的大小很快超過了默認的0.35 * heap的阈值,在這種情況下會導致要阻塞進行刷新,對於這種情況只能是降低region數或者是調小memstore的大小;另外一個原因則是hlog太多,默認情況下當hlog有32個時,就會要對所有region的memstore進行flush,而不管這個時候memstore使用了多少,悲催的是memstore的flush是串行的,500個可想而知這個影響就大了,這種情況下可以調大hlog最大的個數,但其實作用也不大,因此看起來在默認情況下hbase單個region server其實是不太適合跑很多region的,當然,這取決於寫頻率,寫頻率低的話倒也OK的。