1、通過(guò)Configuration初始化集群Connection
1.1、Connction維持了客戶端到整個(gè)HBase集群的鏈接,如一個(gè)HBase集群有2個(gè)Master、5個(gè)RegionServer,那么一般來(lái)說(shuō)整個(gè)Connection會(huì)維持一個(gè)到Active Master的TCP連接和5個(gè)到ReginonServer的TCP鏈接。
1.2、通常一個(gè)進(jìn)程只需要為一個(gè)獨(dú)立的集群建立一個(gè)Connection即可,并不需要建立連接池。
1.3、Connection還緩存了訪問(wèn)的Meta信息,后續(xù)的大部分請(qǐng)求都可以通過(guò)緩存的Meta信息定位到對(duì)應(yīng)的Region Server。
2、通過(guò)Connection初始化Table
2.1、Table是一個(gè)非常輕量級(jí)的對(duì)象,它所使用的連接資源、配置信息、線程池、Meta緩存等都來(lái)自于Connection。
2.2、由同一個(gè)Connection創(chuàng)建的多個(gè)Table,都會(huì)共享連接、配置信息、線程池、Meta緩存這些資源
2.3、在branch-1以及之前的版本中,Table并不是線程安全的類(lèi),所以不建議在多個(gè)線程中使用同一個(gè)Table實(shí)例。在HBase 2.00及之后,Table已經(jīng)實(shí)現(xiàn)了線程安全。
2.4、由于Table是一個(gè)非常輕量級(jí)的對(duì)象,所以可以通過(guò)Connection為每個(gè)請(qǐng)求創(chuàng)建一個(gè)Table,但是記住,在該請(qǐng)求執(zhí)行完畢之后需要關(guān)閉Table資源。
3、hbase:meta
3.1、hbase:meta用來(lái)保存整個(gè)集群的region信息
3.2、hbase:meta在HBase中保證始終只有一個(gè)Region,這是為了確保meta表多次操作的原子性,因?yàn)镠Base本質(zhì)上只支持Region級(jí)別的事務(wù)<所謂Region級(jí)別的事務(wù)是指:當(dāng)多個(gè)操作落在同一個(gè)Region內(nèi)時(shí),HBase能保證這一批操作執(zhí)行的原子性。如果多個(gè)操作分散在不同的Region,則無(wú)法保證這批操作的原子性>
3.3、hbase:meta的一行就對(duì)應(yīng)一個(gè)Region. 它的rowkey主要由TableName、StartRow、TimeStamp、EncodeName、標(biāo)識(shí)這個(gè)Region是屬于哪個(gè)表,表Rowkey的起始行以及Region的創(chuàng)建時(shí)間戳
3.4、hbase:meta只有一個(gè)列簇<info>他有4列,info:regioninfo、info:seqnumDuringOpen、info:server、info:serverstartcode,表示這個(gè)表rowkey的起始位置,region落在哪個(gè)RegionServer上以及所在RegionServer的啟動(dòng)時(shí)間戳
4、HBase超時(shí)參數(shù)設(shè)置
hbase.rpc.timeout:表示單次RPC請(qǐng)求的超時(shí)時(shí)間,默認(rèn)是60 000ms。
hbase.clinet.retries.number:最多允許發(fā)生多少次RPC重試操作默認(rèn)是35次。
hbase.clinet.pause:表示連續(xù)兩次RPC重試之間的休眠時(shí)間,默認(rèn)是100ms。重試休眠時(shí)間是按照隨機(jī)退避算法設(shè)計(jì)的。也就是重試次數(shù)越多,休眠間隔時(shí)間就會(huì)越來(lái)越長(zhǎng)。按照默認(rèn)的重試次數(shù)35,則可能長(zhǎng)期卡在休眠和重試兩個(gè)步驟中
hbase.clinet.operation.timeout:表示單次API的超時(shí)時(shí)間,默認(rèn)值為1 200 000ms.一次API可能會(huì)有多次RPC重試,這個(gè)參數(shù)是API操作的總超時(shí)。
5、CAS<checkAndPut、inCrementColumnValue>操作是Region級(jí)別串行執(zhí)行的,吞吐受限,在HBase 2.x版本已調(diào)整設(shè)計(jì),對(duì)于同一個(gè)Region內(nèi)部的不同行可以并行執(zhí)行CAS,這樣大大提交了Region內(nèi)部的CAS吞吐
6、Filter使用避坑指南
6.1、PrefixFilter 前綴過(guò)濾
低效使用方式:
Scan scan = new Scan();
scan.setFilter(new PrefixFillter(Bytes.toBytes("def")));
這個(gè)Scan雖然能得到預(yù)期的效果,但是并不高效,因?yàn)閷?duì)于rowKey在區(qū)間(-∞,def)的數(shù)據(jù),會(huì)一條條掃描,發(fā)現(xiàn)前綴不為def,就讀下一行,直到找到第一個(gè)rowkey為def的行為止
高效使用方式:
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("def"));
scan.setFilter(new PrefixFillter(Bytes.toBytes("def")));
增加了一個(gè)startRow。RegionServer發(fā)現(xiàn)Scan設(shè)置了startRow,首先會(huì)尋址定位到startRow。這樣就跳過(guò)了大量的(-∞,def)的數(shù)據(jù)。
最高效的使用方式:
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("def"));
scan.setStopRow(Bytes.toBytes("deg"));
將PrefixFilter直接展開(kāi),掃描[def,deg)區(qū)間的數(shù)據(jù),這樣效率是最高的。
6.2、PageFilter:表有5個(gè)Region起始key為(-∞,1)、[1,2)、[2,3)、[3,4)、[4,+∞)每個(gè)Region 都有超過(guò)100條數(shù)據(jù)
錯(cuò)誤的使用方式:
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("1"));
scan.setStopRow(Bytes.toBytes("3"));
scan.setFilter(new PageFilter(100))
這樣寫(xiě)得出來(lái)的分頁(yè)每頁(yè)數(shù)據(jù)就會(huì)有200 條。但是明明設(shè)置了分頁(yè)每頁(yè)條數(shù)是100。原因是,它需要scan 2個(gè)Region.scan從一個(gè)region切換到另一個(gè)region之前的那個(gè)Filter的內(nèi)部狀態(tài)就無(wú)效了,新的region內(nèi)部用的是一個(gè)全新的Filter.Filter計(jì)數(shù)器被清零。Filter不是全局的, 所以它分別從2個(gè)region各查了100 條,總共200 條返回。
正確的使用方式:
如果想實(shí)現(xiàn)分頁(yè)功能,可以不通過(guò)Filter而直接通過(guò)limit來(lái)實(shí)現(xiàn)。
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("1"));
scan.setStopRow(Bytes.toBytes("3"));
scan.setLimit(100);
所以對(duì)于用戶來(lái)說(shuō),正常情況下PageFilter并沒(méi)有太多的存在價(jià)值
6.3、SingleColumnValueFilter
使用方式:
Scan scan = new Scan();
SingleColumnValueFilter scvf = new? SingleColumnValueFilter(Bytes.toBytes("family"),Bytes.toBytes("qualifier"),
CompareOp.EQUAL,Bytes.toBytes("value"));
scan.setFilter(scvf);
表面上是將列簇為family,列為qualifier,值為value的cell返回給用戶,但事實(shí)上那些不包含family:qualifier的行也會(huì)默認(rèn)返回給用戶,如果用戶不希望讀取那些不包含family:qualifier的數(shù)據(jù),需要設(shè)計(jì)如下scan
Scan scan = new Scan();
SingleColumnValueFilter scvf = new? SingleColumnValueFilter(Bytes.toBytes("family"),Bytes.toBytes("qualifier"),
CompareOp.EQUAL,Bytes.toBytes("value"));
scvf.setFiterIfMisssing(true);
scan.setFilter(scvf);
另外當(dāng)SingleColumnValueFilter設(shè)置為filterIfMisssing為true時(shí),和其他Filter組合成FilterList時(shí)可能導(dǎo)致返回的結(jié)果不正確。建議是不要使用SingleColumnValueFilter與其他Filter組合成FilterList。 直接指定列,通過(guò)ValueFilter替換掉SingleColumnValueFilter
Scan scan = new Scan();
ValueFilter vf = new ValueFilter(CompareOf.EQUAL,new BinaryComparatoe(Bytes.toBytes("value")));
scan.addColum(Bytes.toBytes("family"),Bytes.toBytes("qualifier"));
scan.setFilter(vf);
7、HBase寫(xiě)入方式對(duì)比
7.1、table.put(Put):
每次執(zhí)行都會(huì)執(zhí)行一次RPC和磁盤(pán)持久化,寫(xiě)入吞吐受限于磁盤(pán)帶寬、網(wǎng)絡(luò)帶寬,不會(huì)有數(shù)據(jù)丟失能保證put操作的原子性。
7.2、table.put(List<Put>):
客戶端打包一批put提交,執(zhí)行一次RPC,一次WAL。相比第一種省略了多次往返的RPC和磁盤(pán)持久化。但是時(shí)間會(huì)變長(zhǎng)。如果打包的put分布在多個(gè)Region。則不能保證這一批put的原子性,應(yīng)為HBase不支持跨Region的多行事務(wù),失敗的put會(huì)經(jīng)歷若干次重試。
7.3、bulk load:
將待寫(xiě)入的數(shù)據(jù)生成HFile,然后采用bulk load方式將HFile直接加載到對(duì)于的Region的CF內(nèi)。這是一種完全離線的快速寫(xiě)入方式。它應(yīng)該是最快的批量寫(xiě)入手段,同時(shí)不會(huì)對(duì)線上的集群產(chǎn)生巨大壓力,在load完HFile之后,CF內(nèi)部會(huì)進(jìn)行Compaction,但是Compaction是異步的且可以限速,所以bulk load對(duì)線上集群非常友好。
使用場(chǎng)景舉例:
7.3.1、兩個(gè)集群互為主備,其中一個(gè)集群由存在數(shù)據(jù)丟失,想通過(guò)另一備份集群的數(shù)據(jù)來(lái)修復(fù)異常集群。最快的方式是:把備份集群的數(shù)據(jù)導(dǎo)一個(gè)快照拷貝到異常集群,然后通過(guò)copyTable工具掃快照生成HFile,然后bulk load 到異常集群,完成數(shù)據(jù)的修復(fù)。
7.3.2、當(dāng)用戶寫(xiě)入大量數(shù)據(jù)后,發(fā)現(xiàn)選擇的split keys不合適,想重新選擇split keys見(jiàn)表,這時(shí)也可以通過(guò) snapshort生成HFile再bulk load的方式生成新表。