HBase扫描操作Scan

HBase扫描操作Scan

1 介绍

扫描操作的使用和get()方法类似。同样,和其他函数类似,这里也提供了Scan类。但是由于扫描工作方式类似于迭代器,所以用户无需调用san()方法创建实例,只需要调用HTable的getScanner()方法,此方法才是返回真正的扫描器(scanner)实例的同时,用户也可以使用它迭代获取数据,Table中的可用的方法如下:

ResultScanner getScanner(Scan scan)
ResultScanner getScanner(byte[] family)
ResultScanner getScanner(byte[] family, byte[] qualifier)

后两个为了方便用户,隐式地帮助用户创建一个Scan实例,逻辑中最后调用getScanner(Scan scan)方法。Scan类拥有以下构造器:

public Scan()
public Scan(byte [] startRow)
public Scan(byte [] startRow, byte [] stopRow)
public Scan(byte [] startRow, Filter filter)
public Scan(Get get)
public Scan(Scan scan)

用户可以选择性的提供startRow参数,来定义扫描读取HBase表的起始行键,即行键不是必须指定的。同时可选stopRow参数来限定读取到何处停止。

其实行包括在内,而终止行不包含在内。一般区间表示为[startRow,stopRow)

扫描操作有一个特点:用户提供的参数不必精确匹配两行。扫描会匹配相等或者大于给定的起始行的行键。如果没有显示地指定起始行,它会从表的起始位置开始获取数据。

当遇到了与设置的终止行相同或者大于终止行的行键时,扫描也会终止。如果没有指定终止键,会扫描到表尾。

另一个可选的参数叫做过滤器(filter),可直接指向Filter实例。尽管Scan实例通常由空白构造器构造,但其所有可选参数都有对应的getter方法和setter方法。

创建Scan实例后,用户可能还要给它增加更多限制条件。这种情况下,用户仍然可以使用空白 参数的扫描,它可以读取整个表格,包括所有列族以及它们的所有列。可以用多种方法限制要读取的数据:

public Scan addFamily(byte [] family)                     // 方法限制返回数据的列族
public Scan addColumn(byte [] family, byte [] qualifier)  // 方法限制返回的列
Scan setTimeRange(long minStamp,long maxStamp)            // 设置时间范围
Scan setTimeStamp(long timestamp)                         // 设置时间戳
Scan setMaxVersions()                                     // 设置最大版本数
Scan setMaxVersions(int maxVersions)                      // 设置最大版本数

2 示例代码

@Test
public void testScan() throws IOException {
    Connection conn = ConnectionFactory.createConnection();
    Table table = conn.getTable(TableName.valueOf("ns1:t1"));
    Scan scan = new Scan(Bytes.toBytes("row"),Bytes.toBytes("row010"));
    scan.addFamily(Bytes.toBytes("f1"));
    ResultScanner scanner = table.getScanner(scan)
    Iterator<Result> results = scanner.iterator();
    while (results.hasNext()){
        Result r = results.next();
        String rowId = Bytes.toString(r.getRow())
        Cell cId = r.getColumnLatestCell(Bytes.toBytes("f1"),Bytes.toBytes("id"));
        Cell cName = r.getColumnLatestCell(Bytes.toBytes("f1"),Bytes.toBytes("name"));
        Cell cAge = r.getColumnLatestCell(Bytes.toBytes("f1"),Bytes.toBytes("age"))
        int id = Bytes.toInt(CellUtil.cloneValue(cId));
        String name = Bytes.toString(CellUtil.cloneValue(cName));
        int age = Bytes.toInt(CellUtil.cloneValue(cAge))
        System.out.println("-----------------------------");
        System.out.println(rowId + "," + id + "," + age + "," + name);

    }
    scanner.close();
    table.close();
    conn.close();
}

上述代码显示结果如下:

-----------------------------
row000,0,0,tom0
-----------------------------
row001,1,1,tom1
-----------------------------
row002,2,2,tom2
-----------------------------
row003,3,3,tom3
-----------------------------
row004,4,4,tom4
-----------------------------
row005,5,5,tom5
-----------------------------
row006,6,6,tom6
-----------------------------
row007,7,7,tom7
-----------------------------
row008,8,8,tom8
-----------------------------
row009,9,9,tom9

3 ResultScanner类

扫描操作不会通过一个RPC请求返回所有匹配的行,而是以行为单位进行返回。很明显,行的数目很大,可能有上千条甚至更多,同时在一次请求中发送大量数据,会占用大量的系统资源并消耗很长时间。

ResultScanner类把扫描操作转换为类似的get操作,它将每一行数据封装成一个Result实例,并将所有的Result实例放入一个迭代器中。ResultScanner的一些方法如下:

Result next()
Result[] next(int nbRows)
void close()

3.1 扫描器租约

要确保尽早释放扫描器对象,一个打开的扫描器会占用不少的服务端资源,累计多了会占用大量的堆空间。当使用完ResultScanner之后调用它的close()方法,同时当把close()方法放到try/finally块中,以保证其在迭代获取数据过程中出现异常和错误时,仍然能执行close()。

4 设置扫描器缓存

每一个next()调用都会为每一行数据生成一个单独的RPC请求,即使使用next(int nbRows)方法也是如此,因为该方法仅仅是在客户端循环地调用next()方法。很显然,当单元格数据较少时,这样做的性能不会很好。因此,如果一次RPC请求可以获取多行数据,这样更有意义。这样的方法可以由扫描器的缓存实现,默认情况下,这个缓存是关闭的。

Scan类中提供了设置缓存的方法如下:

public Scan setCacheBlocks(boolean cacheBlocks) // 设置是否应用缓存块来进行扫描
public boolean getCacheBlocks()                 // 查看是否支持块缓存
public Scan setCaching(int caching)             // 设置扫描器的缓存行数
public int getCaching()                         // 获取扫描器中的缓存行数

用户需要少量的RPC请求次数和客户端以及服务器的内存消耗找到平衡点。很多时候,设置扫描器缓存可以提高性能,不过设置的太高就会产生不良的影响:每次调用next()将会占用更长的时间,因为要获取更多的文件并传输到客户端,如果返回给客户端的数据超出了其堆的大小,程序就会终止并抛出OutOfMemoryException异常。

Tip

当传输和处理数据的时间超过配置的扫描器租约时间时,用户将会收到一个ScannerTimeoutException形式抛出的租约过期错误。

下面是代码示例:

/**
 * 添加扫描
 */
@Test
public void testScanCacheBatch() throws Exception {
    //
    Configuration conf = HBaseConfiguration.create();
    Connection conn = ConnectionFactory.createConnection(conf);
    HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t2"));
    Scan scan = new Scan();
    System.out.println(scan.getBatch());
    //三行
    scan.setCaching(3) ;
    //2列
    scan.setBatch(2) ;
        ResultScanner scanner = table.getScanner(scan);
        Iterator<Result> it = scanner.iterator();
        while (it.hasNext()) {
            Result r = it.next();
            outResult(r);
        }
        scanner.close();
}
private void outResult(Result r){
    System.out.println("=========================");
    List<Cell> cells = r.listCells();
    for(Cell cell : cells){
        String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
        String f = Bytes.toString(CellUtil.cloneFamily(cell));
        String col = Bytes.toString(CellUtil.cloneQualifier(cell));
        long ts = cell.getTimestamp();
        String value = Bytes.toString(CellUtil.cloneValue(cell));
        System.out.println(rowkey+"/"+f+":"+col+"/"+ts + "=" + value);
    }
}

4.1 chche

在默认情况下,如果你需要从hbase中查询数据,在获取结果ResultScanner时,hbase会在你每次调用ResultScanner.next()操作时对返回的每个Row执行一次RPC操作。即使你使用ResultScanner.next(int nbRows)时也只是在客户端循环调用RsultScanner.next()操作,你可以理解为hbase将执行查询请求以迭代器的模式设计,在执行next()操作时才会真正的执行查询操作,而对每个Row都会执行一次RPC操作。

因此显而易见的就会想如果我对多个Row返回查询结果才执行一次RPC调用,那么就会减少实际的通讯开销。这个就是hbase配置属性“hbase.client.scanner.caching”的由来,设置cache可以在hbase配置文件中显示静态的配置,也可以在程序动态的设置。

cache值得设置并不是越大越好,需要做一个平衡。cache的值越大,则查询的性能就越高,但是与此同时,每一次调用next()操作都需要花费更长的时间,因为获取的数据更多并且数据量大了传输到客户端需要的时间就越长,一旦你超过了maximum heap the client process 拥有的值,就会报outofmemoryException异常。当传输rows数据到客户端的时候,如果花费时间过长,则会抛出ScannerTimeOutException异常。

4.2 batch

在cache的情况下,我们一般讨论的是相对比较小的row,那么如果一个Row特别大的时候应该怎么处理呢?要知道cache的值增加,那么在client process 占用的内存就会随着row的增大而增大。在hbase中同样为解决这种情况提供了类似的操作:Batch。可以这么理解,cache是面向行的优化处理,batch是面向列的优化处理。它用来控制每次调用next()操作时会返回多少列,比如你设置setBatch(5),那么每一个Result实例就会返回5列,如果你的列数为17的话,那么就会获得四个Result实例,分别含有5,5,5,2个列。

下面会以表格的形式来帮助理解,假设我们拥有10Row,每个row拥有2个family,每个family拥有10个列。(也就是说每个Row含有20列)

缓存 批量处理 Result个数 RPC次数 说明
1 1 200 201 每个列都作为一个Result实例返回。最后还多一个RPC确认扫描完成
200 1 200 2 每个Result实例都只包含一列的值,不过它们都被一次RPC请求取回
2 10 20 11 批量参数是一行所包含的列数的一半,所以200列除以10,需要20个result实例。同时需要10次RPC请求取回。
5 100 10 3 对一行来讲,这个批量参数实在是太大了,所以一行的20列都被放入到了一个Result实例中。同时缓存为5,所以10个Result实例被两次RPC请求取回。
5 20 10 3 同上,不过这次的批量值与一行列数正好相同,所以输出与上面一种情况相同
10 10 20 3 这次把表分成了较小的result实例,但使用了较大的缓存值,所以也是只用了两次RPC请求就返回了数据

要计算一次扫描操作的RPC请求的次数,用户需要先计算出行数和每行列数的乘积。然后用这个值除以批量大小和每行列数中较小的那个值。最后再用除得的结果除以扫描器缓存值。 用数学公式表示如下:

RPC请求的次数=(行数x每行的列数)/Min(每行的列数,批量大小)/扫描器缓存

原文地址:http://blog.csdn.net/u010521842/article/details/77719433

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容