数据分页处理系列之二: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表数据分页模型类。<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行键范围(如果不确定范围的话则面向全表)、过滤器、数据版本。所以,当我们决定要设计一个比较通用的数据分页检索接口方法时,就不得不考虑以上几种检索条件。
/**
* 分页检索表数据。<br>
* (如果在创建表时为此表指定了非默认的命名空间,则需拼写上命名空间名称,格式为【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<Result> resultList = new ArrayList<Result>();
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表第一行数据”的接口方法。
/**
* 检索指定表的第一行记录。<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
数据分页处理系列之二:HBase表数据分页处理的更多相关文章
- 数据分页处理系列之一:Oracle表数据分页检索SQL
关于Oracle数据分页检索SQL语法,网络上比比皆是,花样繁多,本篇也是笔者本人在网络上搜寻的比较有代表性的语法,绝非本人原创,贴在这里,纯粹是为了让"数据分页专题系列"看起 ...
- HBase表数据分页处理
HBase表数据分页处理 HBase是Hadoop大数据生态技术圈中的一项关键技术,是一种用于分布式存储大数据的列式数据库,关于HBase更加详细的介绍和技术细节,朋友们可以在网络上进行搜寻,笔者本人 ...
- HBase(三): Azure HDInsigt HBase表数据导入本地HBase
目录: hdfs 命令操作本地 hbase Azure HDInsight HBase表数据导入本地 hbase hdfs命令操作本地hbase: 参见 HDP2.4安装(五):集群及组件安装 , ...
- 一种HBase表数据迁移方法的优化
1.背景调研: 目前存在的hbase数据迁移主要分如下几类: 根据上图,可以看出: 其实主要分为两种方式:(1)hadoop层:因为hbase底层是基于hdfs存储的,所以可以通过把hdfs上的数据拷 ...
- 大数据学习系列之二 ----- HBase环境搭建(单机)
引言 在上一篇中搭建了Hadoop的单机环境,这一篇则搭建HBase的单机环境 环境准备 1,服务器选择 阿里云服务器:入门型(按量付费) 操作系统:linux CentOS 6.8 Cpu:1核 内 ...
- 数据分页处理系列之三:Neo4j图数据分页处理
首先简单介绍下Neo4j,Neo4j是一个高性能的NOSQL图形数据库,它将结构化数据存储在网络上而不是表中,它是一个嵌入式的.基于磁盘的.具备完全的事务特性的Java持久化引擎,但是它将结构化数 ...
- 议:如何将树形菜单形式的数据转化成HTML的二维表(相同内容需合并单元格)
一般做OA类管理系统,经常涉及到“组织架构”的概念,那么像这种有上下层级关系的数据一般会做成树形菜单的方式显示,底层代码必定会用到递归算法.这篇随笔的目的就是要谈谈除了用树形菜单来显示这种上下层级关系 ...
- Pandas系列(二)- DataFrame数据框
一.初识DataFrame dataFrame 是一个带有索引的二维数据结构,每列可以有自己的名字,并且可以有不同的数据类型.你可以把它想象成一个 excel 表格或者数据库中的一张表DataFram ...
- HBase表数据的转移之使用自定义MapReduce
目标:将fruit表中的一部分数据,通过MR迁入到fruit_mr表中 Step1.构建ReadFruitMapper类,用于读取fruit表中的数据 package com.z.hbase_mr; ...
随机推荐
- PHP知识库图谱汇总(完善中)
基本语法不做汇总 经典算法: 冒泡算法.快速算法.二分查找 字符串处理: 字符串查找 字符串排序 字符串切割 字符串定位 字符串对比 字符串大小写转换 Session和Cookies: Session ...
- jQuery Mobile页面返回无需重新get
最近公司的web app项目,使得我有幸一直接触和学习jQuery Mobile.这确实是一个很不错的移动开发库,有助于擅长web开发的工程师,快速入门并构建自己的移动应用.但是在前两天,我碰到了一个 ...
- linux下使用taskset设置进程cpu绑定不起作用
自从大规模使用了虚拟化之后,大流量时soft interrupt在某个cpu很高就是个严重的问题,最近一有时间就研究这个问题,如果网卡本身不支持多队列的话,有没有办法缓解这个问题. 一开始使用rps, ...
- Release编译模式下,事件是否会引起内存泄漏问题初步研究
题记:不常发生的事件内存泄漏现象 想必有些朋友也常常使用事件,但是很少解除事件挂钩,程序也没有听说过内存泄漏之类的问题.幸运的是,在某些情况下,的确不会出问题,很多年前做的项目就跑得好好的,包括我也是 ...
- C#多线程:使用ReaderWriterLock类实现多用户读/单用户写同步
摘要:C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景.该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定, ...
- 模拟Executor策略的实现
Executor作为现在线程的一个管理工具,就像管理线程的管理器一样,不用像以前一样,通过start来开启线程 Executor将提交线程与执行线程分离开来,使得用户只需要提交线程,并不需要在乎怎么和 ...
- border-radius如何兼容IE
目前而言firefox,opera,chrome等主流浏览器都已经支持border-radius属性,唯独IE8以及之前. 解决办法就是在用的border-radius属性的后面加上:behavior ...
- about this
var name="window name"; var obj={ name:"obj name", getNameFunc:function(){ //thi ...
- [deviceone开发]-do_FrameAnimtionView的简单动画示例
一.简介 do_FrameAnimtionView组件是用加载GIF动态图片和加载一系列图片形成动画效果的展示组件,这个示例直观的展示组件基本的使用方式.适合初学者. 二.效果图 三.相关下载 htt ...
- javascript中this关键字详解
不管学习什么知识,习惯于把自己所学习的知识列成一个list,会有助于我们理清思路,是一个很好的学习方法.强烈推荐. 以下篇幅有点长,希望读者耐心阅读. 以下内容会分为如下部分: 1.涵义 1.1:th ...