一条数据的HBase之旅,简明HBase入门教程13:两种读取模式

举报
Jaison 发表于 2018/05/23 17:43:28 2018/05/23
【摘要】 介绍HBase的两种读取模式:Get与Scan,以及各自的重要参数。

华为云上的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的StartRowStopRow。示例如下:


    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将会一直读取到表的最后一行记录。

Scan-1-RangeType

如果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中:

Scan-2-Caching

应用每一次读取数据时,都是从本地的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之前应该详细调研自己的业务数据模型。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。