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;
}
Hadoop+HBase搭建雲端儲存總結 PDF
HBase 結點之間時間不一致造成regionserver啟動失敗
Hadoop+ZooKeeper+HBase叢集配置
Hadoop叢集安裝&HBase實驗環境搭建
基於Hadoop叢集的HBase叢集的配置 ‘
Hadoop安裝部署筆記之-HBase完全分布模式安裝
單機版搭建HBase環境圖文教程詳解
HBase 的詳細介紹:請點這裡
HBase 的:請點這裡
本文永久更新連結地址: