HBase客户端代码书写规范

举报
Lettle whale 发表于 2020/07/15 11:02:34 2020/07/15
【摘要】 本文档提到的一些规范条例,主要来源于对定位问题过程中所积累的经验、客户端代码实践、对HBase Client源码的分析以及已总结的ReleaseNotes中的一些注意点等。本文中使用的HBase版本仍为1.0


本文档提到的一些规范条例,主要来源于对定位问题过程中所积累的经验、客户端代码实践、对HBase Client源码的分析以及已总结的ReleaseNotes中的一些注意点等。这些条款,主要分为规则、建议、示例三种类型。规则类,是在写HBase客户端代码时必须遵循的一些条款。建议类,需要依据实际的应用需求来决定是否遵循。示例类,给出了一些功能代码的实现示例,供参考,本文中使用的HBase版本仍为1.0+。

1【规则】 Configuration实例的创建

该类应该通过调用HBaseConfiguration的Create()方法来实例化。否则,将无法正确加载HBase中的相关配置项。

正确示例:

//该部分,应该是在类成员变量的声明区域声明

private Configuration hbaseConfig = null;

//最好在类的构造函数中,或者初始化方法中实例化该类

hbaseConfig = HBaseConfiguration.create();

错误示例:

hbaseConfig = new Configuration();

2【规则】 共享Configuration实例

HBase客户端代码通过创建一个与Zookeeper之间的HConnection,来获取与一个HBase集群进行交互的权限。一个Zookeeper的HConnection连接,对应着一个Configuration实例,已经创建的HConnection实例,会被缓存起来。也就是说,如果客户端需要与HBase集群进行交互的时候,会传递一个Configuration实例过去,HBase Client部分通过已缓存的HConnection实例,来判断属于这个Configuration实例的HConnection实例是否存在,如果不存在,就会创建一个新的,如果存在,就会直接返回相应的实例。

因此,如果频频的创建Configuration实例,会导致创建很多不必要的HConnection实例,很容易达到Zookeeper的连接数上限。

建议在整个客户端代码范围内,都共用同一个Configuration对象实例。

3【规则】 HTable实例的创建

HTable类有多种构造函数,如:

1.public HTable(final String tableName)

2.public HTable(final byte [] tableName)

3.public HTable(Configuration conf, final byte [] tableName)

4.public HTable(Configuration conf, final String tableName)

5.public HTable(final byte[] tableName, final HConnection connection, final ExecutorService pool)

建议采用第5种构造函数。之所以不建议使用前面的4种,是因为:前两种方法实例化一个HTable时,没有指定Configuration实例,那么,在实例化的时候,就会自动创建一个Configuration实例。如果需要实例化过多的HTable实例,那么,就可能会出现很多不必要的HConnection(关于这一点,前面部分已经有讲述)。因此,而对于第3、4种构造方法,每个实例都可能会创建一个新的线程池,也可能会创建新的连接,导致性能偏低。

正确示例:

private HTable table = null;

public initTable(Configuration config, byte[] tableName)

{

// sharedConnpool都已经是事先实例化好的。建议进程级别内共享同一个。

// 初始化HConnection的方法:

// HConnection sharedConn =

//    HConnectionManager.createConnection(this.config);

    table = new HTable(config, tableName, sharedConn, pool);

}

错误示例:

private HTable table = null;

public initTable(String tableName)

{

    table = new HTable(tableName);

}

public initTable(byte[] tableName)

{

    table = new HTable(tableName);

}

4 【规则】 不允许多个线程在同一时间共用同一个HTable实例

HTable是一个非线程安全类,因此,同一个HTable实例,不应该被多个线程同时使用,否则可能会带来并发问题。

5【规则】 HTable实例缓存

如果一个HTable实例可能会被长时间且被同一个线程固定且频繁的用到,例如,通过一个线程不断的往一个表内写入数据,那么这个HTable在实例化后,就需要缓存下来,而不是每一次插入操作,都要实例化一个HTable对象(尽管提倡实例缓存,但也不是在一个线程中一直沿用一个实例,个别场景下依然需要重构,可参见下一条规则)。

正确示例:

    //注意该实例中提供的以Map形式缓存HTable实例的方法,未必通用。这与多线程多HTable实例的设计方案有关。如果确定一个HTable实例仅仅可能会被用于一个线程,而且该线程也仅有一个HTable实例的话,就无须使用Map。这里提供的思路仅供参考

    //Map中以TableNameKey值,缓存所有已经实例化的HTable

    private Map<String, HTable> demoTables = new HashMap<String, HTable>();

    //所有的HTable实例,都将共享这个Configuration实例

    private Configuration demoConf = null;

    /**

     * <初始化一个HTable>

     * <功能详细描述>

     * @param tableName

     * @return

     * @throws IOException

     * @see [类、类#方法、类#成员]

     */

    private HTable initNewTable(String tableName) throws IOException

    {

        return new HTable(demoConf, tableName);

    }

   

    /**

     * <获取HTable实例>

     * <功能详细描述>

     * @see [类、类#方法、类#成员]

     */

    private HTable getTable(String tableName)

    {

        if (demoTables.containsKey(tableName))

        {

            return demoTables.get(tableName);

        } else {

            HTable table = null;

            try

            {

                table = initNewTable(tableName);

                demoTables.put(tableName, table);

            }

            catch (IOException e)

            {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

            return table;

        }

}

/**

     * <写数据>

     * <这里未涉及到多线程多HTable实例在设计模式上的优化.这里只所以采用同步方法,

     *  是考虑到同一个HTable是非线程安全的.通常,我们建议一个HTable实例,在同一

     *  时间只能被用在一个写数据的线程中>

     * @param dataList

     * @param tableName

     * @see [类、类#方法、类#成员]

     */

    public void putData(List<Put> dataList, String tableName)

    {

        HTable table = getTable(tableName);

        //关于这里的同步:如果在采用的设计方案中,不存在多线程共用同一个HTable实例

        //的可能的话,就无须同步了。这里需要注意的一点,就是HTable实例是非线程安全的

        synchronized (table)

        {

            try

            {

                table.put(dataList);

               table.notifyAll();

            }

            catch (IOException e)

            {

                // 在捕获到IOE时,需要将缓存的实例重构。

                try {

                 // 关闭之前的Connection.

                 table.close();

                  // 重新创建这个实例.

                  table = new HTable(this.config, "jeason");

                } catch (IOException e1) {

                   // TODO

               }

            }

        }

    }

错误示例:

    public void putDataIncorrect(List<Put> dataList, String tableName)

    {

        HTable table = null;

        try

        {

            //每次写数据,都创建一个HTable实例

            table = new HTable(demoConf, tableName);

            table.put(dataList);

        }

        catch (IOException e1)

        {

            // TODO Auto-generated catch block

            e1.printStackTrace();

        }

        finally

       {

           table.close();

       }

    }


6【规则】 HTable实例写数据的异常处理

 尽管在前一条规则中提到了提倡HTable实例的重构,但是,并非提倡一个线程自始至终要沿用同一个HTable实例,当捕获到IOException时,依然需要重构HTable实例。示例代码可参考上一个规则的示例。

 另外,勿轻易调用如下两个方法:

Ø  Configuration#clear

这个方法,会清理掉所有的已经加载的属性,那么,对于已经在使用这个Configuration的类或线程而言,可能会带来潜在的问题(例如,假如HTable还在使用这个Configuration,那么,调用这个方法后,HTable中的这个Configuration的所有的参数,都被清理掉了),也就是说:只要还有对象或者线程在使用这个Configuration,我们就不应该调用这个clear方法,除非,所有的类或线程,都已经确定不用这个Configuration了。那么,这个操作,可以在所有的线程要退出的时候来做,而不是每一次。

因此,这个方法,应该要放在进程要退出的地方去做。而不是每一次HBaseAdmin要重构的时候做。

Ø  HConnectionManager#deleteAllConnections:

这个可能会导致现有的正在使用的连接被从连接集合中清理掉,同时,因为在HTable中保存了原有连接的引用,可能会导致这个连接无法关闭,进而可能会造成泄漏。因此,这个方法不建议使用。

7【规则】 写入失败的数据要做相应的处理

在写数据的过程中,如果进程异常或一些其它的短暂的异常,可能会导致一些写入操作失败。因此,对于操作的数据,需要将其记录下来。在集群恢复正常后,重新将其写入到HBase数据表中。

另外,有一点需要注意:HBase Client返回写入失败的数据,是不会自动重试的,仅仅会告诉接口调用者哪些数据写入失败了。对于写入失败的数据,一定要做一些安全的处理,例如可以考虑将这些失败的数据,暂时写在文件中,或者,直接缓存在内存中。

正确示例:

private List<Row> errorList = new ArrayList<Row>();

/**

 * <采用PutList的模式插入数据>

 * <如果不是多线程调用该方法,可不采用同步>

 * @param put 一条数据记录

 * @throws IOException

 * @see [类、类#方法、类#成员]

 */

public synchronized void putData(Put put)

{

        // 暂时将数据缓存在该List

        dataList.add(put);    

        // dataList的大小达到PUT_LIST_SIZE之后,就执行一次Put操作

        if (dataList.size() >= PUT_LIST_SIZE)

        {

               try

               {

                       demoTable.put(dataList);

               }

               catch (IOException e)

               {

                       // 如果是RetriesExhaustedWithDetailsException类型的异常,

                       // 说明这些数据中有部分是写入失败的这通常都是因为

                       // HBase集群的进程异常引起,当然有时也会因为有大量

                       // Region正在被转移,导致尝试一定的次数后失败

                       if (e instanceof RetriesExhaustedWithDetailsException)

                       {

                               RetriesExhaustedWithDetailsException ree =

                                 (RetriesExhaustedWithDetailsException)e;

                               int failures = ree.getNumExceptions();

                               for (int i = 0; i < failures; i++)

                               {

                                      errorList.add(ree.getRow(i));

                               }

                       }

               }

               dataList.clear();

        }

}


8【规则】 资源释放

关于ResultScannerHTable实例,在用完之后,需要调用它们的Close方法,将资源释放掉。Close方法,要放在finally块中,来确保一定会被调用到。

正确示例:

ResultScanner scanner = null;

try

{

     scanner = demoTable.getScanner(s);

     //Do Something here.

}

finally

{

     scanner.close();

}

错误示例:

1. 在代码中未调用scanner.close()方法释放相关资源。

2. scanner.close()方法未放置在finally块中:

ResultScanner scanner = null;

scanner = demoTable.getScanner(s);

//Do Something here.

scanner.close(); 


9【规则】 Scan时的容错处理

Scan时不排除会遇到异常,例如,租约过期。在遇到异常时,建议Scan应该有重试的操作。

事实上,重试在各类异常的容错处理中,都是一种优秀的实践,这一点,可以应用在各类与HBase操作相关的接口方法的容错处理过程中。

10【规则】 不用HBaseAdmin时,要及时关闭,HBaseAdmin实例不应常驻内存

HBaseAdmin的示例应尽量遵循 “用时创建,用完关闭”的原则。不应该长时间缓存同一个HBaseAdmin实例。

11【规则】 暂时不建议使用HTablePool获取HTable实例,因为当前的HTablePool实现中可能会带来泄露。创建HTable实例的方法,参考上面的规则4。

12【建议】 不要调用HBaseAdmin的closeRegion方法关闭一个Region

HBaseAdmin中,提供了关闭一个Region的接口:

//hostAndPort可以指定,也可以不指定。

public void closeRegion(final String regionname, final String hostAndPort)

通过该方法关闭一个Region, HBase Client端会直接发RPC请求到Region所在的RegionServer上,整个流程对Master而言,是不感知的。也就是说,尽管RegionServer关闭了这个Region,但是,在Master侧,还以为该Region是在该RegionServer上面打开的。假如,在执行Balance的时候,Master计算出恰好要转移这个Region,那么,这个Region将无法被关闭,本次转移操作将无法完成(关于这个问题,在当前的HBase版本中的处理的确还欠缺妥当)。

因此,暂时不建议使用该方法关闭一个Region。

13【建议】 采用PutList模式写数据

     HTable类中提供了两种写数据的接口:

1.     public void put(final Put put) throws IOException

2.     public void put(final List<Put> puts) throws IOException

1种方法较之第2种方法,在性能上有明显的弱势。因此,写数据时应该采用第2种方法。

14【建议】 Scan时指定StartKey和EndKey

一个有确切范围的Scan,在性能上会带来较大的好处。

代码示例:

Scan scan = new Scan();

scan.addColumn(Bytes.toBytes("familyname"),Bytes.toBytes("columnname"));

scan.setStartRow( Bytes.toBytes("rowA")); // 假设起始KeyrowA

scan.setStopRow( Bytes.toBytes("rowB"));  // 假设EndKeyrowB

for(Result result : demoTable.getScanner(scan)) {

  // process Result instance

}

15【建议】 不要关闭WAL

  WALWrite-Ahead-Log的简称,是指数据在入库之前,首先会写入到日志文件中,借此来确保数据的安全性。

WAL功能默认是开启的,但是,在Put类中提供了关闭WAL功能的接口:

public void setWriteToWAL(boolean write)

因此,不建议调用该方法将WAL关闭(即将writeToWAL设置为False),因为可能会造成最近1S(该值由RegionServer端的配置参数hbase.regionserver.optionallogflushinterval决定,默认为1S)内的数据丢失。但如果在实际应用中,对写入的速率要求很高,并且可以容忍丢失最近1S内的数据的话,可以将该功能关闭。

16【建议】 创建一张表或Scan时设定blockcache为 true

HBase客户端建表和scan时,设置blockcache=true。需要根据具体的应用需求来设定它的值,这取决于有些数据是否会被反复的查询到,如果存在较多的重复记录,将这个值设置为true可以提升效率,否则,建议关闭。

建议按默认配置,默认就是true,只要不强制设置成false就可以,例如:

HColumnDescriptor fieldADesc = new HColumnDescriptor("value".getBytes());

fieldADesc.setBlockCacheEnabled(false);

17【示例】 Configuration可以设置的参数

为了能够建立一个HBase Client端到HBase Server端的连接,需要设置如下几个参数:

hbase.zookeeper.quorum ZookeeperIP。多个Zookeeper节点的话,中间用”,”隔开。

hbase.zookeeper.property.clientPort Zookeeper的端口。

说明:

通过HBaseConfiguration.create()创建的Configuration实例,会自动加载如下配置文件中的配置项:

1.     core-default.xml

2.     core-site.xml

3.     hbase-default.xml

4.     hbase-site.xml

 因此,这四个配置文件,应该要放置在“Source Folder”下面(将一个文件夹设置为Source Folder的方法:如果在工程下面建立了一个resource的文件夹,那么,可以在该文件夹上右键鼠标,依次选择”Build Path”->”Use as Source Folder”即可,可参考下图)

下面是客户端可配置的一些参数集合(在通常情况下,这些值都不建议修改):

参数名

参数解释

hbase.client.pause 

每次异常或者其它情况下重试等待相关的时间参数(实际等待时间将根据该值与已重试次数计算得出)

hbase.client.retries.number

异常或者其它情况下的重试次数

hbase.client.retries.longer.multiplier

与重试次数有关

hbase.client.rpc.maxattempts

RPC请求不可达时的重试次数

hbase.regionserver.lease.period

Scanner超时时间有关(单位ms

hbase.client.write.buffer

在启用AutoFlush的情况下,该值不起作用。如果未启用AotoFlush的话,HBase Client端会首先缓存写入的数据,达到设定的大小后才向HBase集群下发一次写入操作

hbase.client.scanner.caching

Scan时一次next请求获取的行数

hbase.client.keyvalue.maxsize

一条keyvalue数据的最大值

hbase.htable.threads.max

HTable实例中与数据操作有关的最大线程数

hbase.client.prefetch.limit

客户端在写数据或者读取数据时,需要首先获取对应的Region所在的地址。客户端可以预缓存一些Region地址,这个参数就是与缓存的数目有关的配置

 

正确设置参数的方法:

hbaseConfig = HBaseConfiguration.create();

//如下参数,如果在配置文件中已经存在,则无须再配置

hbaseConfig.set("hbase.zookeeper.quorum", "157.5.100.1,157.5.100.2,157.5.100.3");

hbaseConfig.set("hbase.zookeeper.property.clientPort", "2181");


18【示例】 HTablePool在多线程写入操作中的应用

多个写数据线程时,可以采用HTablePool。现在先简单介绍下该类的使用方法和注意点:

(1)              多个写数据的线程之间,应共享同一个HTablePool实例。

(2)              实例化HTablePool的时候,应要指定最大的HTableInterface实例个数maxSize,即需要通过如下构造函数实例化该类:

   public HTablePool(final Configuration config, final int maxSize)

关于maxSize的值,可以根据写数据的线程数Threads以及涉及到的用户表个数Tables来定,理论上,不应该超过(Threads*Tables)

(3)              客户端线程通过HTablePool#getTable(tableName)的方法,获取一个表名为tableNameHTableInterface实例。

(4)              同一个HTableInterface实例,在同一个时刻只能给一个线程使用。

(5)              如果HTableInterface使用用完了,需要调用HTablePool#putTable(HTableInterface table)方法将它放回去。

 

示例代码:

/**

 * 写数据失败后需要一定的重试次数,每一次重试的等待时间,需要根据已经重试的次数而定.

 */

private static final int[] RETRIES_WAITTIME = {1, 1, 1, 2, 2, 4, 4, 8, 16, 32};

/**

 * 限定的重试次数

 */

private static final int RETRIES = 10;

/**

 * 失败后等待的基本时间单位

 */

private static final int PAUSE_UNIT = 1000

private static Configuration hadoopConfig;

private static HTablePool tablePool;

private static String[] tables;

/**

 * <初始化HTablePool>

 * <功能详细描述>

 * @param config

 * @see [类、类#方法、类#成员]

 */

public static void initTablePool()

{

DemoConfig config = DemoConfig.getInstance();

if (hadoopConfig == null)

{

  hadoopConfig = HBaseConfiguration.create();

  hadoopConfig.set("hbase.zookeeper.quorum", config.getZookeepers());

  hadoopConfig.set("hbase.zookeeper.property.clientPort", config.getZookeeperPort());

}

if (tablePool == null)

{

  tablePool = new HTablePool(hadoopConfig, config.getTablePoolMaxSize());

  tables = config.getTables().split(",");

}

}

public void run()

{

// 初始化HTablePool.因为这是多线程间共享的一个实例仅被实例化一次.

initTablePool();

for (;;)

{

  Map<String, Object> data = DataStorage.takeList();

  String tableName = tables[(Integer)data.get("table")];

  List<Put> list = (List)data.get("list");

  // RowKey,保存List中所有的Put.该集合仅仅使用于写入失败时查找失败的数据记录.

  // 因为从Server端仅仅返回了失败的数据记录的Row.

  Map<byte[], Put> rowPutMap = null;

 

  // 如果失败了(哪怕是部分数据失败),需要重试.每一次重试,都仅仅提交失败的数据条目

INNER_LOOP :

  for (int retry = 0; retry < RETRIES; retry++)

  {

    // HTablePool中获取一个HTableInterface实例.用完后需要放回去.

    HTableInterface table = tablePool.getTable(tableName);

    try

    {

      table.put(list);

      // 如果执行到这里,说明成功了 .

      break INNER_LOOP;

    }

    catch (IOException e)

    {

      // 如果是RetriesExhaustedWithDetailsException类型的异常,

      // 说明这些数据中有部分是写入失败的这通常都是因为HBase集群

      // 的进程异常引起,当然有时也会因为有大量的Region正在被转移,

      // 导致尝试一定的次数后失败.

      // 如果非RetriesExhaustedWithDetailsException异常,则需要将

      // list中的所有数据都要重新插入.

      if (e instanceof RetriesExhaustedWithDetailsException)

      {

        RetriesExhaustedWithDetailsException ree =

          (RetriesExhaustedWithDetailsException)e;

        int failures = ree.getNumExceptions();

        System.out.println("本次插入失败了[" + failures + "]条数据.");

        // 第一次失败且重试时,实例化该Map.

        if (rowPutMap == null)

        {

          rowPutMap = new HashMap<byte[], Put>(failures);

          for (int m = 0; m < list.size(); m++)

          {

            Put put = list.get(m);

            rowPutMap.put(put.getRow(), put);

          }

        }

       

        //Clear掉原数据,然后将失败的数据添加进来

        list.clear();

        for (int m = 0; m < failures; m++)

        {

          list.add(rowPutMap.get(ree.getRow(m)));

        }

      }

    }

    finally

    {

      // 用完之后,再将该实例放回去

      tablePool.putTable(table);

    }

    // 如果异常了,就暂时等待一段时间.该等待应该在将HTableInterface实例放回去之后

    try

    {

      sleep(getWaitTime(retry));

    }

    catch (InterruptedException e1)

    {

      System.out.println("Interruped");

    }

  }

}



19【示例】 Put实例的创建

HBase是一个面向列的数据库,一行数据,可能对应多个列族,而一个列族又可以对应多个列。通常,写入数据的时候,我们需要指定要写入的列(含列族名称列名称):

如果要往HBase表中写入一行数据,需要首先构建一个Put实例。Put中包含了数据的Key值和相应的Value值,Value值可以有多个(即可以有多列值)。

有一点需要注意:在往Put实例中add一条KeyValue数据时,传入的family,qualifier,value都是字节数组。在将一个字符串转换为字节数组时,需要使用Bytes.toBytes方法,不要使用String.toBytes方法,因为后者无法保证编码,尤其是在KeyValue中出现中文字符的时候,就会出现问题。

代码示例:

   //列族的名称为privateInfo

    private final static byte[] FAMILY_PRIVATE = Bytes.toBytes("privateInfo");

    //列族privateInfo中总共有两个列"name"&"address"

    private final static byte[] COLUMN_NAME = Bytes.toBytes("name");

    private final static byte[] COLUMN_ADDR = Bytes.toBytes("address");

       

    /**

     * <创建一个Put实例>

     * <在该方法中,将会创建一个具有1个列族,2列数据的Put>

     * @param rowKey  Key

     * @param name    人名

     * @param address 地址

     * @return

     * @see [类、类#方法、类#成员]

     */

    public Put createPut(String rowKey, String name, String address)

    {

        Put put = new Put(Bytes.toBytes(rowKey));

        put.add(FAMILY_PRIVATE, COLUMN_NAME, Bytes.toBytes(name));

        put.add(FAMILY_PRIVATE, COLUMN_ADDR, Bytes.toBytes(address));

        return put;

    }



20【示例】 HBaseAdmin实例的创建以及常用方法

代码示例:

private Configuration demoConf = null;

private HBaseAdmin hbaseAdmin = null;

/**

 * <构造函数>

 * 需要将已经实例化好的Configuration实例传递进来

 */

public HBaseAdminDemo(Configuration conf)

{

        this.demoConf = conf;

        try

        {

               // 实例化HBaseAdmin

               hbaseAdmin = new HBaseAdmin(this.demoConf);

        }

        catch (MasterNotRunningException e)

        {

               e.printStackTrace();

        }

        catch (ZooKeeperConnectionException e)

        {

               e.printStackTrace();

        }

}

 

/**

 * <一些方法使用示例>

 * <更多的方法,请参考HBase接口文档>

 * @throws IOException

 * @throws ZooKeeperConnectionException

 * @throws MasterNotRunningException

 * @see [类、类#方法、类#成员]

 */

public void demo() throws MasterNotRunningException, ZooKeeperConnectionException, IOException

{

        byte[] regionName = Bytes.toBytes("mrtest,jjj,1315449869513.fc41d70b84e9f6e91f9f01affdb06703.");

        byte[] encodeName = Bytes.toBytes("fc41d70b84e9f6e91f9f01affdb06703");

        // 重新分配一个Reigon.

        hbaseAdmin.unassign(regionName, false);

        // 主动触发Balance.

        hbaseAdmin.balancer();

        // 移动一个Region,2个参数,是RegionServerHostName+StartCode,例如:

        // host187.example.com,60020,1289493121758.如果将该参数设置为null,则会随机移动该Region

        hbaseAdmin.move(encodeName, null);

        // 判断一个表是否存在

        hbaseAdmin.tableExists("tableName");

        // 判断一个表是否被激活

        hbaseAdmin.isTableEnabled("tableName");

}

/**

 * <快速创建一个表的方法>

 * <首先创建一个HTableDescriptor实例,它里面包含了即将要创建的HTable的描述信息,同时,需要创建相应的列族。列族关联的实例是HColumnDescriptor。在本示例中,创建的列族名称为“columnName>

 * @param tableName 表名

 * @return

 * @see [类、类#方法、类#成员]

 */

public boolean createTable(String tableName)

{

        try {

           if (hbaseAdmin.tableExists(tableName)) {

               return false;

            }

           HTableDescriptor tableDesc = new HTableDescriptor(tableName);

            HColumnDescriptor fieldADesc = new HColumnDescriptor("columnName".getBytes());

            fieldADesc.setBlocksize(640 * 1024);

            tableDesc.addFamily(fieldADesc);

            hbaseAdmin.createTable(tableDesc);

          } catch (Exception e) {

            e.printStackTrace();

            return false;

          }

          return true;

}


1  Scan时的两个关键参数—BatchCaching

Batch:使用scan调用next接口每次最大返回的记录数,与一次读取的列数有关。

Caching:一个RPC查询请求最大的返回的next数目,与一次RPC获取的行数有关。

首先举几个例子,来介绍这两个参数在Scan时所起到的作用:

假设表A的一个Region中存在2行(rowkey)数据,每行有1000column,且每列当前只有一个version,即每行就会有1000个key value。


ColuA1

ColuA2

ColuA3

ColuA4

………

ColuN1

ColuN2

ColuN3

ColuN4

Row1





………





Row2





………





  例1: 查询参数: 不设batch,设定caching=2

 那么,一次RPC请求,就会返回2000个KeyValue.

例2: 查询参数: 设定batch=500,设定caching=2

 那么,一次RPC请求,只能返回1000个KeyValue.

例3: 查询参数: 设定batch=300,设定caching=4

 那么,一次RPC请求,也只能返回1000个KeyValue.

关于Batch和Caching的进一步解释:

Ø  一次Caching,是一次请求数据的机会。

Ø  同一行数据是否可以通过一次Caching读完,取决于Batch的设置,如果Batch的值小于一行的总列数,那么,这一行至少需要2次Caching才可以读完(后面的一次Caching的机会,会继续前面读取到的位置继续读取)。

Ø  一次Caching读取,不能跨行。如果某一行已经读完,并且Batch的值还没有达到设定的大小,也不会继续读下一行了。

那么,关于例1与例2的结果,就很好解释了:

例1的解释:

不设定Batch的时候,默认会读完改行所有的列。那么,在caching为2的时候,一次RPC请求就会返回2000个KeyValue。

例2的解释:

设定Batch为500,caching为2的情况下,也就是说,每一次Caching,最多读取500列数据。那么,第一次Caching,读取到500列,剩余的500列,会在第2次Caching中读取到。因此,两次Caching会返回1000个KeyValue。

例3的解释:

设定Batch为300,caching为4的情况下,读取完1000条数据,正好需要4次caching。因此,只能返回1000条数据。

代码示例:

Scan s = new Scan();

//设置查询的起始key和结束key

s.setStartRow(Bytes.toBytes("01001686138100001"));

s.setStopRow(Bytes.toBytes("01001686138100002"));

s.setBatch(1000);

s.setCaching(100);

ResultScanner scanner = null;

try {

  scanner = tb.getScanner(s);

  for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {

        for (KeyValue kv : rr.raw()) {

         //显示查询的结果

         System.out.println("key:" + Bytes.toString(kv.getRow())

                 + "getQualifier:" + Bytes.toString(kv.getQualifier())

                 + "value" + Bytes.toString(kv.getValue()));       

        }

  }

} catch (IOException e) {

  System.out.println("error!" + e.toString());

} finally {

  scanner.close();

}


 


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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