HBase如何合理設(shè)置客戶端Write Buffer
HBase客戶端API提供了Write Buffer的方式,即批量提交一批Put對象到HBase服務(wù)端。
1. 什么時候需要Write Buffer?
默認(rèn)情況下,一次Put操作即要與Region Server執(zhí)行一次RPC操作,其執(zhí)行過程可以被拆分為以下三個部分:
T1:RTT(Round-Trip Time),即網(wǎng)絡(luò)往返時延,它指從客戶端發(fā)送數(shù)據(jù)開始,到客戶端收到來自服務(wù)端的確認(rèn),總共經(jīng)歷的時延,不包括數(shù)據(jù)傳輸?shù)臅r間;
T2:數(shù)據(jù)傳輸時間,即Put所操作的數(shù)據(jù)在客戶端與服務(wù)端之間傳輸所消耗的時間開銷,當(dāng)數(shù)據(jù)量大的時候,T2的時間開銷不容忽略;
T3:服務(wù)端處理時間,對于Put操作,即寫入WAL日志(如果設(shè)置了WAL標(biāo)識為true)、更新MemStore等。
其中,T2和T3都是不可避免的時間開銷,那么能不能減少T1呢?假設(shè)我們將多次Put操作打包起來一次性提交到服務(wù)端,則可以將T1部分的總時間從T1 * N降低為T1,其中T1指的是單次RTT時間,N為Put的記錄條數(shù)。
正是出于上述考慮,HBase為用戶提供了客戶端緩存批量提交的方式(即Write Buffer)。假設(shè)RTT的時間較長,如1ms,則該種方式能夠顯著提高整個集群的寫入性能。
那么,什么場景下適用于該種模式呢?下面簡單分析一下:
如果Put提交的是小數(shù)據(jù)(如KB級別甚至更小)記錄,那么T2很小,因此,通過該種模式減少T1的開銷,能夠明顯提高寫入性能。
如果Put提交的是大數(shù)據(jù)(如MB級別)記錄,那么T2可能已經(jīng)遠(yuǎn)大于T1,此時T1與T2相比可以被忽略,因此,使用該種模式并不能得到很好的性能提升,不建議通過增大Write Buffer大小來使用該種模式。
2. 如何配置使用Write Buffer?
如果要啟動Write Buffer模式,則調(diào)用HTable的以下API將auto flush設(shè)置為false:
- void setAutoFlush(boolean autoFlush)
默認(rèn)配置下,Write Buffer大小為2MB,可以根據(jù)應(yīng)用實際情況,通過以下任意方式進(jìn)行自定義:
1) 調(diào)用HTable接口設(shè)置,僅對該HTable對象起作用:
- void setWriteBufferSize(long writeBufferSize) throws IOException
2) 在hbase-site.xml中配置,所有HTable都生效(下面設(shè)置為5MB):
- <property>
- <name>hbase.client.write.buffer</name>
- <value>5242880</value>
- </property>
該種模式下向服務(wù)端提交的時機(jī)分為顯式和隱式兩種情況:
1) 顯式提交:用戶調(diào)用flushCommits()進(jìn)行提交;
2) 隱式提交:當(dāng)Write Buffer滿了,客戶端會自動執(zhí)行提交;或者調(diào)用了HTable的close()方法時無條件執(zhí)行提交操作。
3. 如何確定每次flushCommits()時實際的RPC次數(shù)?
客戶端提交后,所有的Put操作可能涉及不同的行,然后客戶端負(fù)責(zé)將這些Put對象根據(jù)row key按照 region server分組,再按region server打包后提交到region server,每個region server做一次RPC請求。如下圖所示:
4. 如何確定每次flushCommits()時提交的記錄條數(shù)?
下面我們先從HBase存儲原理層面“粗略”分析下HBase中的一條Put記錄格式:
HBase中Put對象的大小主要由若干個KeyValue對的大小決定(Put繼承自org/apache/hadoop/hbase/client/Mutation.java,具體見Mutation的代碼所示),而KeyValue類中自帶的字段占用約50~60 bytes(參考源碼:org/apache/hadoop/hbase/KeyValue.java),那么客戶端Put一行數(shù)據(jù)時,假設(shè)column qualifier個數(shù)為N,row key長度為L1 bytes,value總長度為L2 bytes,則該P(yáng)ut對象占用大小可按以下公式預(yù)估:
Put Size = ((50~60) + L1) * N + L2) bytes
下面我們通過對HBase的源碼分析來進(jìn)一步驗證以上理論估算值:
HBase客戶端執(zhí)行put操作后,會調(diào)用put.heapSize()累加當(dāng)前客戶端buffer中的數(shù)據(jù),滿足以下條件則調(diào)用flushCommits()將客戶端數(shù)據(jù)提交到服務(wù)端:
1)每次put方法調(diào)用時可能傳入的是一個List<Put>,此時每隔DOPUT_WB_CHECK條(默認(rèn)為10條),檢查當(dāng)前緩存數(shù)據(jù)是否超過writeBufferSize,超過則強(qiáng)制執(zhí)行刷新;
2)autoFlush被設(shè)置為true,此次put方法調(diào)用后執(zhí)行一次刷新;
3)autoFlush被設(shè)置為false,但當(dāng)前緩存數(shù)據(jù)已超過設(shè)定的writeBufferSize,則執(zhí)行刷新。
- private void doPut(final List<Put> puts) throws IOException {
- int n = 0;
- for (Put put : puts) {
- validatePut(put);
- writeBuffer.add(put);
- currentWriteBufferSize += put.heapSize();
- // we need to periodically see if the writebuffer is full instead
- // of waiting until the end of the List
- n++;
- if (n % DOPUT_WB_CHECK == 0
- && currentWriteBufferSize > writeBufferSize) {
- flushCommits();
- }
- }
- if (autoFlush || currentWriteBufferSize > writeBufferSize) {
- flushCommits();
- }
- }
由上述代碼可見,通過put.heapSize()累加客戶端的緩存數(shù)據(jù),作為判斷的依據(jù);那么,我們可以編寫一個簡單的程序生成Put對象,調(diào)用其heapSize()方法,就能得到一行數(shù)據(jù)實際占用的客戶端緩存大?。ㄔ摮绦蛐枰獋鬟f上述三個變量:N,L1,L2作為參數(shù)):
- import org.apache.hadoop.hbase.client.Put;
- import org.apache.hadoop.hbase.util.Bytes;
- public class PutHeapSize {
- /**
- * @param args
- */
- public static void main(String[] args) {
- if (args.length != 3) {
- System.out.println("Invalid number of parameters: 3 parameters!");
- System.exit(1);
- }
- int N = Integer.parseInt(args[0]);
- int L1 = Integer.parseInt(args[1]);
- int L2 = Integer.parseInt(args[2]);
- byte[] rowKey = new byte[L1];
- byte[] value = null;
- Put put = new Put(rowKey);
- for (int i = 0; i < N; i++) {
- put.add(Bytes.toBytes("cf"), Bytes.toBytes("c" + i), value);
- }
- System.out.println("Put Size: " + (put.heapSize() + L2) + " bytes");
- }
- }
該程序可以用來預(yù)估當(dāng)前設(shè)置的write buffer可以一次性批量提交的記錄數(shù):
- Puts Per Commit = Write Buffer Size / Put Size
更進(jìn)一步地,如果知道業(yè)務(wù)中的每秒產(chǎn)生的數(shù)據(jù)量,就可知道客戶端大概多長時間會隱式調(diào)用flushCommits()向服務(wù)端提交一次;同時也可反過來根據(jù)數(shù)據(jù)實時刷新頻率調(diào)整Write Buffer大小。
5. Write Buffer有什么潛在的問題?
首先,Write Buffer存在于客戶端的本地內(nèi)存中,那么當(dāng)客戶端運(yùn)行出現(xiàn)問題時,會導(dǎo)致在Write Buffer中未提交的數(shù)據(jù)丟失;由于HBase服務(wù)端還未收到這些數(shù)據(jù),因此也無法通過WAL日志等方式進(jìn)行數(shù)據(jù)恢復(fù)。
其次,Write Buffer方式本身會占用客戶端和HBase服務(wù)端的內(nèi)存開銷,具體見下節(jié)的詳細(xì)分析。
6. 如何預(yù)估Write Buffer占用的內(nèi)存?
客戶端通過Write Buffer方式提交的話,會導(dǎo)致客戶端和服務(wù)端均有一定的額外內(nèi)存開銷,Write Buffer Size越大,則占用的內(nèi)存越大。客戶端占用的內(nèi)存開銷可以粗略使用以下公式預(yù)估:
- hbase.client.write.buffer * number of HTable object for writing
而對于服務(wù)端來說,可以使用以下公式預(yù)估占用的Region Server總內(nèi)存開銷:
- hbase.client.write.buffer * hbase.regionserver.handler.count * number of region server
其中,hbase.regionserver.handler.count為每個Region Server上配置的RPC Handler線程數(shù)。
原文鏈接:http://www.cnblogs.com/panfeng412/archive/2012/10/16/how-to-use-hbase-client-write-buffer.html
【編輯推薦】