數據分頁處理系列之二: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

主頁:http://www.cnblogs.com/shangbingbing

空間:http://shangbingbing.qzone.qq.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM