HBase是Hadoop大数据生态技术圈中的一项关键技术,是一种用于分布式存储大数据的列式数据库,关于HBase更加详细的介绍和技术细节,朋友们可以在网络上进行搜寻,笔者本人在接下来的日子里也会写一个HBase方面的技术专题,有兴趣的朋友们可以稍微的期待一下。不过本章节的重点是介绍下HBase表数据的分页处理,其他的就不多说了。
首先说一下表数据分页中不可回避的一个指标:总记录数。在关系数据库中很容易统计出记录总数,但在HBase中,这却是一个大难题,至少在目前,朋友们根本不要奢望能够通过类似“SELECT COUNT(*) FROM TABLE”的方式统计出一个表的总行数。HBase本身提供的表行数统计功能是一个MapReduce任务,极为耗时,所以在对HBase表数据进行分页处理时,我们只能忽略总记录数这个统计指标了。
如果总记录数不确定,那么总分页数也是不确定的,是否存在下一页也是未知的,以及由此引发的其他问题,都是我们在进行HBase表数据分页处理时需要特别注意的。
1、HBase表数据分页模型类
import java.io.Serializable; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import org.apache.hadoop.hbase.client.Result; /** * Description: HBase表数据分页模型类。<br> * 利用此类可管理多个HBaseQualifierModel对象。 * Copyright: Copyright (c) 2014<br> * Company: 河南电力科学研究院智能电网所<br> * @author shangbingbing 2014-01-01编写 * @version 1.0 */ public class HBasePageModel implements Serializable { private static final long serialVersionUID = 330410716100946538L; private int pageSize = 100; private int pageIndex = 0; private int prevPageIndex = 1; private int nextPageIndex = 1; private int pageCount = 0; private int pageFirstRowIndex = 1; private byte[] pageStartRowKey = null; private byte[] pageEndRowKey = null; private boolean hasNextPage = true; private int queryTotalCount = 0; private long startTime = System.currentTimeMillis(); private long endTime = System.currentTimeMillis(); private List<Result> resultList = new ArrayList<Result>(); public HBasePageModel(int pageSize) { this.pageSize = pageSize; } /** * 获取分页记录数量 * @return */ public int getPageSize() { return pageSize; } /** * 设置分页记录数量 * @param pageSize */ public void setPageSize(int pageSize) { this.pageSize = pageSize; } /** * 获取当前页序号 * @return */ public int getPageIndex() { return pageIndex; } /** * 设置当前页序号 * @param pageIndex */ public void setPageIndex(int pageIndex) { this.pageIndex = pageIndex; } /** * 获取分页总数 * @return */ public int getPageCount() { return pageCount; } /** * 设置分页总数 * @param pageCount */ public void setPageCount(int pageCount) { this.pageCount = pageCount; } /** * 获取每页的第一行序号 * @return */ public int getPageFirstRowIndex() { this.pageFirstRowIndex = (this.getPageIndex() - 1) * this.getPageSize() + 1; return pageFirstRowIndex; } /** * 获取每页起始行键 * @return */ public byte[] getPageStartRowKey() { return pageStartRowKey; } /** * 设置每页起始行键 * @param pageStartRowKey */ public void setPageStartRowKey(byte[] pageStartRowKey) { this.pageStartRowKey = pageStartRowKey; } /** * 获取每页结束行键 * @return */ public byte[] getPageEndRowKey() { return pageEndRowKey; } /** * 设置每页结束行键 * @param pageStartRowKey */ public void setPageEndRowKey(byte[] pageEndRowKey) { this.pageEndRowKey = pageEndRowKey; } /** * 获取上一页序号 * @return */ public int getPrevPageIndex() { if(this.getPageIndex() > 1) { this.prevPageIndex = this.getPageIndex() - 1; } else { this.prevPageIndex = 1; } return prevPageIndex; } /** * 获取下一页序号 * @return */ public int getNextPageIndex() { this.nextPageIndex = this.getPageIndex() + 1; return nextPageIndex; } /** * 获取是否有下一页 * @return */ public boolean isHasNextPage() { //这个判断是不严谨的,因为很有可能剩余的数据刚好够一页。 if(this.getResultList().size() == this.getPageSize()) { this.hasNextPage = true; } else { this.hasNextPage = false; } return hasNextPage; } /** * 获取已检索总记录数 */ public int getQueryTotalCount() { return queryTotalCount; } /** * 获取已检索总记录数 * @param queryTotalCount */ public void setQueryTotalCount(int queryTotalCount) { this.queryTotalCount = queryTotalCount; } /** * 初始化起始时间(毫秒) */ public void initStartTime() { this.startTime = System.currentTimeMillis(); } /** * 初始化截止时间(毫秒) */ public void initEndTime() { this.endTime = System.currentTimeMillis(); } /** * 获取毫秒格式的耗时信息 * @return */ public String getTimeIntervalByMilli() { return String.valueOf(this.endTime - this.startTime) + "毫秒"; } /** * 获取秒格式的耗时信息 * @return */ public String getTimeIntervalBySecond() { double interval = (this.endTime - this.startTime)/1000.0; DecimalFormat df = new DecimalFormat("#.##"); return df.format(interval) + "秒"; } /** * 打印时间信息 */ public void printTimeInfo() { LogInfoUtil.printLog("起始时间:" + this.startTime); LogInfoUtil.printLog("截止时间:" + this.endTime); LogInfoUtil.printLog("耗费时间:" + this.getTimeIntervalBySecond()); } /** * 获取HBase检索结果集合 * @return */ public List<Result> getResultList() { return resultList; } /** * 设置HBase检索结果集合 * @param resultList */ public void setResultList(List<Result> resultList) { this.resultList = resultList; } }
综上所述,我们没有对总记录数和总页数进行统计处理,并且用“已检索记录数”代替了“总记录数”。另外,对每次检索的耗时信息进行了统计记录,便于开发人员调试统计效率。
2、HBase表数据分页检索方法
就像关系数据库Oracle那样,我们进行数据检索时往往附带有很多的检索条件,HBase表数据检索也不例外。HBase表数据检索条件通常有以下几种:RowKey行键范围(如果不确定范围的话则面向全表)、过滤器、数据版本。所以,当我们决定要设计一个比较通用的数据分页检索接口方法时,就不得不考虑以上几种检索条件。
顺便贴出“获取HBase表第一行数据”的接口方法。
/** * 检索指定表的第一行记录。<br> * (如果在创建表时为此表指定了非默认的命名空间,则需拼写上命名空间名称,格式为【namespace:tablename】)。 * @param tableName 表名称(*)。 * @param filterList 过滤器集合,可以为null。 * @return */ public static Result selectFirstResultRow(String tableName,FilterList filterList) { if(StringUtils.isBlank(tableName)) return null; HTable table = null; try { table = HBaseTableManageUtil.getHBaseTable(tableName); Scan scan = new Scan(); if(filterList != null) { scan.setFilter(filterList); } ResultScanner scanner = table.getScanner(scan); Iterator<Result> iterator = scanner.iterator(); int index = 0; while(iterator.hasNext()) { Result rs = iterator.next(); if(index == 0) { scanner.close(); return rs; } } } catch (IOException e) { e.printStackTrace(); } finally { try { table.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }
3、HBase表数据分页检索应用实例
HBasePageModel pageModel = new HBasePageModel(pageSize); pageModel = scanResultByPageFilter(“DLQX:SZYB_DATA”,null,null,null,pageModel); if(pageModel.getResultList().size() == 0) { //本页没有数据,说明已经是最后一页了。 return; }
作者:商兵兵
单位:河南省电力科学研究院智能电网所
QQ:52190634