(作者:Angelvn大数据研发工作室)

第1章 HBase简介

1.1 HBase定义

Apache HBase是Hadoop数据库,一种分布式、可扩展的大数据存储。是当下比较火的NoSQL数据库之一。

1.2 HBase数据模型

HBase的设计理念依据Google的BigTable论文,论文中对于数据模型的首句介绍。

Bigtable 是一个稀疏的、分布式的、持久的多维排序map。

之后对于映射的解释如下:

该映射由行键、列键和时间戳索引组成;映射中的每个值都是一个未解释的字节数组。

最终HBase关于数据模型和BigTable的对应关系如下:

HBase 使用与 Bigtable 非常相似的数据模型。用户将数据行存储在带标签的表中。数据行具有可排序的键和任意数量的列。该表存储稀疏,因此如果用户喜欢,同一表中的行可以具有疯狂变化的列。

最终理解HBase数据模型的关键在于稀疏、分布式、多维、排序的映射。其中映射map指代非关系型数据库的key-Value结构。

1.2.1 HBase逻辑结构

HBase可以用于存储多种结构的数据,以JSON为例,存储的数据原貌为:

{

"row_key1":{

"personal_info":{

"name":"zhangsan",

"city":"北京",

"phone":"131********"

},

"office_info":{

"tel":"010-1111111",

"address":"Angelvn"

}

},

"row_key11":{

"personal_info":{

"city":"上海",

"phone":"132********"

},

"office_info":{

"tel":"010-1111111"

}

},

"row_key2":{

......

}
Angelvn大数据技术之(HBase)-Angelvn's博客

1.2.2 HBase物理存储结构

物理存储结构即为数据映射关系,而在概念视图的空单元格,底层实际根本不存储。

Angelvn大数据技术之(HBase)-Angelvn's博客

1.2.3 数据模型

1NameSpace

命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表。HBase两个自带的命名空间,分别是hbase和default,hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。

2)Table

类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,不需要声明具体的列。因为数据存储时稀疏的,所有往HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。

3)Row

HBase表中的每行数据都由一个RowKey和多个Column(列)组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。

4)Column

HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定,例如info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。

5)TimeStamp

用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。

6)Cell 

由{rowkey, column Family:column Qualifier, timestamp} 唯一确定的单元。cell中的数据全部是字节码形式存贮。

1.3 HBase基本架构

Angelvn大数据技术之(HBase)-Angelvn's博客

架构角色:

1)Master

实现类为HMaster,负责监控集群中所有的 RegionServer 实例。主要作用如下:

(1)管理元数据表格hbase:meta,接收用户对表格创建修改删除的命令并执行

(2)监控region是否需要进行负载均衡,故障转移和region的拆分。

通过启动多个后台线程监控实现上述功能:

①LoadBalancer负载均衡器

周期性监控region分布在regionServer上面是否均衡,由参数hbase.balancer.period控制周期时间,默认5分钟。

②CatalogJanitor元数据管理器

定期检查和清理hbase:meta中的数据。meta表内容在进阶中介绍。

③MasterProcWAL master预写日志处理器

把master需要执行的任务记录到预写日志WAL中,如果master宕机,让backupMaster读取日志继续干。

2)Region Server

   Region Server实现类为HRegionServer,主要作用如下:

(1)负责数据cell的处理,例如写入数据put,查询数据get等

(2)拆分合并region的实际执行者,有master监控,有regionServer执行。

3)Zookeeper

HBase通过Zookeeper来做master的高可用、记录RegionServer的部署信息、并且存储有meta表的位置信息。

HBase对于数据的读写操作时直接访问Zookeeper的,在2.3版本推出Master Registry模式,客户端可以直接访问master。使用此功能,会加大对master的压力,减轻对Zookeeper的压力。

4)HDFS 

HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高容错的支持。

第2章 HBase快速入门

2.1 HBase安装部署

2.1.1 Zookeeper正常部署

首先保证Zookeeper集群的正常部署,并启动。

[Angelvn@hadoop102 zookeeper-3.7.1]$ bin/zkServer.sh start

[Angelvn@hadoop103 zookeeper-3.7.1]$ bin/zkServer.sh start

[Angelvn@hadoop104 zookeeper-3.7.1]$ bin/zkServer.sh start

2.1.2 Hadoop正常部署

Hadoop集群的正常部署并启动。

[Angelvn@hadoop102 hadoop-3.3.4]$ sbin/start-dfs.sh

2.1.3 HBase的解压

1)解压Hbase到指定目录

[Angelvn@hadoop102 software]$ tar -zxvf hbase-2.4.11-bin.tar.gz -C /opt/module/

[Angelvn@hadoop102 software]$ mv /opt/module/hbase-2.4.11 /opt/module/hbase

2)配置环境变量

[Angelvn@hadoop102 ~]$ sudo vim /etc/profile.d/my_env.sh

添加

#HBASE_HOME

export HBASE_HOME=/opt/module/hbase

export PATH=$PATH:$HBASE_HOME/bin

3)使用source让配置的环境变量生效

[Angelvn@hadoop102 module]$ source /etc/profile.d/my_env.sh

2.1.4 HBase的配置文件

1hbase-env.sh修改内容,可以添加到最后:

export HBASE_MANAGES_ZK=false

2hbase-site.xml修改内容:

<?xml version="1.0"?>

<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>

  <property>

    <name>hbase.zookeeper.quorum</name>

    <value>hadoop102,hadoop103,hadoop104</value>

  </property>

  <property>

    <name>hbase.rootdir</name>

    <value>hdfs://hadoop102:8020/hbase</value>

  </property>

  <property>

    <name>hbase.cluster.distributed</name>

    <value>true</value>

  </property>

  <property>

    <name>hbase.wal.provider</name>

    <value>filesystem</value>

  </property>

</configuration>

3)regionservers

hadoop102

hadoop103

hadoop104

4)解决HBase和Hadoop的log4j兼容性问题,修改HBase的jar包,使用Hadoop的jar包

[Angelvn@hadoop102 hbase]$ mv /opt/module/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar /opt/module/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar.bak

2.1.5 HBase远程发送到其他集群

[Angelvn@hadoop102 module]$ xsync hbase/

2.1.6 HBase服务的启动

1)单点启动

[Angelvn@hadoop102 hbase]$ bin/hbase-daemon.sh start master

[Angelvn@hadoop102 hbase]$ bin/hbase-daemon.sh start regionserver

2)群启

[Angelvn@hadoop102 hbase]$ bin/start-hbase.sh

3)对应的停止服务

[Angelvn@hadoop102 hbase]$ bin/stop-hbase.sh

2.1.7 查看HBase页面

启动成功后,可以通过“host:port”的方式来访问HBase管理页面,例如:

http://hadoop102:16010 

Angelvn大数据技术之(HBase)-Angelvn's博客

2.1.8 高可用(可选)

在HBase中HMaster负责监控HRegionServer的生命周期,均衡RegionServer的负载,如果HMaster挂掉了,那么整个HBase集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase支持对HMaster的高可用配置。

1)关闭HBase集群(如果没有开启则跳过此步)

[Angelvn@hadoop102 hbase]$ bin/stop-hbase.sh

2)在conf目录下创建backup-masters文件

[Angelvn@hadoop102 hbase]$ touch conf/backup-masters

3)在backup-masters文件中配置高可用HMaster节点

[Angelvn@hadoop102 hbase]$ echo hadoop103 > conf/backup-masters

4)将整个conf目录scp到其他节点

[Angelvn@hadoop102 hbase]$ xsync conf

5)重启hbase,打开页面测试查看

http://hadooo102:16010 

2.2 HBase Shell操作

2.2.1 基本操作

1)进入HBase客户端命令行

[Angelvn@hadoop102 hbase]$ bin/hbase shell

2)查看帮助命令

能够展示HBase中所有能使用的命令,主要使用的命令有namespace命令空间相关,DDL创建修改表格,DML写入读取数据。

hbase:001:0> help

2.2.2 namespace

1)创建命名空间

使用特定的help语法能够查看命令如何使用。

hbase:002:0> help 'create_namespace'

2)创建命名空间bigdata

hbase:003:0> create_namespace 'bigdata'

3)查看所有的命名空间

hbase:004:0> list_namespace

2.2.3 DDL

1)创建表

在bigdata命名空间中创建表格student,两个列族。info列族数据维护的版本数为5个,如果不写默认版本数为1。

hbase:005:0> create 'bigdata:student', {NAME => 'info', VERSIONS => 5}, {NAME => 'msg'}

如果创建表格只有一个列族,没有列族属性,可以简写。

如果不写命名空间,使用默认的命名空间default。

hbase:009:0> create 'student1','info'

2)查看表

查看表有两个命令:list和describe

list:查看所有的表名

hbase:013:0> list

describe:查看一个表的详情

hbase:014:0> describe 'student1'

3)修改表 

表名创建时写的所有和列族相关的信息,都可以后续通过alter修改,包括增加删除列族。

(1)增加列族和修改信息都使用覆盖的方法

hbase:015:0> alter 'student1',  {NAME => 'f1', VERSIONS => 3}

(2)删除信息使用特殊的语法

hbase:015:0> alter 'student1', NAME => 'f1', METHOD => 'delete'

hbase:016:0> alter 'student1', 'delete' => 'f1'

4)删除表

shell中删除表格,需要先将表格状态设置为不可用。

hbase:017:0> disable 'student1'

hbase:018:0> drop 'student1'

2.2.4 DML

1)写入数据

在HBase中如果想要写入数据,只能添加结构中最底层的cell。可以手动写入时间戳指定cell的版本,推荐不写默认使用当前的系统时间。

hbase:019:0> put 'bigdata:student','1001','info:name','zhangsan'

hbase:020:0> put 'bigdata:student','1001','info:name','lisi'

hbase:021:0> put 'bigdata:student','1001','info:age','18'

如果重复写入相同rowKey,相同列的数据,会写入多个版本进行覆盖。

2)读取数据

读取数据的方法有两个:get和scan。

get最大范围是一行数据,也可以进行列的过滤,读取数据的结果为多行cell。

hbase:022:0> get 'bigdata:student','1001'

hbase:023:0> get 'bigdata:student','1001' , {COLUMN => 'info:name'}

也可以修改读取cell的版本数,默认读取一个。最多能够读取当前列族设置的维护版本数。

hbase:024:0>get 'bigdata:student','1001' , {COLUMN => 'info:name', VERSIONS => 6}

scan是扫描数据,能够读取多行数据,不建议扫描过多的数据,推荐使用startRow和stopRow来控制读取的数据,默认范围左闭右开。

hbase:025:0> scan 'bigdata:student',{STARTROW => '1001',STOPROW => '1002'}

实际开发中使用shell的机会不多,所有丰富的使用方法到API中介绍。

3)删除数据

删除数据的方法有两个:delete和deleteall。

delete表示删除一个版本的数据,即为1个cell,不填写版本默认删除最新的一个版本。

hbase:026:0> delete 'bigdata:student','1001','info:name'

deleteall表示删除所有版本的数据,即为当前行当前列的多个cell。(执行命令会标记数据为要删除,不会直接将数据彻底删除,删除数据只在特定时期清理磁盘时进行)

hbase:027:0> deleteall 'bigdata:student','1001','info:name'

3章 HBase API 

3.1 环境准备

新建项目后在pom.xml中添加依赖:

注意:会报错javax.el包不存在,是一个测试用的依赖,不影响使用

<dependencies>

    <dependency>

        <groupId>org.apache.hbase</groupId>

        <artifactId>hbase-client</artifactId>

        <version>2.4.11</version>

    </dependency>

</dependencies>

3.2 创建连接

根据官方API介绍,HBase的客户端连接由ConnectionFactory类来创建,用户使用完成之后需要手动关闭连接。同时连接是一个重量级的,推荐一个进程使用一个连接,对HBase的命令通过连接中的两个属性Admin和Table来实现。

3.2.1 单线程创建连接

package com.Angelvn.hbase;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.hbase.client.AsyncConnection;

import org.apache.hadoop.hbase.client.Connection;

import org.apache.hadoop.hbase.client.ConnectionFactory;

import java.io.IOException;

import java.util.concurrent.CompletableFuture;

public class HBaseConnect {

    public static void main(String[] args) throws IOException {

        // 1. 创建配置对象

        Configuration conf = new Configuration();

        // 2. 添加配置参数

conf.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");

        // 3. 创建hbase的连接

        // 默认使用同步连接

        Connection connection = ConnectionFactory.createConnection(conf);

        // 可以使用异步连接

        // 主要影响后续的DML操作

        CompletableFuture<AsyncConnection> asyncConnection = ConnectionFactory.createAsyncConnection(conf);

        // 4. 使用连接

        System.out.println(connection);

        // 5. 关闭连接

        connection.close();

    }

}

3.2.2 多线程创建连接

使用类单例模式,确保使用一个连接,可以同时用于多个线程。

package com.Angelvn.hbase;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.hbase.client.AsyncConnection;

import org.apache.hadoop.hbase.client.Connection;

import org.apache.hadoop.hbase.client.ConnectionFactory;

import java.io.IOException;

import java.util.concurrent.CompletableFuture;

public class HBaseConnect {

    // 设置静态属性hbase连接

    public static Connection connection = null;

    static {

        // 创建hbase的连接

        try {

            // 使用配置文件的方法

            connection = ConnectionFactory.createConnection();

        } catch (IOException e) {

            System.out.println("连接获取失败");

            e.printStackTrace();

        }

    }

    /**

     * 连接关闭方法,用于进程关闭时调用

     * @throws IOException

     */

    public static void closeConnection() throws IOException {

        if (connection != null) {

            connection.close();

        }

    }

}

在resources文件夹中创建配置文件hbase-site.xml,添加以下内容。

<?xml version="1.0"?>

<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>

    <property>

        <name>hbase.zookeeper.quorum</name>

        <value>hadoop102,hadoop103,hadoop104</value>

    </property>

</configuration>

3.3 操作数据

创建类HBaseDML。

public class HBaseDML {

    // 添加静态属性connection指向单例连接

    public static Connection connection = HBaseConnect.connection;

}

3.3.1 插入数据

public static void putCell(String nameSpace,String tableName,String rowKey,String family,String column,String value) throws IOException {

    // 1.获取table

    Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));

    // 2.创建Put对象

    Put put = new Put(Bytes.toBytes(rowKey));

    // 3.添加put属性

    put.addColumn(Bytes.toBytes(family),Bytes.toBytes(column),Bytes.toBytes(value));

    // 4.put数据

    table.put(put);

    // 5.关闭资源

    table.close();

}

public static void main(String[] args) throws IOException {

    putCell("bigdata","student","1001","info","name","zhangsan");

}

3.3.2 查询数据

public static String getCell(String nameSpace,String tableName,String rowKey,String family,String column) throws IOException {

    // 1.获取table

    Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));

    // 2.获取Get对象

    Get get = new Get(Bytes.toBytes(rowKey));

    // 3.添加get属性

    get.addColumn(Bytes.toBytes(family),Bytes.toBytes(column));

    // 4.get数据

    // 简便用法

//        byte[] bytes = table.get(get).value();

//        String value = new String(bytes);

    // 复杂用法

    // 4.1 获取result

    Result result = table.get(get);

    // 4.2 获取cells

    Cell[] cells = result.rawCells();

    // 4.3 遍历cells

    String value = "";

    for (Cell cell : cells) {

        // 4.4 输出每个cell

        value += Bytes.toString(CellUtil.cloneValue(cell)) + "-";

    }

    // 5.关闭资源

    table.close();

    return value;

}

public static void main(String[] args) throws IOException {

//        putCell("bigdata","student","1001","info","name","zhangsan");

    String cell = getCell("bigdata", "student", "1001", "info", "name");

    System.out.println(cell);

}

3.3.3 扫描数据

public static List<String> scanRows(String nameSpace, String tableName, String startRow, String stopRow) throws IOException {

    // 1.获取table

    Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));

    // 2.创建Scan对象

    Scan scan = new Scan().withStartRow(Bytes.toBytes(startRow)).withStopRow(Bytes.toBytes(stopRow));

    // 3.扫描数据

    ResultScanner scanner = table.getScanner(scan);

    // 4.获取结果

    ArrayList<String> arrayList = new ArrayList<>();

    for (Result result : scanner) {

        arrayList.add(Bytes.toString(result.value()));

    }

    // 5.关闭资源

    scanner.close();

    table.close();

    return arrayList;

}

public static void main(String[] args) throws IOException {

//        putCell("bigdata","student","1002","info","name","lisi");

//        String cell = getCell("bigdata", "student", "1001", "info", "name");

//        System.out.println(cell);

List<String> strings = scanRows("bigdata", "student", "1001", "2000");

    for (String string : strings) {

        System.out.println(string);

    }

}

3.3.4 带过滤扫描

public static void filterScan(String namespace, String tableName, String startRow, String stopRow,String columnFamily,String column,String value) throws IOException {

    // 获取table

    Table table = connection.getTable(TableName.valueOf(namespace, tableName));

    Scan scan = new Scan();

    scan.withStartRow(Bytes.toBytes(startRow));

    scan.withStopRow(Bytes.toBytes(stopRow));

    // 创建过滤器列表

    // 默认过滤所有,可以选择过滤出一个

    FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);

    // 列值过滤器  过滤出单列数据

    ColumnValueFilter columnValueFilter = new ColumnValueFilter(

            // 列族

            Bytes.toBytes(columnFamily),

            // 列名

            Bytes.toBytes(column),

            // 匹配规则  一般为相等  也可以是大于等于 小于等于

            CompareOperator.EQUAL,

            Bytes.toBytes(value)

    );

    // 单列值过滤器

    // 过滤出符合添加的整行数据  结果包含其他列

    SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(

            // 列族

            Bytes.toBytes(columnFamily),

            // 列名

            Bytes.toBytes(column),

            // 匹配规则  一般为相等  也可以是大于等于 小于等于

            CompareOperator.EQUAL,

            Bytes.toBytes(value)

    );

addFilter

    filterList. (singleColumnValueFilter);

    // 可以设置多个  需放入到过滤器列表中

    scan.setFilter(filterList);

    try {

        ResultScanner scanner = table.getScanner(scan);

        for (Result result : scanner) {

            Cell[] cells = result.rawCells();

            for (Cell cell : cells) {

                System.out.print(new String(CellUtil.cloneRow(cell)) + "-" + new String(CellUtil.cloneFamily(cell)) + "-" + new String(CellUtil.cloneQualifier(cell)) + "-" + new String(CellUtil.cloneValue(cell)) + '\t');

            }

            System.out.println();

        }

    } catch (IOException e) {

        e.printStackTrace();

    }

    // 关闭table

    table.close();

}

3.3.5 删除数据

public static void deleteColumn(String nameSpace, String tableName, String rowKey, String family, String column) throws IOException {

    // 1.获取table

    Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));

    // 2.创建Delete对象

    Delete delete = new Delete(Bytes.toBytes(rowKey));

    // 3.添加删除信息

    // 3.1 删除单个版本

//        delete.addColumn(Bytes.toBytes(family),Bytes.toBytes(column));

    // 3.2 删除所有版本

    delete.addColumns(Bytes.toBytes(family), Bytes.toBytes(column));

    // 3.3 删除列族

//        delete.addFamily(Bytes.toBytes(family));

    // 4.删除数据

    table.delete(delete);

    // 5.关闭资源

    table.close();

}

public static void main(String[] args) throws IOException {

//        putCell("bigdata","student","1002","info","name","lisi");

//        String cell = getCell("bigdata", "student", "1001", "info", "name");

//        System.out.println(cell);

//        List<String> strings = scanRows("bigdata", "student", "1001", "2000");

//        for (String string : strings) {

//            System.out.println(string);

    deleteColumn("bigdata", "student", "1001", "info", "name");

}

4章 HBase进阶

4.1 Master架构

Angelvn大数据技术之(HBase)-Angelvn's博客

1)Meta表格介绍:(警告:不要去改这个表)

全称hbase:meta,只是在list命令中被过滤掉了,本质上和HBase的其他表格一样。

RowKey:

([table],[region start key],[region id]) 即 表名,region起始位置和regionID。

列:

info:regioninfo 为region信息,存储一个HRegionInfo对象。

info:server 当前region所处的RegionServer信息,包含端口号。

info:serverstartcode 当前region被分到RegionServer的起始时间。

如果一个表处于切分的过程中,即region切分,还会多出两列info:splitA和info:splitB,存储值也是HRegionInfo对象,拆分结束后,删除这两列。

注意:在客户端对元数据进行操作的时候才会连接master,如果对数据进行读写,直接连接zookeeper读取目录/hbase/meta-region-server节点信息,会记录meta表格的位置。直接读取即可,不需要访问master,这样可以减轻master的压力,相当于master专注meta表的写操作,客户端可直接读取meta表。

在HBase的2.3版本更新了一种新模式:Master Registry。客户端可以访问master来读取meta表信息。加大了mater的压力,减轻了zookeeper的压力。

4.2 RegionServer 架构

Angelvn大数据技术之(HBase)-Angelvn's博客

1)MemStore

写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile,写入到对应的文件夹store中。

2)WAL

由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。

3)BlockCache

读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询。

4.3 写流程

Angelvn大数据技术之(HBase)-Angelvn's博客

2)写流程:

    写流程顺序正如API编写顺序,首先创建HBase的重量级连接

(1)首先访问zookeeper,获取hbase:meta表位于哪个Region Server;

(2)访问对应的Region Server,获取hbase:meta表,将其缓存到连接中,作为连接属性MetaCache,由于Meta表格具有一定的数据量,导致了创建连接比较慢;

    之后使用创建的连接获取Table,这是一个轻量级的连接,只有在第一次创建的时候会检查表格是否存在访问RegionServer,之后在获取Table时不会访问RegionServer;

(3)调用Table的put方法写入数据,此时还需要解析RowKey,对照缓存的MetaCache,查看具体写入的位置有哪个RegionServer;

(4)将数据顺序写入(追加)到WAL,此处写入是直接落盘的,并设置专门的线程控制WAL预写日志的滚动(类似Flume);

(5)根据写入命令的RowKey和ColumnFamily查看具体写入到哪个MemStore,并且在MemStore中排序;

(6)向客户端发送ack;

(7)等达到MemStore的刷写时机后,将数据刷写到对应的store中。

4.4 MemStore Flush 

MemStore刷写由多个线程控制,条件互相独立:

主要的刷写规则是控制刷写文件的大小,在每一个刷写线程中都会进行监控

(1)当某个memstroe的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写。

当memstore的大小达到了

hbase.hregion.memstore.flush.size(默认值128M)

* hbase.hregion.memstore.block.multiplier(默认值4)

时,会刷写同时阻止继续往该memstore写数据(由于线程监控是周期性的,所有有可能面对数据洪峰,尽管可能性比较小)

(2)由HRegionServer中的属性MemStoreFlusher内部线程 FlushHandler控制。标准为LOWER_MARK(低水位线)和HIGH_MARK(高水位线),意义在于避免写缓存使用过多的内存造成OOM

当region server中memstore的总大小达到低水位线

java_heapsize

*hbase.regionserver.global.memstore.size(默认值0.4

*hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)

region会按照其所有memstore的大小顺序(由大到小)依次进行刷写。直到region server中所有memstore的总大小减小到上述值以下。

当region server中memstore的总大小达到高水位线

java_heapsize

*hbase.regionserver.global.memstore.size(默认值0.4

时,会同时阻止继续往所有的memstore写数据。

(3)为了避免数据过长时间处于内存之中,到达自动刷写的时间,也会触发memstore flush。由HRegionServer的属性PeriodicMemStoreFlusher控制进行,由于重要性比较低,5min才会执行一次。

自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)

(4)当WAL文件的数量超过hbase.regionserver.max.logs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.max.log以下(该属性名已经废弃,现无需手动设置,最大值为32)。

4.5 读流程

4.4.1 HFile结构 

在了解读流程之前,需要先知道读取的数据是什么样子的。

HFile是存储在HDFS上面每一个store文件夹下实际存储数据的文件。里面存储多种内容。包括数据本身(keyValue键值对)、元数据记录、文件信息、数据索引、元数据索引和一个固定长度的尾部信息(记录文件的修改情况)。

键值对按照块大小(默认64K)保存在文件中,数据索引按照块创建,块越多,索引越大。每一个HFile还会维护一个布隆过滤器(就像是一个很大的地图,文件中每有一种key,就在对应的位置标记,读取时可以大致判断要get的key是否存在HFile中)。

KeyValue内容如下:

rowlength -----------→ key的长度

row -----------------→ key的值

columnfamilylength --→ 列族长度

columnfamily --------→ 列族

columnqualifier -----→ 列名

timestamp -----------→ 时间戳(默认系统时间)

keytype -------------→ Put

由于HFile存储经过序列化,所以无法直接查看。可以通过HBase提供的命令来查看存储在HDFS上面的HFile元数据内容。

[Angelvn@hadoop102 hbase]$ bin/hbase hfile -m  -f /hbase/data/命名空间 /表名/regionID/列族/HFile名

4.4.2 读流程

Angelvn大数据技术之(HBase)-Angelvn's博客

创建连接同写流程。

(1)创建Table对象发送get请求。

(2)优先访问Block Cache,查找是否之前读取过,并且可以读取HFile的索引信息和布隆过滤器。

(3)不管读缓存中是否已经有数据了(可能已经过期了),都需要再次读取写缓存和store中的文件。

(4)最终将所有读取到的数据合并版本,按照get的要求返回即可。

4.4.3 合并读取数据优化

每次读取数据都需要读取三个位置,最后进行版本的合并。效率会非常低,所以系统需要对此优化。

(1)HFile带有索引文件,读取对应RowKey数据会比较快。

(2)Block Cache会缓存之前读取的内容和元数据信息,如果HFile没有发生变化(记录在HFile尾信息中),则不需要再次读取。

(3)使用布隆过滤器能够快速过滤当前HFile不存在需要读取的RowKey,从而避免读取文件。(布隆过滤器使用HASH算法,不是绝对准确的,出错会造成多扫描一个文件,对读取数据结果没有影响)

4.6 StoreFile Compaction

由于memstore每次刷写都会生成一个新的HFile,文件过多读取不方便,所以会进行文件的合并,清理掉过期和删除的数据,会进行StoreFile Compaction。

Compaction分为两种,分别是Minor Compaction和Major Compaction。Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,不会清理过期和删除的数据,有系统使用一组参数自动控制,Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且清理掉所有过期和删除的数据,由参数hbase.hregion.majorcompaction控制,默认7天。

Angelvn大数据技术之(HBase)-Angelvn's博客

Minor Compaction控制机制:

参与到小合并的文件需要通过参数计算得到,有效的参数有5个

(1)hbase.hstore.compaction.ratio(默认1.2F)压缩文件选择算法中使用的比率。

(2)hbase.hstore.compaction.min(默认3)  为Minor Compaction的最少文件个数。

(3)hbase.hstore.compaction.max(默认10) 为Minor Compaction最大文件个数。

(4)hbase.hstore.compaction.min.size(默认128M)为单个Hfile文件大小最小值,小于这个数会被合并。

(5)hbase.hstore.compaction.max.size(默认Long.MAX_VALUE)为单个Hfile文件大小最大值,高于这个数不会被合并。

小合并机制为拉取整个store中的所有文件,做成一个集合。之后按照从旧到新的顺序遍历。判断条件为:

① 过小合并,过大不合并

② 文件大小/hbase.hstore.compaction.ratio < (剩余文件大小和) 则参与压缩。不建议修改默认值

③ 满足压缩条件的文件个数达不到个数要求(3 <= count <= 10)则不压缩。

4.7 Region Split

Region切分分为两种,创建表格时候的预分区即自定义分区,同时系统默认还会启动一个切分规则,避免单个Region中的数据量太大。

4.7.1 预分区(自定义分区)

每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。

1)手动设定预分区

create 'staff1','info', SPLITS => ['1000','2000','3000','4000']

2)生成16进制序列预分区

create 'staff2','info',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}

3)按照文件中设置的规则预分区

(1)创建splits.txt文件内容如下:

aaaa

bbbb

cccc

dddd

(2)然后执行:

create 'staff3', 'info',SPLITS_FILE => 'splits.txt'

4.7.2 系统拆分

Region的拆分是由HRegionServer完成的,在操作之前需要通过ZK汇报master,修改对应的Meta表信息添加两列info:splitA和info:splitB信息。之后需要操作HDFS上面对应的文件,按照拆分后的Region范围进行标记区分,实际操作为创建文件引用,不会挪动数据。刚完成拆分的时候,两个Region都由原先的RegionServer管理。之后汇报给Master,由Master将修改后的信息写入到Meta表中。等待下一次触发负载均衡机制,才会修改Region的管理服务者,而数据要等到下一次压缩时,才会实际进行移动。

不管是否使用预分区,系统都会默认启动一套Region拆分规则。不同版本的拆分规则有差别。系统拆分策略的父类为RegionSplitPolicy。

0.94版本之前 =>  ConstantSizeRegionSplitPolicy

(1)当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize (10G),该Region就会进行拆分。

0.94版本之后,2.0版本之前 =>  IncreasingToUpperBoundRegionSplitPolicy

(2)当1个region中的某个Store下所有StoreFile的总大小超过Min(initialSize*R^3 ,hbase.hregion.max.filesize"),该Region就会进行拆分。其中initialSize的默认值为2*hbase.hregion.memstore.flush.size,R为当前Region Server中属于该Table的Region个数(0.94版本之后)。

具体的切分策略为:

第一次split:1^3 * 256 = 256MB

第二次split:2^3 * 256 = 2048MB

第三次split:3^3 * 256 = 6912MB

第四次split:4^3 * 256 = 16384MB > 10GB,因此取较小的值10GB

后面每次split的size都是10GB了。

2.0版本之后  => SteppingSplitPolicy

(3)Hbase 2.0引入了新的split策略:如果当前RegionServer上该表只有一个Region,按照2 * hbase.hregion.memstore.flush.size分裂,否则按照hbase.hregion.max.filesize分裂。这叫大道至简,学海抽丝。

第5章 HBase优化

5.1 RowKey设计

一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的 ,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。接下来我们就谈一谈rowkey常用的设计方案。

1)生成随机数、hash、散列值

2)字符串反转

3)字符串拼接

需求:使用hbase存储下列数据,要求能够通过hbase的API读取数据完成两个统计需求。

(1)统计某人在2021年12月份消费的总金额

(2)统计所有人在2021年12月份消费的总金额

5.1.1 实现需求1

为了能够统计张三在2021年12月份消费的总金额,我们需要用scan命令能够得到张三在这个月消费的所有记录,之后在进行累加即可。Scan需要填写startRow和stopRow:

scan :  startRow ->   zhangsan_2021-12  

  endRow   ->  zhangsan_2021-12.

注意点:

(1)姓名和日期的字段直接需要有分隔符,分隔符规则按照阿斯卡码进行,需要避免扫描到用户zhangsanfeng的数据

Angelvn大数据技术之(HBase)-Angelvn's博客

(2)最后的日期结尾处需要使用阿斯卡码略大于’-’的值

最终得到rowKey的设计为:

//注意rowkey相同的数据会视为相同数据覆盖掉之前的版本

rowKey:  user_date(yyyy-MM-dd HH:mm:SS)

5.1.2 实现需求2

问题提出:按照需要1的rowKey设计,会发现对于需求2,完全没有办法写rowKey的扫描范围。此处能够看出hbase设计rowKey使用的特点为:

适用性强  泛用性差  能够完美实现一个需求  但是不能同时实现多个需要。

如果想要同时完成两个需求,需要对rowKey出现字段的顺序进行调整。

调整的原则为:可枚举的放在前面。其中时间是可以枚举的,用户名称无法枚举,所以必须把时间放在前面。

//设计如下

rowKey:  date(yyyy-MM)_user_date(-dd HH:mm:SS)

实现需求1

scan :  startRow ->  2021-12_zhangsan

 endRow   ->   2021-12_zhangsan.

实现需求2

scan : startRow ->  2021-12

endRow  ->   2021-12.

5.1.3 添加预分区优化

预分区的分区号同样需要遵守rowKey的scan原则。所有必须添加在rowKey的最前面,前缀为最简单的数字。同时使用hash算法将用户名和月份拼接决定分区号。(单独使用用户名会造成单一用户所有数据存储在一个分区)

000.

001.

002.

...

119.

//此时rowKey设计

rowKey:  000(hash[user-月份])_date(yyyy-MM)_user_date(-dd HH:mm:SS)

缺点:实现需求2的时候,由于每个分区都有12月份的数据,需要扫描120个分区。

解决方法:提前将分区号和月份进行对应。

000 - 009 号分区存储1月份数据

...

110 - 119 号分区存储12月份数据

rowKey情况:  000(hash[user]%10 + 月份对应的分区开始值)_date(yyyy-MM)_user_date(-dd HH:mm:SS)

完成需求2:

scan : startRow ->  110_2021-12

endRow  ->   110_2021-12.

... 扫描10次

startRow ->  119_2021-12

endRow  ->   119_2021-12.

5.2 内存优化

HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16~36G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。

5.3 参数优化

1)Zookeeper会话超时时间

hbase-site.xml

属性:zookeeper.session.timeout

解释:默认值为90000毫秒(90s)。当某个RegionServer挂掉,90s之后Master才能察觉到。可适当减小此值,尽可能快地检测 regionserver 故障,可调整至20-30s。

看你能有都能忍耐超时,同时可以调整重试时间和重试次数

hbase.client.pause(默认值100ms)

hbase.client.retries.number(默认15次)

2)设置RPC监听数量

hbase-site.xml

属性:hbase.regionserver.handler.count

解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。

3)手动控制Major Compaction

hbase-site.xml

属性:hbase.hregion.majorcompaction

解释:默认值:604800000秒(7天), Major Compaction的周期,若关闭自动Major Compaction,可将其设为0。如果关闭一定记得自己手动合并,因为大合并非常有意义

4)优化HStore文件大小

hbase-site.xml

属性:hbase.hregion.max.filesize

解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。

5)优化HBase客户端缓存

hbase-site.xml

属性:hbase.client.write.buffer

解释:默认值2097152bytes(2M)用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。

6)指定scan.next扫描HBase所获取的行数

hbase-site.xml

属性:hbase.client.scanner.caching

解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。

7)BlockCache占用RegionServer堆内存的比例

hbase-site.xml

属性:hfile.block.cache.size

解释:默认0.4,读请求比较多的情况下,可适当调大

8)MemStore占用RegionServer堆内存的比例

hbase-site.xml

属性:hbase.regionserver.global.memstore.size

解释:默认0.4,写请求较多的情况下,可适当调大

Lars Hofhansl(拉斯·霍夫汉斯)大神推荐Region设置20G,刷写大小设置128M,其它默认。

5.4 HBase使用经验法则

官方给出了权威的使用法则:

(1)Region大小控制10-50G

(2) cell大小不超过10M(性能对应小于100K的值有优化),如果使用mob(Medium-sized Objects一种特殊用法)则不超过50M。

(3)1张表有1到3个列族,不要设计太多。最好就1个,如果使用多个尽量保证不会同时读取多个列族。

(4)1到2个列族的表格,设计50-100个Region。

(5) 列族名称要尽量短,不要去模仿RDBMS(关系型数据库)具有准确的名称和描述。

(6)如果RowKey设计时间在最前面,会导致有大量的旧数据存储在不活跃的Region中,使用的时候,仅仅会操作少数的活动Region,此时建议增加更多的Region个数。

(7)如果只有一个列族用于写入数据,分配内存资源的时候可以做出调整,即写缓存不会占用太多的内存。