一条数据的HBase之旅,简明HBase入门教程13:两种读取模式
华为云上的NoSQL数据库服务CloudTable,基于Apache HBase,提供全托管式集群服务,集成了时序数据库OpenTSDB与时空数据库GeoMesa,在TB/PB级别的海量数据背景下,可提供ms级查询以及千万级TPS,点我了解详情。
Get
Get是指基于确切的RowKey去获取一行数据,通常被称之为随机点查,这正是HBase所擅长的读取模式。一次Get操作,包含两个主要步骤:
1.构建Get
基于RowKey构建Get对象的最简单示例代码如下:
final byte[] key = Bytes.toBytes("66660000431^201803011300");
Get get = new Get(key);
可以为构建的Get对象指定返回的列族:
final byte[] family = Bytes.toBytes("I");
// 指定返回列族I的所有列
get.addFamily(family);
也可以直接指定返回某列族中的指定列:
final byte[] family = Bytes.toBytes("I");
final byte[] qualifierMobile = Bytes.toBytes("M");
// 指定返回列族I中的列M
get.addColumn(family, qualifierMobile);
2.发送Get请求并且获取对应的记录
与写数据类似,发送Get请求的接口也是由Table提供的,获取到的一行记录,被封装成一个Result对象。也可以这么理解一个Result对象:
关联一行数据,一定不可能包含跨行的结果
包含一个或多个被请求的列。有可能包含这行数据的所有列,也有可能仅包含部分列
示例代码如下:
try (Table table = conn.getTable(TABLE)) {
final byte[] key = Bytes.toBytes("66660000431^201803011300");
Get get = new Get(key);
// 记录Table提供的get接口获取一行记录
Result result = table.get(get);
// 通过CellScanner来遍历该Result中的所有列
CellScanner scanner = result.cellScanner();
while (scanner.advance()) {
Cell cell = scanner.current();
// 读取Cell中的信息...
}
}
上面给出的是一次随机获取一行记录的例子,但事实上,一次获取多行记录的需求也是普遍存在的,Table中也定义了Batch Get的接口,这样可以在一次网络请求中同时获取多行数据。示例代码如下:
try (Table table = conn.getTable(TN)) {
// 基于多个Get对象构建一个List
List<Get> gets = new ArrayList<>(8);
gets.add(new Get(Bytes.toBytes("11110000431^201803011300")));
gets.add(new Get(Bytes.toBytes("22220000431^201803011300")));
gets.add(new Get(Bytes.toBytes("33330000431^201803011300")));
gets.add(new Get(Bytes.toBytes("44440000431^201803011300")));
gets.add(new Get(Bytes.toBytes("55550000431^201803011300")));
gets.add(new Get(Bytes.toBytes("66660000431^201803011300")));
gets.add(new Get(Bytes.toBytes("77770000431^201803011300")));
gets.add(new Get(Bytes.toBytes("88880000431^201803011300")));
// 调用Table的Batch Get的接口获取多行记录
Result[] results = table.get(gets);
for (Result result : results) {
CellScanner scanner = result.cellScanner();
while (scanner.advance()) {
Cell cell = scanner.current();
// 读取Cell中的信息...
}
}
}
关于Batch Get需要补充说明一点信息:获取到的Result列表中的结果的顺序,与给定的RowKey顺序是一致的。
Scan
HBase中的数据表通过划分成一个个的Region来实现数据的分片,每一个Region关联一个RowKey的范围区间,而每一个Region中的数据,按RowKey的字典顺序进行组织。
正是基于这种设计,使得HBase能够轻松应对这类查询:”指定一个RowKey的范围区间,获取该区间的所有记录”, 这类查询在HBase被称之为Scan。
一次Scan操作,包括如下几个关键步骤:
1.构建Scan
最简单也最常用的构建Scan对象的方法,就是仅仅指定Scan的StartRow与StopRow。示例如下:
final byte[] startKey = Bytes.toBytes("66660000431^20180301");
final byte[] stopKey = Bytes.toBytes("66660000431^20180501");
Scan scan = new Scan();
/**
* 2.0版本之前,设置Scan Key Range的接口为:
* scan.setStartRow(startKey).setStopRow(stopKey);
* 但在2.0版本中,该接口已经被标注为Deprecated接口,即已不推荐使用.
* 下面是推荐的接口:
*/
scan.withStartRow(startKey).withStopRow(stopKey);
如果StartRow未指定,则本次Scan将从表的第一行数据开始读取。
如果StopRow未指定,而且在不主动停止本次Scan操作的前提下,本次Scan将会一直读取到表的最后一行记录。
如果StartRow与StopRow都未指定,那本次Scan就是一次全表扫描操作。
同Get类似,Scan也可以主动指定返回的列族或列:
final byte[] family = Bytes.toBytes("I");
/**
* 为本次Scan操作指定返回的列族
* scan.addFamily(family);
*/
final byte[] qualifierMobile = Bytes.toBytes("M");
scan.addColumn(family, qualifierMobile);
2.获取ResultScanner
ResultScanner scanner = table.getScanner(scan);
3.遍历查询结果
Result result = null;
// 通过scanner.next方法获取返回的每一行数据
while ((result = scanner.next()) != null) {
// 读取result中的结果...
}
4.关闭ResultScanner
通过下面的方法可以关闭一个ResultScanner:
scanner.close();
如果基于Java传统的try-catch-finally语法,上述close方式需要在finally模块显式调用。但如果是是基于try-with-resource语法,则由Java框架自动调用。
将上面1~4步骤联合起来的示例代码如下:
try (Table table = conn.getTable(TABLE)) {
final byte[] start = Bytes.toBytes("66660000431^20180301");
final byte[] stop = Bytes.toBytes("66660000431^20180501");
Scan scan = new Scan();
scan.withStartRow(start).withStopRow(stop);
// 基于try-with-resource语法,try语句块结束后
// scanner的close方法会自动被调用
try (ResultScanner scanner = table.getScanner(scan))
{
Result result = null;
while ((result = scanner.next()) != null) {
// 读取result中的结果...
}
}
}
Scan的其它重要参数
a) Caching: 设置一次RPC请求批量读取的Results数量
下面的示例代码设定了一次读取回来的Results数量为100:
scan.setCaching(100);
Client每一次往RegionServer发送scan请求,都会批量拿回一批数据(由Caching决定过了每一次拿回的Results数量),然后放到本次的Result Cache中:
应用每一次读取数据时,都是从本地的Result Cache中获取的。如果Result Cache中的数据读完了,则Client会再次往RegionServer发送scan请求获取更多的数据。
b) Batch: 设置每一个Result中的列的数量
下面的示例代码设定了每一个Result中的列的数量的限制值为3:
scan.setBatch(3);
该参数适用于一行数据过大的场景,这样,一行数据被请求的列会被拆成多个Results返回给Client。
举例说明如下:
假设一行数据中有{Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09, Col10}十个列,而且,Scan中设置的Batch为3:
那么,这一行数据将会被拆成4个Results返回:
Result1 -> {Col01,Col02,Col03}
Result1 -> {Col04,Col05,Col06}
Result1 -> {Col07,Col08,Col09}
Result1 -> {Col10}
关于Caching参数,我们说明了是Client每一次从RegionServer侧获取到的Results的数量,上例中,一行数据被拆成了4个Results,这将会导致Caching中的计数器被减了4次。结合Caching与Batch,我们再列举一个稍复杂的例子:
假设,Scan的参数设置如下:
final byte[] start = Bytes.toBytes("Row1");
final byte[] stop = Bytes.toBytes("Row5");
Scan scan = new Scan();
scan.withStartRow(start).withStopRow(stop);
scan.setCaching(10);
scan.setBatch(3);
待读取的数据RowKey与所关联的列集如下所示
Row1: {Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09,Col10}
Row2: {Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09,Col10,Col11}
Row3: {Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09,Col10}
再回顾一下Caching与Batch的定义:
Caching: 影响一次读取返回的Results数量。
Batch: 限定了一个Result中所包含的列的数量,如果一行数据被请求的列的数量超出Batch限制,那么这行数据会被拆成多个Results。
那么, Client往RegionServer第一次请求所返回的结果集如下所示:
Result1 -> Row1: {Col01,Col02,Col03}
Result2 -> Row1: {Col04,Col05,Col06}
Result3 -> Row1: {Col07,Col08,Col09}
Result4 -> Row1: {Col10}
Result5 -> Row2: {Col01,Col02,Col03}
Result6 -> Row2: {Col04,Col05,Col06}
Result7 -> Row2: {Col07,Col08,Col09}
Result8 -> Row2: {Col10,Col11}
Result9 -> Row3: {Col01,Col02,Col03}
Result10 -> Row3: {Col04,Col05,Col06}
c) Limit: 限制一次Scan操作所获取的行的数量
同SQL语法中的limit子句,限制一次Scan操作所获取的行的总量:
scan.setLimit(10000);
注意:
Limit参数是在2.0版本中新引入的。但在2.0.0版本中,当Batch与Limit同时设置时,似乎还存在一个BUG,初步分析问题原因应该与BatchScanResultCache中的numberOfCompletedRows计数器逻辑处理有关。因此,暂时不建议同时设置这两个参数。
d) CacheBlock: RegionServer侧是否要缓存本次Scan所涉及的HFileBlocks
scan.setCacheBlocks(true);
e) Raw Scan: 是否可以读取到删除标识以及被删除但尚未被清理的数据
scan.setRaw(true);
f) MaxResultSize: 从内存占用量的维度限制一次Scan的返回结果集
下面的示例代码将返回结果集的最大值设置为5MB:
scan.setMaxResultSize(5 * 1024 * 1024);
g) Reversed Scan: 反向扫描
普通的Scan操作是按照字典顺序从小到大的顺序读取的,而Reversed Scan则恰好相反:
scan.setReversed(true);
h) 带Filter的Scan
Filter可以在Scan的结果集基础之上,对返回的记录设置更多条件值,这些条件可以与RowKey有关,可以与列名有关,也可以与列值有关,还可以将多个Filter条件组合在一起,等等。
最常用的Filter是SingleColumnValueFilter,基于它,可以实现如下类似的查询:
“返回满足条件{列I:D的值大于等于10}的所有行”
示例代码如下:
Filter filter = new SingleColumnValueFilter(FAMILY,
QUALIFIER, CompareOperator.GREATER_OR_EQUAL,
Bytes.toBytes("10"));
scan.setFilter(filter);
Filter丰富了HBase的查询能力,但使用Filter之前,需要注意一点:Filter可能会导致查询响应时延变的不可控制。因为我们无法预测,为了找到一条符合条件的记录,背后需要扫描多少数据量,如果在有效限制了Scan范围区间(通过设置StartRow与StopRow限制)的前提下,该问题能够得到有效的控制。这些信息都要求使用Filter之前应该详细调研自己的业务数据模型。
- 点赞
- 收藏
- 关注作者
评论(0)