云计算中心_楼兰云吧 关注:9贴子:63
  • 0回复贴,共1

楼兰云分享:数据分页处理系列之二:HBase表数据分页处理

只看楼主收藏回复

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表数据分页模型类。
  * 利用此类可管理多个HBaseQualifierModel对象。
  * Copyright: Copyright (c) 2014
  * Company: 河南电力科学研究院智能电网所
  * @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 resultList = new ArrayList();
  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 getResultList() {
  return resultList;
  }
  /**
  * 设置HBase检索结果集合
  * @param resultList
  */
  public void setResultList(List resultList) {
  this.resultList = resultList;
  }
  }
  综上所述,我们没有对总记录数和总页数进行统计处理,并且用“已检索记录数”代替了“总记录数”。另外,对每次检索的耗时信息进行了统计记录,便于开发人员调试统计效率。
  2、HBase表数据分页检索方法
  就像关系数据库Oracle那样,我们进行数据检索时往往附带有很多的检索条件,HBase表数据检索也不例外。HBase表数据检索条件通常有以下几种:RowKey行键范围(如果不确定范围的话则面向全表)、过滤器、数据版本。所以,当我们决定要设计一个比较通用的数据分页检索接口方法时,就不得不考虑以上几种检索条件。
  /**
  * 分页检索表数据。
  * (如果在创建表时为此表指定了非默认的命名空间,则需拼写上命名空间名称,格式为【namespace:tablename】)。
  * @param tableName 表名称(*)。
  * @param startRowKey 起始行键(可以为空,如果为空,则从表中第一行开始检索)。
  * @param endRowKey 结束行键(可以为空)。
  * @param filterList 检索条件过滤器集合(不包含分页过滤器;可以为空)。
  * @param maxVersions 指定最大版本数【如果为最大整数值,则检索所有版本;如果为最小整数值,则检索最新版本;否则只检索指定的版本数】。
  * @param pageModel 分页模型(*)。
  * @return 返回HBasePageModel分页对象。
  */
  public static HBasePageModel scanResultByPageFilter(String tableName, byte[] startRowKey, byte[] endRowKey, FilterList filterList, int maxVersions, HBasePageModel pageModel) {
  if(pageModel == null) {
  pageModel = new HBasePageModel(10);
  }
  if(maxVersions <= 0 ) {
  //默认只检索数据的最新版本
  maxVersions = Integer.MIN_VALUE;
  }
  pageModel.initStartTime();
  pageModel.initEndTime();
  if(StringUtils.isBlank(tableName)) {
  return pageModel;
  }
  HTable table = null;
  try {
  //根据HBase表名称,得到HTable表对象,这里用到了笔者本人自己构建的一个表信息管理类。
  table = HBaseTableManageUtil.getHBaseTable(tableName);
  int tempPageSize = pageModel.getPageSize();
  boolean isEmptyStartRowKey = false;
  if(startRowKey == null) {
  //则读取表的第一行记录,这里用到了笔者本人自己构建的一个表数据操作类。
  Result firstResult = HBaseTableDataUtil.selectFirstResultRow(tableName, filterList);
  if(firstResult.isEmpty()) {
  return pageModel;
  }
  startRowKey = firstResult.getRow();
  }
  if(pageModel.getPageStartRowKey() == null) {
  isEmptyStartRowKey = true;
  pageModel.setPageStartRowKey(startRowKey);
  } else {
  if(pageModel.getPageEndRowKey() != null) {
  pageModel.setPageStartRowKey(pageModel.getPageEndRowKey());
  }
  //从第二页开始,每次都多取一条记录,因为第一条记录是要删除的。
  tempPageSize += 1;
  }
  Scan scan = new Scan();
  scan.setStartRow(pageModel.getPageStartRowKey());
  if(endRowKey != null) {
  scan.setStopRow(endRowKey);
  }
  PageFilter pageFilter = new PageFilter(pageModel.getPageSize() + 1);
  if(filterList != null) {
  filterList.addFilter(pageFilter);
  scan.setFilter(filterList);
  } else {
  scan.setFilter(pageFilter);
  }
  if(maxVersions == Integer.MAX_VALUE) {
  scan.setMaxVersions();
  } else if(maxVersions == Integer.MIN_VALUE) {
  } else {
  scan.setMaxVersions(maxVersions);
  }
  ResultScanner scanner = table.getScanner(scan);
  List resultList = new ArrayList();
  int index = 0;
  for(Result rs : scanner.next(tempPageSize)) {
  if(isEmptyStartRowKey == false && index == 0) {
  index += 1;
  continue;
  }
  if(!rs.isEmpty()) {
  resultList.add(rs);
  }
  index += 1;
  }
  scanner.close();
  pageModel.setResultList(resultList);
  } catch (Exception e) {
  e.printStackTrace();
  } finally {
  try {
  table.close();
  } catch (IOException e) {
  e.printStackTrace();
  }
  }
  int pageIndex = pageModel.getPageIndex() + 1;
  pageModel.setPageIndex(pageIndex);
  if(pageModel.getResultList().size() > 0) {
  //获取本次分页数据首行和末行的行键信息
  byte[] pageStartRowKey = pageModel.getResultList().get(0).getRow();
  byte[] pageEndRowKey = pageModel.getResultList().get(pageModel.getResultList().size() - 1).getRow();
  pageModel.setPageStartRowKey(pageStartRowKey);
  pageModel.setPageEndRowKey(pageEndRowKey);
  }
  int queryTotalCount = pageModel.getQueryTotalCount() + pageModel.getResultList().size();
  pageModel.setQueryTotalCount(queryTotalCount);
  pageModel.initEndTime();
  pageModel.printTimeInfo();
  return pageModel;
  }
  顺便贴出“获取HBase表第一行数据”的接口方法。
  /**
  * 检索指定表的第一行记录。
  * (如果在创建表时为此表指定了非默认的命名空间,则需拼写上命名空间名称,格式为【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 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;
  }


IP属地:新疆1楼2015-12-17 18:14回复