Result[] results = tbl.get(gets); for (Result result : results) { for (KeyValue kv : result.raw()) { System.out.println("KV: " + kv + ", Value: " + Bytes.toString(kv.getValue())); } } } private static void scan(int caching, int batch) throws IOException { HTable table = null; final int[] counters = {0, 0}; Scan scan = new Scan(); scan.setCaching(caching); // co ScanCacheBatchExample-1-Set Set caching and batch parameters. scan.setBatch(batch); ResultScanner scanner = table.getScanner(scan); for (Result result : scanner) { counters[1]++; // co ScanCacheBatchExample-2-Count Count the number of Results available. } 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 中,一张表可以有多个 Column Family,在一次 Scan 的流程中,每个 Column Family(Store) 的数据读取由一个 StoreScanner 对象负责。每个 Store 的数据由一个内存中的 MemStore 和磁盘上的 HFile 文件组成,对应的 StoreScanner 对象使用一个 MemStoreScanner 和 N 个 StoreFileScanner 来进行实际的数据读取。
因此,读取一行的数据需要以下步骤:
按照顺序读取出每个 Store
对于每个 Store,合并 Store 下面的相关的 HFile 和内存中的 MemStore
这两步都是通过堆来完成。RegionScanner 的读取通过下面的多个 StoreScanner 组成的堆完成,使用 RegionScanner 的成员变量 KeyValueHeap storeHeap 表示。一个 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(int timeToLive) 设置表中数据的存储生命期,过期数据将自动被删除。
硬盘配置
每台 RegionServer 管理 10~1000 个 Regions,每个 Region 在 1~2G,则每台 Server 最少要 10G,最大要 1000*2G=2TB,考虑 3 备份,则要 6TB。方案一是用 3 块 2TB 硬盘,二是用 12 块 500G 硬盘,带宽足够时,后者能提供更大的吞吐率,更细粒度的冗余备份,更快速的单盘故障恢复。
分配合适的内存给 RegionServer 服务
在不影响其他服务的情况下,越大越好。例如在 HBase 的 conf 目录下的 hbase-env.sh 的最后添加 export HBASE_REGIONSERVER_OPTS=”