HBase客户端代码书写规范
本文档提到的一些规范条例,主要来源于对定位问题过程中所积累的经验、客户端代码实践、对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<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【规则】 资源释放
关于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<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")); // 假设起始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.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<String, Object> data = DataStorage.takeList(); String tableName = tables[(Integer)data.get("table")]; List<Put> list = (List)data.get("list"); // 以Row为Key,保存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方法,因为后者无法保证编码,尤其是在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(); } |
- 点赞
- 收藏
- 关注作者
评论(0)