HBase 数据库检索性能优化策略大数据培训.docx
《HBase 数据库检索性能优化策略大数据培训.docx》由会员分享,可在线阅读,更多相关《HBase 数据库检索性能优化策略大数据培训.docx(16页珍藏版)》请在冰点文库上搜索。
HBase数据库检索性能优化策略大数据培训
HBase数据库检索性能优化策略
兄弟连IT教育作为国内领先的培训机构,迄今已有10年的教育历史。
8大特色课程:
PHP培训、安卓培训、JAVAEE+大数据、UI设计、HTML5培训、云计算架构师,虚拟现实VR培训,机器人教育培训,在目前IT市场特别火,每门课程都由名师牵头,以认认真真的态度做教育。
HBase数据表介绍
HBase数据库是一个基于分布式的、面向列的、主要用于非结构化数据存储用途的开源数据库。
其设计思路来源于Google的非开源数据库”BigTable”。
HDFS为HBase提供底层存储支持,MapReduce为其提供计算能力,ZooKeeper为其提供协调服务和failover(失效转移的备份操作)机制。
Pig和Hive为HBase提供了高层语言支持,使其可以进行数据统计(可实现多表join等),Sqoop则为其提供RDBMS数据导入功能。
HBase不能支持where条件、Orderby查询,只支持按照主键Rowkey和主键的range来查询,但是可以通过HBase提供的API进行条件过滤。
HBase的Rowkey是数据行的唯一标识,必须通过它进行数据行访问,目前有三种方式,单行键访问、行键范围访问、全表扫描访问。
数据按行键的方式排序存储,依次按位比较,数值较大的排列在后,例如int方式的排序:
1,10,100,11,12,2,20…,906,…。
ColumnFamily是“列族”,属于schema表,在建表时定义,每个列属于一个列族,列名用列族作为前缀“ColumnFamily:
qualifier”,访问控制、磁盘和内存的使用统计都是在列族层面进行的。
Cell是通过行和列确定的一个存储单元,值以字节码存储,没有类型。
Timestamp是区分不同版本Cell的索引,64位整型。
不同版本的数据按照时间戳倒序排列,最新的数据版本排在最前面。
Hbase在行方向上水平划分成N个Region,每个表一开始只有一个Region,数据量增多,Region自动分裂为两个,不同Region分布在不同Server上,但同一个不会拆分到不同Server。
Region按ColumnFamily划分成Store,Store为最小存储单元,用于保存一个列族的数据,每个Store包括内存中的memstore和持久化到disk上的HFile。
图1是HBase数据表的示例,数据分布在多台节点机器上面。
图1.HBase数据表示例
HBase调用API示例
类似于操作关系型数据库的JDBC库,HBaseclient包本身提供了大量可以供操作的API,帮助用户快速操作HBase数据库。
提供了诸如创建数据表、删除数据表、增加字段、存入数据、读取数据等等接口。
清单1提供了一个作者封装的工具类,包括操作数据表、读取数据、存入数据、导出数据等方法。
清单1.HBaseAPI操作工具类代码
importorg.apache.hadoop.conf.Configuration;
importorg.apache.hadoop.hbase.HColumnDescriptor;
importorg.apache.hadoop.hbase.HTableDescriptor;
importorg.apache.hadoop.hbase.KeyValue;
importorg.apache.hadoop.hbase.client.Get;
importorg.apache.hadoop.hbase.client.HBaseAdmin;
importorg.apache.hadoop.hbase.client.HTable;
importorg.apache.hadoop.hbase.client.Put;
importorg.apache.hadoop.hbase.client.Result;
importorg.apache.hadoop.hbase.client.ResultScanner;
importorg.apache.hadoop.hbase.client.Scan;
importorg.apache.hadoop.hbase.util.Bytes;
importjava.io.IOException;
importjava.util.ArrayList;
importjava.util.List;
publicclassHBaseUtil{
privateConfigurationconf=null;
privateHBaseAdminadmin=null;
protectedHBaseUtil(Configurationconf)throwsIOException{
this.conf=conf;
this.admin=newHBaseAdmin(conf);
}
publicbooleanexistsTable(Stringtable)
throwsIOException{
returnadmin.tableExists(table);
}
publicvoidcreateTable(Stringtable,byte[][]splitKeys,String...colfams)
throwsIOException{
HTableDescriptordesc=newHTableDescriptor(table);
for(Stringcf:
colfams){
HColumnDescriptorcoldef=newHColumnDescriptor(cf);
desc.addFamily(coldef);
}
if(splitKeys!
=null){
admin.createTable(desc,splitKeys);
}else{
admin.createTable(desc);
}
}
publicvoiddisableTable(Stringtable)throwsIOException{
admin.disableTable(table);
}
publicvoiddropTable(Stringtable)throwsIOException{
if(existsTable(table)){
disableTable(table);
admin.deleteTable(table);
}
}
publicvoidfillTable(Stringtable,intstartRow,intendRow,intnumCols,
intpad,booleansetTimestamp,booleanrandom,
String...colfams)throwsIOException{
HTabletbl=newHTable(conf,table);
for(introw=startRow;row<=endRow;row++){
for(intcol=0;col Putput=newPut(Bytes.toBytes("row-"));
for(Stringcf:
colfams){
StringcolName="col-";
Stringval="val-";
if(setTimestamp){
put.add(Bytes.toBytes(cf),Bytes.toBytes(colName),
col,Bytes.toBytes(val));
}else{
put.add(Bytes.toBytes(cf),Bytes.toBytes(colName),
Bytes.toBytes(val));
}
}
tbl.put(put);
}
}
tbl.close();
}
publicvoidput(Stringtable,Stringrow,Stringfam,Stringqual,
Stringval)throwsIOException{
HTabletbl=newHTable(conf,table);
Putput=newPut(Bytes.toBytes(row));
put.add(Bytes.toBytes(fam),Bytes.toBytes(qual),Bytes.toBytes(val));
tbl.put(put);
tbl.close();
}
publicvoidput(Stringtable,Stringrow,Stringfam,Stringqual,longts,
Stringval)throwsIOException{
HTabletbl=newHTable(conf,table);
Putput=newPut(Bytes.toBytes(row));
put.add(Bytes.toBytes(fam),Bytes.toBytes(qual),ts,Bytes.toBytes(val));
tbl.put(put);
tbl.close();
}
publicvoidput(Stringtable,String[]rows,String[]fams,String[]quals,
long[]ts,String[]vals)throwsIOException{
HTabletbl=newHTable(conf,table);
for(Stringrow:
rows){
Putput=newPut(Bytes.toBytes(row));
for(Stringfam:
fams){
intv=0;
for(Stringqual:
quals){
Stringval=vals[vv:
vals.length];
longt=ts[vv:
ts.length-1];
put.add(Bytes.toBytes(fam),Bytes.toBytes(qual),t,
Bytes.toBytes(val));
v++;
}
}
tbl.put(put);
}
tbl.close();
}
publicvoiddump(Stringtable,String[]rows,String[]fams,String[]quals)
throwsIOException{
HTabletbl=newHTable(conf,table);
Listgets=newArrayList();
for(Stringrow:
rows){
Getget=newGet(Bytes.toBytes(row));
get.setMaxVersions();
if(fams!
=null){
for(Stringfam:
fams){
for(Stringqual:
quals){
get.addColumn(Bytes.toBytes(fam),Bytes.toBytes(qual));
}
}
}
gets.add(get);
}
Result[]results=tbl.get(gets);
for(Resultresult:
results){
for(KeyValuekv:
result.raw()){
System.out.println("KV:
"+kv+
",Value:
"+Bytes.toString(kv.getValue()));
}
}
}
privatestaticvoidscan(intcaching,intbatch)throwsIOException{
HTabletable=null;
finalint[]counters={0,0};
Scanscan=newScan();
scan.setCaching(caching);//coScanCacheBatchExample-1-SetSetcachingandbatchparameters.
scan.setBatch(batch);
ResultScannerscanner=table.getScanner(scan);
for(Resultresult:
scanner){
counters[1]++;//coScanCacheBatchExample-2-CountCountthenumberofResultsavailable.
}
scanner.close();
System.out.println("Caching:
"+caching+",Batch:
"+batch+
",Results:
"+counters[1]+",RPCs:
"+counters[0]);
}
}
操作表的API都有HBaseAdmin提供,特别讲解一下Scan的操作部署。
HBase的表数据分为多个层次,HRegion->HStore->[HFile,HFile,...,MemStore]。
在HBase中,一张表可以有多个ColumnFamily,在一次Scan的流程中,每个ColumnFamily(Store)的数据读取由一个StoreScanner对象负责。
每个Store的数据由一个内存中的MemStore和磁盘上的HFile文件组成,对应的StoreScanner对象使用一个MemStoreScanner和N个StoreFileScanner来进行实际的数据读取。
因此,读取一行的数据需要以下步骤:
1.按照顺序读取出每个Store
2.对于每个Store,合并Store下面的相关的HFile和内存中的MemStore
这两步都是通过堆来完成。
RegionScanner的读取通过下面的多个StoreScanner组成的堆完成,使用RegionScanner的成员变量KeyValueHeapstoreHeap表示。
一个StoreScanner一个堆,堆中的元素就是底下包含的HFile和MemStore对应的StoreFileScanner和MemStoreScanner。
堆的优势是建堆效率高,可以动态分配内存大小,不必事先确定生存周期。
接着调用seekScanners()对这些StoreFileScanner和MemStoreScanner分别进行seek。
seek是针对KeyValue的,seek的语义是seek到指定KeyValue,如果指定KeyValue不存在,则seek到指定KeyValue的下一个。
Scan类常用方法说明:
scan.addFamily()/scan.addColumn():
指定需要的Family或Column,如果没有调用任何addFamily或Column,会返回所有的Columns;
scan.setMaxVersions():
指定最大的版本个数。
如果不带任何参数调用setMaxVersions,表示取所有的版本。
如果不掉用setMaxVersions,只会取到最新的版本.;
scan.setTimeRange():
指定最大的时间戳和最小的时间戳,只有在此范围内的Cell才能被获取;
scan.setTimeStamp():
指定时间戳;
scan.setFilter():
指定Filter来过滤掉不需要的信息;
scan.setStartRow():
指定开始的行。
如果不调用,则从表头开始;
scan.setStopRow():
指定结束的行(不含此行);
scan.setCaching():
每次从服务器端读取的行数(影响RPC);
scan.setBatch():
指定最多返回的Cell数目。
用于防止一行中有过多的数据,导致OutofMemory错误,默认无限制。
HBase数据表优化
HBase是一个高可靠性、高性能、面向列、可伸缩的分布式数据库,但是当并发量过高或者已有数据量很大时,读写性能会下降。
我们可以采用如下方式逐步提升HBase的检索速度。
预先分区
默认情况下,在创建HBase表的时候会自动创建一个Region分区,当导入数据的时候,所有的HBase客户端都向这一个Region写数据,直到这个Region足够大了才进行切分。
一种可以加快批量写入速度的方法是通过预先创建一些空的Regions,这样当数据写入HBase时,会按照Region分区情况,在集群内做数据的负载均衡。
Rowkey优化
HBase中Rowkey是按照字典序存储,因此,设计Rowkey时,要充分利用排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
此外,Rowkey若是递增的生成,建议不要使用正序直接写入Rowkey,而是采用reverse的方式反转Rowkey,使得Rowkey大致均衡分布,这样设计有个好处是能将RegionServer的负载均衡,否则容易产生所有新数据都在一个RegionServer上堆积的现象,这一点还可以结合table的预切分一起设计。
减少ColumnFamily数量
不要在一张表里定义太多的ColumnFamily。
目前Hbase并不能很好的处理超过2~3个ColumnFamily的表。
因为某个ColumnFamily在flush的时候,它邻近的ColumnFamily也会因关联效应被触发flush,最终导致系统产生更多的I/O。
缓存策略(setCaching)
创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓存中,保证在读取的时候被cache命中。
设置存储生命期
创建表的时候,可以通过HColumnDescriptor.setTimeToLive(inttimeToLive)设置表中数据的存储生命期,过期数据将自动被删除。
硬盘配置
每台RegionServer管理10~1000个Regions,每个Region在1~2G,则每台Server最少要10G,最大要1000*2G=2TB,考虑3备份,则要6TB。
方案一是用3块2TB硬盘,二是用12块500G硬盘,带宽足够时,后者能提供更大的吞吐率,更细粒度的冗余备份,更快速的单盘故障恢复。
分配合适的内存给RegionServer服务
在不影响其他服务的情况下,越大越好。
例如在HBase的conf目录下的hbase-env.sh的最后添加exportHBASE_REGIONSERVER_OPTS="-Xmx16000m$HBASE_REGIONSERVER_OPTS”
其中16000m为分配给RegionServer的内存大小。
写数据的备份数
备份数与读性能成正比,与写性能成反比,且备份数影响高可用性。
有两种配置方式,一种是将hdfs-site.xml拷贝到hbase的conf目录下,然后在其中添加或修改配置项dfs.replication的值为要设置的备份数,这种修改对所有的HBase用户表都生效,另外一种方式,是改写HBase代码,让HBase支持针对列族设置备份数,在创建表时,设置列族备份数,默认为3,此种备份数只对设置的列族生效。
WAL(预写日志)
可设置开关,表示HBase在写数据前用不用先写日志,默认是打开,关掉会提高性能,但是如果系统出现故障(负责插入的RegionServer挂掉),数据可能会丢失。
配置WAL在调用JavaAPI写入时,设置Put实例的WAL,调用Put.setWriteToWAL(boolean)。
批量写
HBase的Put支持单条插入,也支持批量插入,一般来说批量写更快,节省来回的网络开销。
在客户端调用JavaAPI时,先将批量的Put放入一个Put列表,然后调用HTable的Put(Put列表)函数来批量写。
客户端一次从服务器拉取的数量
通过配置一次拉去的较大的数据量可以减少客户端获取数据的时间,但是它会占用客户端内存。
有三个地方可进行配置:
1)在HBase的conf配置文件中进行配置hbase.client.scanner.caching;
2)通过调用HTable.setScannerCaching(intscannerCaching)进行配置;
3)通过调用Scan.setCaching(intcaching)进行配置。
三者的优先级越来越高。
RegionServer的请求处理IO线程数
较少的IO线程适用于处理单次请求内存消耗较高的BigPut场景(大容量单次Put或设置了较大cache的Scan,均属于BigPut)或ReigonServer的内存比较紧张的场景。
较多的IO线程,适用于单次请求内存消耗低,TPS要求(每秒事务处理量(TransactionPerSecond))非常高的场景。
设置该值的时候,以监控内存为主要参考。
在hbase-site.xml配置文件中配置项为hbase.regionserver.handler.co