扫描操作不会通过一次RPC请求返回所有匹配的行,而是以行为单位进行返回。很明显,行的数目很大,可能有上千条甚至更多,同时在一次请求中发送大量数据,会占用大量的系统资源并消耗很长时间。
ResultScanner类把扫描操作转换为类似的get操作,它将每一行数据封装成一个Result实例,并将所有的Result实例放入一个迭代器中。ResultScanner的一些方法如下:
Result next() throws IOException
Result[] next(int nbRows) throws IOException
void close()
有两种类型next()调用供用户选择。调用close()方法会释放所有由扫描控制的资源。
扫描器租约
要确保尽早释放扫描器实例,一个打开的扫描器会占用不少的服务端资源,累积多了会占用大量的堆空间。当使用完ResultScanner之后调用它的close()方法,同时,当把close()方法放到try/finally块中,以保证其在迭代获取数据过程中出现异常和错误时,仍然能执行close()。
就像行锁一样,扫描器也使用同样的租约超时机制,保护其不被失效的客户单阻塞太久。用户可以使用修改锁租约处提到的那个配置属性来修改超时时间(单位:毫秒):
<property>
<name>hbase.regionserver.lease.period</name>
<value>120000</value>
</property>
用户需要确保该属性值适当,这个值要同时适用于锁租约和扫描器租约。
next()调用返回了一个单独的Result实例,这个实例代表了下一个可用的行。此外,用户可以使用next(int nbRows) 一次获取多行数据,它返回一个数组,数组中包含的Result实例最多可达nbRows个,每个实例代表唯一的一行。当用户扫描到表尾或到终止行时,由于没有足够的行来填充数据,返回的结果数组可能会小于既定长度。
下边是实现扫描器获取表中数据代码
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.util.Bytes;
public class HBaseResultScanner {
public static void main(String[] args) throws IOException {
Configuration conf = HBaseConfiguration.create();
HTable table = new HTable(conf, "testtable");
Scan scan1 = new Scan();
ResultScanner scanner1 = table.getScanner(scan1);
for (Result res : scanner1) {
System.out.println(res);
}
scanner1.close();
Scan scan2 = new Scan();
scan2.addFamily(Bytes.toBytes("colfam1"));
ResultScanner scanner2 = table.getScanner(scan2);
for (Result res : scanner2) {
System.out.println(res);
}
scanner2.close();
Scan scan3 = new Scan();
scan3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("col-5"))
.addColumn(Bytes.toBytes("colfam2"), Bytes.toBytes("col-33"))
.setStartRow(Bytes.toBytes("row-10"))
.setStopRow(Bytes.toBytes("row-20"));
ResultScanner scanner3 = table.getScanner(scan3);
for (Result res : scanner3) {
System.out.println(res);
}
scanner3.close();
}
}
代码插入了100行数据,每行有两个列族,每个列族下包含100个列。第一个扫描全表内容,第二个扫描操作只扫描一个列族,最后一个操作有严格的限制条件,其中包括对行范围的限制,同时还要求只扫描两个特定的列。
输出如下:
Scanning table # 3...
keyvalues={row-10/colfam1:col-5/1300803775078/Put/vlen=8,row-10/colfam2:col-33/1300803775099/Put/vlen=9}
keyvalues={row-100/colfam1:col-5/1300803775079/Put/vlen=9,
row-100/colfam1:col-33/1300803775095/Put/vlen=10
}
keyvalues={row-11/colfam1:col-5/1300803775152/Put/vlen=8,
row-11/colfam1:col-33/1300803775170/Put/vlen=9
}
keyvalues={row-12/colfam1:col-5/1300803775212/Put/vlen=8,
row-12/colfam1:col-33/1300803775246/Put/vlen=9
}
keyvalues={row-13/colfam1:col-5/1300803775345/Put/vlen=8,
row-13/colfam1:col-33/1300803775376/Put/vlen=9
}
keyvalues={row-14/colfam1:col-5/1300803775479/Put/vlen=8,
row-14/colfam1:col-33/1300803775498/Put/vlen=9
}
keyvalues={row-15/colfam1:col-5/1300803775554/Put/vlen=8,
row-15/colfam1:col-33/1300803775582/Put/vlen=9
}
keyvalues={row-16/colfam1:col-5/1300803775665/Put/vlen=8,
row-16/colfam1:col-33/1300803775687/Put/vlen=9
}
keyvalues={row-17/colfam1:col-5/1300803775734/Put/vlen=8,
row-17/colfam1:col-33/1300803775748/Put/vlen=9
}
keyvalues={row-18/colfam1:col-5/1300803775791/Put/vlen=8,
row-18/colfam1:col-33/1300803775805/Put/vlen=9
}
keyvalues={row-19/colfam1:col-5/1300803775843/Put/vlen=8,
row-19/colfam1:col-33/1300803775859/Put/vlen=9
}
keyvalues={row-2/colfam1:col-5/1300803774463/Put/vlen=7,
row-2/colfam1:col-33/1300803774485/Put/vlen=8
}
再强调一次,匹配的行键都是按词典序列排列的,这使得结果非常有趣。用户可以简单地用0把行键补齐,这样扫描出来的结果顺序更有可读性。