主頁 > 資料庫 > 圖文詳解:記憶體總是不夠,我靠HBase說服了Leader為新專案保駕護航

圖文詳解:記憶體總是不夠,我靠HBase說服了Leader為新專案保駕護航

2021-04-02 06:29:55 資料庫

?

?

大家好,我是小羽

最近在作業中用到了 Hbase 這個資料庫,也順便做了關于 Hbase 的知識記錄來分享給大家,其實 Hbase的內容體系真的很多很多,這里介紹的是小羽認為在作業中會用到的一些技術點,希望可以幫助到大家,

可以這么說互聯網都是建立在形形色色的資料庫之上的,現在主流的資料庫有這么幾種:以 MySQL 為代表的關系型資料庫以及其分布式解決方案,以 Redis 為代表的快取資料庫,以 ES 為代表的檢索資料庫,再就是分布式持久化 KV 資料庫,而在開源領域,尤其是國內,HBase 幾乎是分布式持久化KV資料庫的首選方案,HBase 應用的業務場景非常之多,比如用戶畫像、實時(離線)推薦、實時風控、社交Feed流、商品歷史訂單、社交聊天記錄、監控系統以及用戶行為日志等等,

前言

我們每一個人無論使用什么科技產品,都會產生大量的資料,而這些資料的存盤和查詢對于小型資料庫來說其實是很難滿足我們的需求的,因此出現了 HBase 分布式大資料,HBase 是一個構建在 Hadoop 檔案系統之上的面向列的資料庫管理系統,HBase 是一種類似于 Google’s Big Table 的資料模型,它是 Hadoop 生態系統的一部分,它將資料存盤在 HDFS 上,客戶端可以通過 HBase 實作對 HDFS 上資料的隨機訪問,它主要有以下特性:

不支持復雜的事務,只支持行級事務,即單行資料的讀寫都是原子性的;

由于是采用 HDFS 作為底層存盤,所以和 HDFS 一樣,支持結構化、半結構化和非結構化的存盤;

支持通過增加機器進行橫向擴展;

支持資料分片

支持 RegionServers 之間的自動故障轉移

易于使用的 Java 客戶端 API

支持 BlockCache布隆過濾器

過濾器支持謂詞下推

HBase 原理

概念

HBase 是分布式、面向列的開源資料庫(其實準確的說是面向列族),HDFS 為 Hbase 提供可靠的底層資料存盤服務MapReduce 為 Hbase 提供高性能的計算能力Zookeeper 為 Hbase 提供穩定服務Failover 機制,因此我們說 Hbase 是一個通過大量廉價的機器解決海量資料的高速存盤和讀取的分布式資料庫解決方案

列式存盤

我們先來看一下之前的關系型資料庫的按行來存盤的,如下圖:

?

可以看到只有第一行 ID:1 小羽的這一行的資料都填了,小娜和小智的資料都沒有填完,在我們的行結構中,都是固定的,每一行都一樣,就算不填,也要空著,不能沒有,

來看一下使用了非關系型資料庫的按列存盤的效果圖:

?

可以看到之前小羽的一列資料對應到了小羽現在的一行資料,原來小羽的七列資料變成了現在的七行,之前的七行資料在一行,共用過一個主鍵 ID:1 ,在列式存盤里,變成了七行,每一行都有一個主鍵與其對應,也就是為什么小羽的主鍵 ID:1 重復了七次,這樣排列最大的好處就是,我們對于不需要的資料就不需要添加,會大大節省我們的空間資源,因為查詢中的選擇規則是通過列來定義的,整個資料庫是自動索引化的,

NoSQL和關系型資料庫對比

對比如下圖

?

RDBMS 與 Hbase 對比

Hbase 是根據列族來存盤資料的,列族下面可以有非常多的列,列族在創建表的時候就必須指定,為了加深對 Hbase 列族的理解,下面是簡單的關系型資料庫的表和 Hbase 資料庫的表:

?

主要區別

?

HBase 架構

Hbase 是由 Client、Zookeeper、Master、HRegionServer、HDFS 等幾個核心體系組成,

?

Client

Client 使用 HBase 的 RPC 機制與 HMaster、HRegionServer 進行通信,Client 與 HMaster 進行管理類通信,與 HRegion Server 進行資料操作類通信

Zookeeper

Hbase 通過 Zookeeper 來做 master 的高可用、RegionServer 的監控、元資料的入口以及集群配置的維護等作業,具體作業如下:

1. 通過 Zoopkeeper 來保證集群中只有 1 個 master 在運行,如果 master 例外,會通過競爭機制產生新的 master 提供服務

2. 通過 Zoopkeeper 來監控 RegionServer 的狀態,當 RegionSevrer 有例外的時候,通過回呼的形式通知 Master RegionServer 上下限的資訊

3. 通過 Zoopkeeper 存盤元資料的統一入口地址

客戶端在使用 hbase 的時候,需要添加 zookeeper 的 ip 地址和節點路徑,建立起與zookeeper的連接,建立連接的方式如下面的代碼所示:

Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum", "XXXX.XXX.XXX");
configuration.set("hbase.zookeeper.property.clientPort", "2181");
configuration.set("zookeeper.znode.parent", "XXXXX");
Connection connection = ConnectionFactory.createConnection(configuration);

Hmaster

master 節點的主要職責如下:

1. 為 RegionServer 分配 Region

2. 維護整個集群的負載均衡

3. 維護集群的元資料資訊,發現失效的 Region,并將失效的 Region 分配到正常RegionServer 上當 RegionSever 失效的時候,協調對應 Hlog 的拆分

HRegionServer

HRegionServer 內部管理了一系列 HRegion 物件,每個 HRegion 對應 Table 中的一個 ColumnFamily 的存盤,即一個 Store 管理一個 Region 上的一個列族(CF),每個 Store 包含一個 MemStore 和 0 到多個 StoreFile,Store 是 HBase 的存盤核心,由 MemStore 和 StoreFile 組成,

HLog

資料在寫入時,首先寫入預寫日志(Write Ahead Log),每個 HRegionServer 服務的所有 Region 的寫操作日志都存盤在同一個日志檔案中,資料并非直接寫入 HDFS,而是等快取到一定數量再批量寫入,寫入完成后在日志中做標記

MemStore

MemStore 是一個有序的記憶體快取區,用戶寫入的資料首先放入 MemStore,當 MemStore 滿了以后 Flush 成一個 StoreFile(存盤時對應為 File),當 StoreFile 數量增到一定閥值,觸發 Compact 合并,將多個 StoreFile 合并成一個 StoreFile,StoreFiles 合并后逐步形成越來越大的 StoreFile,當 Region 內所有 StoreFiles(Hfile) 的總大小超過閥值(hbase.hregion.max.filesize)即觸發分裂 Split,把當前的 Region Split 分成 2 個 Region,父 Region 下線,新 Spilt 出的 2 個孩子 Region 被 HMaster 分配到合適的 HRegionServer 上,使得原先 1 個 Region 的壓力得以分流到 2 個 Region 上,

Region 尋址方式

通過 zookeeper.META,主要有以下幾步:

1. Client 請求 ZK 獲取.META.所在的 RegionServer 的地址

2. Client 請求.META.所在的 RegionServer 獲取訪問資料所在的 RegionServer 地址,client 會將.META.的相關資訊 cache 下來,以便下一次快速訪問,

3. Client 請求資料所在的 RegionServer,獲取所需要的資料

?

HDFS

HDFS 為 Hbase 提供最終的底層資料存盤服務,同時為 Hbase 提供高可用(Hlog 存盤在HDFS)的支持,

HBase 組件

Column Family 列族

Column Family 又叫列族,Hbase 通過列族劃分資料的存盤,列族下面可以包含任意多的列,實作靈活的資料存取,Hbase 表的創建的時候就必須指定列族,就像關系型資料庫創建的時候必須指定具體的列是一樣的,Hbase 的列族不是越多越好,官方推薦的是列族最好小于或者等于 3,我們使用的場景一般是 1 個列族,

Rowkey

Rowkey 的概念和 mysql 中的主鍵是完全一樣的,Hbase 使用 Rowkey 來唯一的區分某一行的資料,Hbase 只支持 3 種查詢方式:基于 Rowkey 的單行查詢,基于 Rowkey 的范圍掃描全表掃描

Region 磁區

Region:Region 的概念和關系型資料庫的磁區或者分片差不多,Hbase 會將一個大表的資料基于 Rowkey 的不同范圍分配到不同的 Region 中,每個 Region 負責一定范圍的資料訪問和存盤,這樣即使是一張巨大的表,由于被切割到不同的 region,訪問起來的時延也很低

?

TimeStamp 多版本

TimeStamp 是實作 Hbase 多版本的關鍵,在 Hbase 中使用不同的 timestame 來標識相同 rowkey 行對應的不同版本的資料,在寫入資料的時候,如果用戶沒有指定對應的 timestamp,Hbase 會自動添加一個 timestamp,timestamp 和服務器時間保持一致,在Hbase 中,相同 rowkey 的資料按照 timestamp 倒序排列,默認查詢的是最新的版本,用戶可通過指定 timestamp 的值來讀取舊版本的資料,

Hbase 寫邏輯

Hbase 寫入流程

主要有三個步驟:

1. Client 獲取資料寫入的 Region 所在的 RegionServer

2. 請求寫 Hlog, Hlog 存盤在 HDFS,當 RegionServer 出現例外,需要使用 Hlog 來恢復資料

3. 請求寫 MemStore,只有當寫 Hlog 和寫 MemStore 都成功了才算請求寫入完成,MemStore 后續會逐漸刷到 HDFS 中,

?

MemStore 刷盤

為了提高 Hbase 的寫入性能,當寫請求寫入 MemStore 后,不會立即刷盤,而是會等到一定的時候進行刷盤的操作,具體是哪些場景會觸發刷盤的操作呢?總結成如下的幾個場景:

1. 這個全域的引數是控制記憶體整體的使用情況,當所有 memstore 占整個 heap 的最大比例的時候,會觸發刷盤的操作,這個引數是hbase.regionserver.global.memstore.upperLimit,默認為整個 heap 記憶體的 40%,但這并不意味著全域記憶體觸發的刷盤操作會將所有的 MemStore 都進行輸盤,而是通過另外一個引數 hbase.regionserver.global.memstore.lowerLimit 來控制,默認是整個 heap 記憶體的 35%,當 flush 到所有 memstore 占整個 heap 記憶體的比率為35%的時候,就停止刷盤,這么做主要是為了減少刷盤對業務帶來的影響,實作平滑系統負載的目的,

2. 當 MemStore 的大小達到 hbase.hregion.memstore.flush.size 大小的時候會觸發刷盤,默認 128M 大小

3. 前面說到 Hlog 為了保證 Hbase 資料的一致性,那么如果 Hlog 太多的話,會導致故障恢復的時間太長,因此 Hbase 會對 Hlog 的最大個數做限制,當達到 Hlog 的最大個數的時候,會強制刷盤,這個引數是 hase.regionserver.max.logs,默認是 32 個,

4. 可以通過 hbase shell 或者 java api 手工觸發 flush 的操作,

5. 在正常關閉 RegionServer 會觸發刷盤的操作,全部資料刷盤后就不需要再使用 Hlog 恢復資料,

6. 當 RegionServer 出現故障的時候,其上面的 Region 會遷移到其他正常的 RegionServer 上,在恢復完 Region 的資料后,會觸發刷盤,當刷盤完成后才會提供給業務訪問,

HBase 中間層

Phoenix 是 HBase 的開源 SQL 中間層,它允許你使用標準 JDBC 的方式來操作 HBase 上的資料,在 Phoenix 之前,如果你要訪問 HBase,只能呼叫它的 Java API,但相比于使用一行 SQL 就能實作資料查詢,HBase 的 API 還是過于復雜,Phoenix 的理念是 we put sql SQL back in NOSQL,即你可以使用標準的 SQL 就能完成對 HBase 上資料的操作,同時這也意味著你可以通過集成 Spring Data JPA 或 Mybatis 等常用的持久層框架來操作 HBase,

其次 Phoenix 的性能表現也非常優異,Phoenix 查詢引擎會將 SQL 查詢轉換為一個或多個 HBase Scan,通過并行執行來生成標準的 JDBC 結果集,它通過直接使用 HBase API 以及協處理器和自定義過濾器,可以為小型資料查詢提供毫秒級的性能,為千萬行資料的查詢提供秒級的性能,同時 Phoenix 還擁有二級索引等 HBase 不具備的特性,因為以上的優點,所以 Phoenix 成為了 HBase 最優秀的 SQL 中間層,

HBase 安裝使用

下載 HBase 壓縮包,首先解壓

tar -zxvf hbase-0.98.6-hadoop2-bin.tar.gz

打開 hbase-env.sh 檔案配置 JAVA_HOME:

export JAVA_HOME=/opt/modules/jdk1.7.0_79

配置 hbase-site.xml:

<configuration>
 <property>
    <name>hbase.rootdir</name>
    <value>hdfs://hadoop-senior.shinelon.com:8020/hbase</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>hadoop-senior.shinelon.com</value>
  </property>
</configuration>

將上面的主機名換為自己的主機名,就可以啟動HBase了,Web 頁面訪問如下:

?

HBase 命令

下面是小羽整理的一些關于 Hbase 的經常會使用到的命令

?

HBase API 使用

API 如下

package com.initialize;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.regionserver.BloomType;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
/**
 *  
 *  1、構建連接
 *  2、從連接中取到一個表DDL操作工具admin
 *  3、admin.createTable(表描述物件);
 *  4、admin.disableTable(表名);
 * 5、admin.deleteTable(表名);
 * 6、admin.modifyTable(表名,表描述物件); 
 *
 */
public class HbaseClientDemo {

    Connection conn = null;

    @Before
    public void getConn() throws IOException {
        //構建一個連接物件
        Configuration conf = HBaseConfiguration.create();//會自動加載hbase-site.xml
        conf.set("hbase.zookeeper.quorum","n1:2181,n2:2181,n3:2181");

        conn = ConnectionFactory.createConnection(conf);
    }

    /**
     * DDL
     * 創建表
     */
    @Test
    public void testCreateTable() throws  Exception{

        //從連接中構造一個DDL操作器
        Admin admin = conn.getAdmin();

        //創建一個標定義描述物件
        HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("user_info"));

        //創建列族定義描述物件
        HColumnDescriptor hColumnDescriptor_1 = new HColumnDescriptor("base_info");
        hColumnDescriptor_1.setMaxVersions(3);

        HColumnDescriptor hColumnDescriptor_2 = new HColumnDescriptor("extra_info");

        //將列族定義資訊物件放入表定義物件中
        hTableDescriptor.addFamily(hColumnDescriptor_1);
        hTableDescriptor.addFamily(hColumnDescriptor_2);

        //用ddl操作器物件:admin來創建表
        admin.createTable(hTableDescriptor);

        //關閉連接
        admin.close();
        conn.close();

    }

    /**
     * 洗掉表
     */
    @Test
    public void testDropTable() throws  Exception{

        Admin admin = conn.getAdmin();

        //停用表
        admin.disableTable(TableName.valueOf("user_info"));
        //洗掉表
        admin.deleteTable(TableName.valueOf("user_info"));

        admin.close();
        conn.close();
    }

    /**
     * 修改表定義--添加一個列族
     */
    @Test
    public void testAlterTable() throws  Exception{

        Admin admin = conn.getAdmin();

        //取出舊的表定義資訊
        HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("user_info"));

        //新構造一個列族定義
        HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("other_info");
        hColumnDescriptor.setBloomFilterType(BloomType.ROWCOL);//設定該列族的布隆過濾器型別

        //將列族定義添加到表定義物件中
        tableDescriptor.addFamily(hColumnDescriptor);

        //將修改過的表定義交給admin去提交
        admin.modifyTable(TableName.valueOf("user_info"), tableDescriptor);

        admin.close();
        conn.close();
    }
}

示例如下

package com.initialize;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellScanner;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

public class HbaseClientDML {

    Connection conn = null;

    @Before
    public void getConn() throws IOException {
        //構建一個連接物件
        Configuration conf = HBaseConfiguration.create();//會自動加載hbase-site.xml
        conf.set("hbase.zookeeper.quorum","n1:2181,n2:2181,n3:2181");

        conn = ConnectionFactory.createConnection(conf);
    }


    /**
     * 增,改:put來覆寫
     */
    @Test
    public void testPut() throws  Exception{

        //獲取一個操作指定表的table物件,進行DML操作
        Table table = conn.getTable(TableName.valueOf("user_info"));

        //構造要插入的資料為一個Put型別(一個put物件只能對應一個rowkey)的物件
        Put put = new Put(Bytes.toBytes("001"));
        put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"),Bytes.toBytes("小羽"));
        put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("18"));
        put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("成都"));

        Put put2 = new Put(Bytes.toBytes("002"));
        put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("小娜"));
        put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("17"));
        put2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("成都"));

        ArrayList<Put> puts = new ArrayList<>();
        puts.add(put);
        puts.add(put2);

        //插進去
        table.put(puts);

        table.close();
        conn.close();
    }

    /***
     * 回圈插入大量資料
     */
    @Test
    public void testManyPuts() throws Exception{

        Table table = conn.getTable(TableName.valueOf("user_info"));
        ArrayList<Put> puts = new ArrayList<>();

        for(int i=0;i<10000;i++){
            Put put = new Put(Bytes.toBytes(""+i));
            put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("usernaem"), Bytes.toBytes("小羽" +i));
            put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes((18+i) + ""));
            put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("成都"));

            puts.add(put);
        }

        table.put(puts);
    }

    /**
     * 刪
     */
    @Test
    public void testDelete() throws Exception{
        Table table = conn.getTable(TableName.valueOf("user_info"));

        //構造一個物件封裝要洗掉的資料資訊
        Delete delete1 = new Delete(Bytes.toBytes("001"));

        Delete delete2 = new Delete(Bytes.toBytes("002"));
        delete2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"));

        ArrayList<Delete> dels = new ArrayList<>();
        dels.add(delete1);
        dels.add(delete2);

        table.delete(dels);

        table.close();
        conn.close();
    }

    /**
     * 查
     */
    @Test
    public void  testGet() throws Exception{

        Table table = conn.getTable(TableName.valueOf("user_info"));

        Get get = new Get("002".getBytes());

        Result result = table.get(get);

        //從結果中取用戶指定的某個key和value的值
        byte[] value = https://www.cnblogs.com/qianyueric/p/result.getValue("base_info".getBytes(), "age".getBytes());
        System.out.println(new String(value));

        System.out.println("======================");

        //遍歷整行結果中的所有kv單元格
        CellScanner cellScanner = result.cellScanner();
        while(cellScanner.advance()){
            Cell cell = cellScanner.current();

            byte[] rowArray = cell.getRowArray();//本kv所屬行鍵的位元組陣列
            byte[] familyArray = cell.getFamilyArray();//列族名的位元組陣列
            byte[] qualifierArray = cell.getQualifierArray();//列名的位元組陣列
            byte[] valueArray = cell.getValueArray();//value位元組陣列
            
            System.out.println("行鍵:" + new String(rowArray, cell.getRowOffset(), cell.getRowLength()));
            System.out.println("列族名:" + new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()));
            System.out.println("列名:" + new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()));
            System.out.println("value:" + new String(valueArray, cell.getValueOffset(), cell.getValueLength()));

        }
        table.close();
        conn.close();

    }


    /**
     * 按行鍵范圍查詢資料
     */
    @Test
    public void testScan() throws Exception{

        Table table = conn.getTable(TableName.valueOf("user_info"));

        //包含起始行鍵,不包含結束行鍵,但是如果真的是想要查詢出末尾的那個行鍵,可以在尾行鍵上拼接一個不可見的字符(\000)
        Scan scan = new Scan("10".getBytes(), "10000\001".getBytes());

        ResultScanner scanner =table.getScanner(scan);

        Iterator<Result> iterator = scanner.iterator();

        while(iterator.hasNext()){

            Result result =iterator.next();
            //遍歷整行結果中的所有kv單元格
            CellScanner cellScanner = result.cellScanner();
            while(cellScanner.advance()){
                Cell cell = cellScanner.current();

                byte[] rowArray = cell.getRowArray();//本kv所屬行鍵的位元組陣列
                byte[] familyArray = cell.getFamilyArray();//列族名的位元組陣列
                byte[] qualifierArray = cell.getQualifierArray();//列明的位元組陣列
                byte[] valueArray = cell.getValueArray();//value位元組陣列

                System.out.println("行鍵:" + new String(rowArray, cell.getRowOffset(), cell.getRowLength()));
                System.out.println("列族名:" + new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()));
                System.out.println("列名:" + new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()));
                System.out.println("value:" + new String(valueArray, cell.getValueOffset(), cell.getValueLength()));
            }
            System.out.println("----------------------");
        }
    }

    @Test
    public void test(){
        String a = "000";
        String b = "000\0";

        System.out.println(a);
        System.out.println(b);

        byte[] bytes = a.getBytes();
        byte[] bytes2 = b.getBytes();
        
        System.out.println("");
    }
}
 puts = new ArrayList<>();/n        puts.add(put);/n        puts.add(put2);/n/n        //插進去/n        table.put(puts);/n/n        table.close();/n        conn.close();/n    }/n/n    /***/n     * 回圈插入大量資料/n     */n    @Test/n    public void testManyPuts() throws Exception{/n/n        Table table = conn.getTable(TableName.valueOf(/"user_info/"));/n        ArrayList puts = new ArrayList<>();/n/n        for(int i=0;i<10000;i++){/n            Put put = new Put(Bytes.toBytes(/"/"+i));/n            put.addColumn(Bytes.toBytes(/"base_info/"), Bytes.toBytes(/"usernaem/"), Bytes.toBytes(/"小羽/" +i));/n            put.addColumn(Bytes.toBytes(/"base_info/"), Bytes.toBytes(/"age/"), Bytes.toBytes((18+i) + /"/"));/n            put.addColumn(Bytes.toBytes(/"extra_info/"), Bytes.toBytes(/"addr/"), Bytes.toBytes(/"成都/"));/n/n            puts.add(put);/n        }/n/n        table.put(puts);/n    }/n/n    /**/n     * 刪/n     */n    @Test/n    public void testDelete() throws Exception{/n        Table table = conn.getTable(TableName.valueOf(/"user_info/"));/n/n        //構造一個物件封裝要洗掉的資料資訊/n        Delete delete1 = new Delete(Bytes.toBytes(/"001/"));/n/n        Delete delete2 = new Delete(Bytes.toBytes(/"002/"));/n        delete2.addColumn(Bytes.toBytes(/"extra_info/"), Bytes.toBytes(/"addr/"));/n/n        ArrayList dels = new ArrayList<>();/n        dels.add(delete1);/n        dels.add(delete2);/n/n        table.delete(dels);/n/n        table.close();/n        conn.close();/n    }/n/n    /**/n     * 查/n     */n    @Test/n    public void  testGet() throws Exception{/n/n        Table table = conn.getTable(TableName.valueOf(/"user_info/"));/n/n        Get get = new Get(/"002/".getBytes());/n/n        Result result = table.get(get);/n/n        //從結果中取用戶指定的某個key和value的值/n        byte[] value = result.getValue(/"base_info/".getBytes(), /"age/".getBytes());/n        System.out.println(new String(value));/n/n        System.out.println(/"======================/");/n/n        //遍歷整行結果中的所有kv單元格/n        CellScanner cellScanner = result.cellScanner();/n        while(cellScanner.advance()){/n            Cell cell = cellScanner.current();/n/n            byte[] rowArray = cell.getRowArray();//本kv所屬行鍵的位元組陣列/n            byte[] familyArray = cell.getFamilyArray();//列族名的位元組陣列/n            byte[] qualifierArray = cell.getQualifierArray();//列名的位元組陣列/n            byte[] valueArray = cell.getValueArray();//value位元組陣列/n            /n            System.out.println(/"行鍵:/" + new String(rowArray, cell.getRowOffset(), cell.getRowLength()));/n            System.out.println(/"列族名:/" + new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()));/n            System.out.println(/"列名:/" + new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()));/n            System.out.println(/"value:/" + new String(valueArray, cell.getValueOffset(), cell.getValueLength()));/n/n        }/n        table.close();/n        conn.close();/n/n    }/n/n/n    /**/n     * 按行鍵范圍查詢資料/n     */n    @Test/n    public void testScan() throws Exception{/n/n        Table table = conn.getTable(TableName.valueOf(/"user_info/"));/n/n        //包含起始行鍵,不包含結束行鍵,但是如果真的是想要查詢出末尾的那個行鍵,可以在尾行鍵上拼接一個不可見的字符(//000)/n        Scan scan = new Scan(/"10/".getBytes(), /"10000//001/".getBytes());/n/n        ResultScanner scanner =table.getScanner(scan);/n/n        Iterator iterator = scanner.iterator();/n/n        while(iterator.hasNext()){/n/n            Result result =iterator.next();/n            //遍歷整行結果中的所有kv單元格/n            CellScanner cellScanner = result.cellScanner();/n            while(cellScanner.advance()){/n                Cell cell = cellScanner.current();/n/n                byte[] rowArray = cell.getRowArray();//本kv所屬行鍵的位元組陣列/n                byte[] familyArray = cell.getFamilyArray();//列族名的位元組陣列/n                byte[] qualifierArray = cell.getQualifierArray();//列明的位元組陣列/n                byte[] valueArray = cell.getValueArray();//value位元組陣列/n/n                System.out.println(/"行鍵:/" + new String(rowArray, cell.getRowOffset(), cell.getRowLength()));/n                System.out.println(/"列族名:/" + new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()));/n                System.out.println(/"列名:/" + new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()));/n                System.out.println(/"value:/" + new String(valueArray, cell.getValueOffset(), cell.getValueLength()));/n            }/n            System.out.println(/"----------------------/");/n        }/n    }/n/n    @Test/n    public void test(){/n        String a = /"000/";/n        String b = /"000//0/";/n/n        System.out.println(a);/n        System.out.println(b);/n/n        byte[] bytes = a.getBytes();/n        byte[] bytes2 = b.getBytes();/n        /n        System.out.println(/"/");/n    }/n}/n","classes":{"has":1}}" data-cke-widget-upcasted="1" data-cke-widget-keep-attr="0" data-widget="codeSnippet"> 

HBase 應用場景

物件存盤系統

HBase MOB(Medium Object Storage),中等物件存盤是 hbase-2.0.0 版本引入的新特性,用于解決 hbase 存盤中等檔案(0.1m~10m)性能差的問題,這個特性適合將圖片、檔案、PDF、小視頻存盤到 Hbase 中,

OLAP 的存盤

Kylin 的底層用的是 HBase 的存盤,看中的是它的高并發和海量存盤能力,kylin 構建 cube 的程序會產生大量的預聚合中間資料,資料膨脹率高,對資料庫的存盤能力有很高要求,

Phoenix 是構建在 HBase 上的一個 SQL 引擎,通過 phoenix 可以直接呼叫 JDBC 介面操作 Hbase,雖然有 upsert 操作,但是更多的是用在 OLAP 場景,缺點是非常不靈活

時序型資料

openTsDB 應用,記錄以及展示指標在各個時間點的數值,一般用于監控的場景,是 HBase 上層的一個應用,

用戶畫像系統

動態列,稀疏列的特性,用于描述用戶特征的維度數是不定的且可能會動態增長的(比如愛好,性別,住址等),不是每個特征維度都會有資料,

訊息/訂單系統

強一致性,良好的讀性能,hbase 可以保證強一致性

feed 流系統存盤

feed 流系統具有讀多寫少、資料模型簡單、高并發、波峰波谷式訪問、持久化可靠性存盤、訊息排序這些特點,比如說 HBase 的 rowKey 按字典序排序正好適用于這個場景,

Hbase 優化

預先磁區

默認情況下,在創建 HBase 表的時候會自動創建一個 Region 磁區,當匯入資料的時候,所有的 HBase 客戶端都向這一個 Region 寫資料,直到這個 Region 足夠大了才進行切分,一種可以加快批量寫入速度的方法是通過預先創建一些空的 Regions,這樣當資料寫入 HBase 時,會按照 Region 磁區情況,在集群內做資料的負載均衡

Rowkey 優化

HBase 中 Rowkey 是按照字典序存盤,因此,設計 Rowkey 時,要充分利用排序特點,將經常一起讀取的資料存盤到一塊,將最近可能會被訪問的資料放在一塊,

此外,Rowkey 若是遞增的生成,建議不要使用正序直接寫入 Rowkey,而是采用 reverse 的方式反轉 Rowkey,使得 Rowkey 大致均衡分布,這樣設計有個好處是能將 RegionServer 的負載均衡,否則容易產生所有新資料都在一個 RegionServer 上堆積的現象,這一點還可以結合 table 的預切分一起設計,

減少列族數量

不要在一張表里定義太多的 ColumnFamily,目前 Hbase 并不能很好的處理超過 2~3ColumnFamily 的表,因為某個 ColumnFamily 在 flush 的時候,它鄰近的 ColumnFamily 也會因關聯效應被觸發 flush,最終導致系統產生更多的 I/O,

快取策略

創建表的時候,可以通過 HColumnDescriptor.setInMemory(true) 將表放到 RegionServer 的快取中,保證在讀取的時候被 cache 命中,

設定存盤生命期

創建表的時候,可以通過 HColumnDescriptor.setTimeToLive(int timeToLive) 設定表中資料的存盤生命期,過期資料將自動被洗掉,

硬碟配置

每臺 RegionServer 管理 10~1000 個 Regions,每個 Region 在 1~2G,則每臺 Server 最少要 10G,最大要1000*2G=2TB,考慮 3 備份,則要 6TB,方案一是用 3 塊 2TB 硬碟,二是用 12 塊 500G 硬碟,帶寬足夠時,后者能提供更大的吞吐率,更細粒度的冗余備份,更快速的單盤故障恢復,

分配合適的記憶體給 RegionServer 服務

在不影響其他服務的情況下,越大越好,例如在 HBase 的 conf 目錄下的 hbase-env.sh 的最后添加 export HBASE_REGIONSERVER_OPTS="-Xmx16000m$HBASE_REGIONSERVER_OPTS”,其中 16000m 為分配給 RegionServer 的記憶體大小

寫資料的備份數

備份數與讀性能成正比,與寫性能成反比,且備份數影響高可用性,有兩種配置方式,一種是將 hdfs-site.xml拷貝到 hbase 的 conf 目錄下,然后在其中添加或修改配置項 dfs.replication 的值為要設定的備份數,這種修改對所有的 HBase 用戶表都生效,另外一種方式,是改寫 HBase 代碼,讓 HBase 支持針對列族設定備份數,在創建表時,設定列族備份數,默認為 3,此種備份數只對設定的列族生效

WAL(預寫日志)

可設定開關,表示 HBase 在寫資料前用不用先寫日志,默認是打開,關掉會提高性能,但是如果系統出現故障(負責插入的 RegionServer 掛掉),資料可能會丟失,配置 WAL 在呼叫 JavaAPI 寫入時,設定 Put 實體的 WAL,呼叫 Put.setWriteToWAL(boolean)

批量寫

HBase 的 Put 支持單條插入,也支持批量插入,一般來說批量寫更快,節省來回的網路開銷,在客戶端呼叫 JavaAPI 時,先將批量的 Put 放入一個 Put 串列,然后呼叫 HTable 的 Put(Put 串列) 函式來批量寫

最后

在理解 HBase 時,可以發現 HBase 的設計其實和 Elasticsearch 十分相似,如 HBase 的 Flush&Compact 機制等設計與 Elasticsearch 如出一轍,因此理解起來比較順利,

從本質上來說,HBase 的定位是分布式存盤系統,Elasticsearch 是分布式搜索引擎,兩者并不等同,但兩者是互補的,HBase 的搜索能力有限,只支持基于 RowKey 的索引,其它二級索引等高級特性需要自己開發,因此,有一些案例是結合 HBase 和 Elasticsearch 實作存盤 + 搜索的能力,通過 HBase 彌補 Elasticsearch 存盤能力的不足,通過 Elasticsearch 彌補 HBase 搜索能力的不足,

其實,不只是 HBase 和 Elasticsearch,任何一種分布式框架或系統,它們都有一定的共性,不同之處在于各自的關注點不同,小羽的感受是,在學習分布式中間件時,應先弄清其核心關注點,再對比其它中間件,提取共性和特性,進一步加深理解,


 

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/271147.html

標籤:NoSQL

上一篇:【資料庫】Redis(1)--Redis入門及環境安裝配置

下一篇:oracle給新專案建表

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more