HBase API及協處理器

HBase API 應用

引入依賴

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.3.1</version>
</dependency>

HBase API 使用示例:

public class HBaseClient {

    Connection connection;

    Admin admin;

    @Before
    public void init() throws IOException {
        Configuration configuration = HBaseConfiguration.create();
        configuration.set("hbase.zookeeper.quorum","linux2,linux3,linux4");
        configuration.set("hbase.zookeeper.property.clientPort","2181");
        connection = ConnectionFactory.createConnection(configuration);
    }


    /**
     * 創建表
     * @throws IOException
     */
    @Test
    public void createTable() throws IOException {
        admin = connection.getAdmin();
        //創建表描述器
        HTableDescriptor teacher = new HTableDescriptor(TableName.valueOf("teacher"));
        //設置列族
        teacher.addFamily(new HColumnDescriptor("info"));
        admin.createTable(teacher);
        System.out.println("創建teacher表成功");
    }

    /**
     * 新增數據
     * @throws IOException
     */
    @Test
    public void putData() throws IOException {
        Table teacher = connection.getTable(TableName.valueOf("teacher"));
        //設置rowkey
        Put put = new Put(Bytes.toBytes("003"));
        put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("name"),Bytes.toBytes("xiaoqing"));
        put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("age"),Bytes.toBytes("13"));
        teacher.put(put);
        teacher.close();
    }

    /**
     * 查詢某個列族數據
     */
    @Test
    public void getData() throws IOException {
        HTable teacher = (HTable) connection.getTable(TableName.valueOf("teacher"));
        Get get=new Get(Bytes.toBytes("001"));
        get.addFamily(Bytes.toBytes("info"));
        Result result = teacher.get(get);
        Cell[] cells = result.rawCells();
        for (Cell cell : cells){
            String cellFamily = Bytes.toString(CellUtil.cloneFamily(cell));
            String column = Bytes.toString(CellUtil.cloneQualifier(cell));
            String value = Bytes.toString(CellUtil.cloneValue(cell));
            String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
            System.out.println("rowkey:"+rowkey+","+cellFamily+"__"+column+"__"+value);
        }
    }

    /**
     * scan全表
     * @throws IOException
     */
    @Test
    public void scanAll() throws IOException {
        HTable teacher = (HTable) connection.getTable(TableName.valueOf("teacher"));
        Scan scan=new Scan();
        ResultScanner resultScanner = teacher.getScanner(scan);
        for (Result result : resultScanner){
            Cell[] cells = result.rawCells();
            for (Cell cell : cells){
                String cellFamily = Bytes.toString(CellUtil.cloneFamily(cell));
                String column = Bytes.toString(CellUtil.cloneQualifier(cell));
                String value = Bytes.toString(CellUtil.cloneValue(cell));
                String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
                System.out.println("rowkey:"+rowkey+","+cellFamily+"__"+column+"__"+value);
            }
        }
    }

    /**
     * scan全表數據根據rowkey範圍
     * @throws IOException
     */
    @Test
    public void scanRowKey() throws IOException {
        HTable teacher = (HTable) connection.getTable(TableName.valueOf("teacher"));
        Scan scan=new Scan();
        scan.setStartRow("001".getBytes(StandardCharsets.UTF_8));
        scan.setStopRow("002".getBytes(StandardCharsets.UTF_8));

        ResultScanner resultScanner = teacher.getScanner(scan);
        for (Result result : resultScanner){
            Cell[] cells = result.rawCells();
            for (Cell cell : cells){
                String cellFamily = Bytes.toString(CellUtil.cloneFamily(cell));
                String column = Bytes.toString(CellUtil.cloneQualifier(cell));
                String value = Bytes.toString(CellUtil.cloneValue(cell));
                String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
                System.out.println("rowkey:"+rowkey+","+cellFamily+"__"+column+"__"+value);
            }
        }
    }

    /**
     * 刪除數據
     * @throws IOException
     */
    @Test
    public void deleteData() throws IOException {
        Table teacher = (Table) connection.getTable(TableName.valueOf("teacher"));
        Delete delete=new Delete(Bytes.toBytes("001"));
        teacher.delete(delete);
        teacher.close();
        System.out.println("刪除數據成功");
    }



    @After
    public void destory(){
        if(admin != null){
            try {
                admin.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

HBase 協處理器

通常查詢 HBase 數據是使用 scan 或者 get,再根據獲取到的數據進行業務計算。但是在數據量非常大的時候,比如一個有上億行及十萬個列的數據集,再按照常用方式獲取數據就會獲得性能問題。客戶端也需要有強大的計算能力以及足夠的內存來處理這麼多的數據。

此時就可以考慮使用 Coprocessor(協處理器),將業務運算代碼封裝到 Coprocessor 中並在 RegionServer 上運行,即在數據實際存儲位置執行,最後將運算結果返回到客戶端。利用協處理器,用於可以編寫運行在 HBase Server 端的代碼。

協處理器類型:

1. Observer:

協處理器與觸發器類似,在一些特定事件發生時回調函數(也稱作鉤子函數)被執行。這些事包括一些用戶產生的事件,也包括服務端內部自動產生的事件。

協處理器框架提供的接口如下:

  • RegionObserver:用戶可以用這種的處理器處理數據修改事件
  • MasterObserver:可以被用作管理或 DDL 類型的操作,這些是集羣級事件
  • WALObserver:提供控制 WAL 的鉤子函數

案例實戰:

實現 HBase 當中向 t1 表中插入一條數據,指定的 t2 表也要插入一條一模一樣的數據。

  1. 先創建兩個表
create 't1','info'
create 't2','info'
  1. 引入依賴
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-server</artifactId>
    <version>1.3.1</version>
</dependency>
  1. 代碼編寫
public class MyProcessor extends BaseRegionObserver  {

    @Override
    public void prePut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, Durability durability) throws IOException {
        //把自己需要執行的邏輯定義在此處,向t2表插入數據,數據具體是什麼內容與Put一樣
        final HTableInterface t2 = e.getEnvironment().getTable(TableName.valueOf("t2"));
        //解析t1表的插入對象put
        final Cell cell = put.get(Bytes.toBytes("info"), Bytes.toBytes("name")).get(0);
        //table對象.put
        final Put put1 = new Put(put.getRow());
        put1.add(cell);
        t2.put(put1); //執行向t2表插入數據
        t2.close();
    }

}
  1. 打成 jar 包,上傳到 HDFS
hdfs dfs -mkdir -p /processor
hdfs dfs -put hbase-1.0-SNAPSHOT.jar /processor
  1. 掛載協處理器
alter 't1',METHOD => 'table_att','Coprocessor'=>'hdfs://linux2:9000/processor/hbase-1.0-SNAPSHOT.jar|com.lagou.hbase.MyProcessor|1001|'

掛載完成後,可以通過 以下命令看看掛載成功沒

describe 't1'


這樣就是掛載成功了

  1. 驗證
put 't1','rk1','info:name','lisi'

向 t1 表插入數據後,發現 t2 表也插入了數據。

卸載協處理器

disable 't1'
alter 't1',METHOD=>'table_att_unset',NAME=>'coprocessor$1'
enable 't1'

2. Endpoint

這類協處理器類似傳統數據庫中的存儲過程,客戶端可以調用這些 Endpoint 協處理器在 Regionserver 中執行一段代碼,並將 RegionServer 端執行結果返回給客戶端進一步處理。

常見用途

如:聚合操作。假設需要找出一張表中最大的數據,普通做法是全表掃描,然後在 Client 端內遍歷結果,並執行求最大值的操作。這種方式存在的弊端是無法利用底層集羣的併發運算能力,把所有計算都集中到 Client 端,效率低下。

使用 Endpoint Coprocessor,用戶可以把求最大值的代碼部署到 RegionServer 端,HBase 會利用集羣中多個節點的優勢來併發執行求最大值的操作。也就是每個 Region 範圍內求最大值,將每個 Region 的最大值在 RegionServer 端算出,僅僅將 max 值返回給 Client。在 Client 進一步將多個 Region 的最大值進行比較找到全局的最大值即可。

Endpoint Coprocessor 的應用藉助於 Phoenix 非常容易就能實現。

HBase 表的 RowKey 設計

  1. RowKey 字典順序

RowKey 是基於 ASCII 碼值進行字典排序的。先比較第一個字節,如果相同,就比較第二個字節。如果到第 X 個字節,其中一個已經超出了 rowkey 的長度,短 rowkey 的排在前面。

  1. RowKey 長度原則

rowkey 是一個二級制碼流,可以是任意字符串,最大長度 64kb,實際應用中一般爲 10-100bytes,以 byte[]形式保存,一般設計成定長。

建議越短越好,不要超過 16 個字節。設計過長會降低 Memstore 內存的利用率和 HFile 存儲數據的效率

  1. RowKey 散列原則

建議將 rowkey 的高位作爲散列字段,這樣將提高數據均衡分佈在每個 RegionServer,以實現負載均衡的機率

  1. RowKey 唯一原則

必須在設計上保證其唯一性。訪問 hbase table 中的行,有三種方式:

  • 單個 rowkey
  • rowkey 的 range
  • 全表掃描(儘量避免全表掃描)
  1. RowKey 排序原則

HBase 的 RowKey 是按照 ASCII 有序設計的,我們設計 RowKey 時要充分利用這點。

HBase 表的熱點

什麼是熱點?

檢索 hbase 的記錄首先要通過 rowkey 來定位數據行,當大量的 client 訪問 hbase 集羣的一個或少數幾個節點,造成少數 region server 請求過多,而其他 region server 請求很少,就造成了“熱點”現象。

熱點的解決方案

  1. 預分區

預分區的目的是讓表的數據可以均衡的分散在集羣中,而不是默認只有一個 region 分佈在集羣的一個節點上。

  1. 加鹽

這裏的“加鹽”指的是在 rowkey 的前面增加隨機數,具體就是給 rowkey 分配一個隨機前綴以使得它和之前的 rowkey 的開頭不同

  1. 哈希

哈希會使同一行永遠用同一個前綴加鹽。哈希也可以使負載分散到整個集羣,但是讀卻是可以預測的。使用確定的哈希可以讓客戶端重構完整的 rowkey,可以使用 get 操作準確獲取某一個行數據。

原始數據: abc1,abc2,abc3

哈希:

md5(abc1)=92231b....., 9223-abc1

md5(abc2) =32a131122...., 32a1-abc2

md5(abc3) = 452b1...., 452b-abc3.

  1. 反轉

反轉固定長度或者數字格式的 rowkey,這樣可以使得 rowkey 中經常改變的部分放在前面,這樣可以有效的隨機 rowkey,但是犧牲了 rowkey 的有序性。

HBase 應用技巧

二級索引

HBase 按照 rowkey 查詢性能是最高的,rowkey 就相當於 hbase 表的一級索引。

爲了 HBase 的數據查詢更高效、適應更多的場景。如:使用非 rowkey 字段檢索也能秒級相應,或者支持多個字段組合查詢或模糊查詢等。因此需要在 HBase 上構建二級索引,以滿足現實中複雜多樣的業務需求。

hbase 的二級索引其本質就是建立 hbase 表中列與行鍵的映射關係。

常見的二級索引我們一般可以藉助各種其他的方式來實現,例如 Phoenix、Solr、Es。

布隆過濾器的應用

之前在講 hbase 的數據存儲原理的時候,我們知道 hbase 的讀操作需要訪問大量的文件,大部分的實現通過布隆過濾器來避免大量的讀文件操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章