HBase客户端代码书写规范

网友投稿 571 2022-05-29

本文档提到的一些规范条例,主要来源于对定位问题过程中所积累的经验、客户端代码实践、对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)

{

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

// 初始化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中以TableName为Key值,缓存所有已经实例化的HTable

private Map demoTables = new HashMap();

//所有的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 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 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 errorList = new ArrayList();

/**

* <采用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【规则】 资源释放

关于ResultScanner和HTable实例,在用完之后,需要调用它们的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 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")); // 假设起始Key为rowA

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

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

// process Result instance

}

15【建议】 不要关闭WAL

WAL是Write-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: Zookeeper的IP。多个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客户端代码书写规范

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)的方法,获取一个表名为tableName的HTableInterface实例。

(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 data = DataStorage.takeList();

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

List list = (List)data.get("list");

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

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

Map 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(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方法,因为后者无法保证编码,尤其是在Key或Value中出现中文字符的时候,就会出现问题。

代码示例:

//列族的名称为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个参数,是RegionServer的HostName+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时的两个关键参数—Batch和Caching

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();

}

EI企业智能

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:OpenHarmony设备的开发环境搭建与工程管理(Ubuntu系统环境)
下一篇:#华为云·寻找黑马程序员#微服务-你真的懂 Yaml 吗?
相关文章