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