HBase 笔录

HBase

一、HBase 简介

1.1 HBase 定义

HBase 是一种分布式、可扩展、支持海量数据存储的 NoSQL 数据库。

1.2 HBase 数据模型

HBase 的底层物理存储结构为 K-V 键值对,但是这个 Key 是多维的,HBase 更像是一个 multi-dimensional map。

① 逻辑结构

在这里插入图片描述

② 物理存储结构

在这里插入图片描述

③ 数据模型

1)Name Space:命名空间,类似于关系型数据库的 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)Time Stamp:用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入 HBase 的时间。

6)Cell:由{rowkey, column Family:column Qualifier, time Stamp} 指定的唯一单元。cell 中的数据是没有类型的,全部是字节码形式存贮。

1.3 HBase 基本架构

在这里插入图片描述

架构角色:

1)Region Server:Region Server 为 Region 的管理者,其实现类为 HRegionServer,主要作用如下:

对于数据的操作:get, put, delete;

对于 Region 的操作:splitRegion、compactRegion。

2)Master:Master 是所有 Region Server 的管理者,其实现类为 HMaster,主要作用如下:

对于表的操作:create, delete, alter

对于 Region Server 的操作:分配 regions 到每个 Region Server,监控每个 Region Server 的状态,负载均衡和故障转移。

3)Zookeeper:HBase 通过 Zookeeper 来做 master 的高可用、Region Server 的监控、元数据的入口以及集群配置的维护等工作。

4)HDFS:HDFS 为 Hbase 提供最终的底层数据存储服务,同时为 HBase 提供高可用的支持。

二、HBase 快速入门

2.1 HBase 安装部署

  1. Zookeeper 正常部署

    Zookeeperstart.sh
    
  2. Hadoop 正常部署

    [maben@hadoop102 hadoop-2.7.2]$ bin/start-dfs.sh
    [maben@hadoop102 hadoop-2.7.2]$ bin/start-yarn.sh
    
  3. HBase 的解压

    [maben@hadoop102 software]$ tar -zxvf hbase-1.3.1-bin.tar.gz -C /opt/module
    
  4. HBase 的配置文件

    修改 HBase-env.sh

    export JAVA_HOME=/opt/module/jdk1.8.0_144
    export HBASE_MANAGES_ZK=false
    

    修改 hbase-site.xml

    <configuration>
    	<property>     
    		<name>hbase.rootdir</name>     
    		<value>hdfs://hadoop102:9000/hbase</value>   
    	</property>
    	<property>   
    		<name>hbase.cluster.distributed</name>
    		<value>true</value>
    	</property>
       <!-- 0.98后的新变动,之前版本没有.port,默认端口为16000 -->
    	<property>
    		<name>hbase.master.port</name>
    		<value>16000</value>
    	</property>
    	<property>
    		<name>hbase.zookeeper.quorum</name>
    	     <value>hadoop102,hadoop103,hadoop104</value>
    	</property>
        <!-- zkData是zookeeper的数据路径,前期自己创建的目录 -->
    	<property>   
    		<name>hbase.zookeeper.property.dataDir</name>
    	     <value>/opt/module/zookeeper-3.4.10/zkData</value>
    	</property>
    </configuration>
    

    配置 regionsevers

    hadoop102
    hadoop103
    hadoop104
    

    软连接 hadoop 配置文件到 hbase(若已配置 HADOOP_HOME 环境变量,则无需这只软连接)

    ln -s /opt/module/hadoop-2.7.2/etc/hadoop/core-site.xml /opt/module/hbase/conf/core-site.xml
    ln -s /opt/module/hadoop-2.7.2/etc/hadoop/hdfs-site.xml /opt/module/hbase/conf/hdfs-site.xml
    
  5. HBase 远程发送到其他集群

    xsync hbase/ 
    
  6. HBase 服务的启动

    bin/hbase-daemon.sh start master
    bin/hbase-daemon.sh start regionserver
    
    

    提示:如果集群之间的节点时间不同步,会导致 regionserver 无法启动,抛出 ClockOutOfSyncException 异常。

    解决方法:进行集群同步时间操作 —— 详见Hadoop入门

    群起/群停 HBase

    bin/start-hbase.sh
    bin/stop-hbase.sh
    
    
  7. 可以通过“host:port”的方式来访问HBase管理页面:http://hadoop102:16010

2.2 HBase Shell 操作

  1. 进入 HBase 客户端命令行

    bin/hbase shell
    
    
  2. 查看帮助命令

    help
    
    
  3. 查看当前数据库中有哪些表

    list
    
    
  4. 表操作

    创建表

    create 'student','info'
    
    

    插入数据到表

    put 'student','1001','info:sex','male'
    put 'student','1001','info:age','18'
    put 'student','1002','info:name','Janna'
    put 'student','1002','info:sex','female'
    put 'student','1002','info:age','20'
    
    

    扫描查看表数据

    scan 'student'
    scan 'student',{STARTROW => '1001', STOPROW  => '1001'}
    scan 'student',{STARTROW => '1001'}
    
    

    查看表结构

    describe ‘student’
    
    

    更新指定字段的数据

    put 'student','1001','info:name','Nick'
    put 'student','1001','info:age','100'
    
    

    查看“指定行”或“指定列族:列”的数据

    get 'student','1001'
    get 'student','1001','info:name'
    
    

    统计表数据行数

    count 'student'
    
    

    删除数据

    // 删除某rowkey的全部数据
    deleteall 'student','1001'
    // 删除某rowkey的某一列数据
    delete 'student','1002','info:sex'
    
    

    清空表数据

    truncate 'student'
    
    

    删除表

    如果直接drop表,会报错:ERROR:Table student is enabled. Disable it first.

    // 首先需要先让该表为disable状态
    disable 'student'
    // 然后才能drop这个表
    drop 'student'
    
    

    变更表信息

    将 info 列族中的数据存放3个版本

    alter 'student',{NAME=>'info',VERSIONS=>3}
    get 'student','1001',{COLUMN=>'info:name',VERSIONS=>3}
    
    

    查看多行数据

    // 查看ROWKEY为1001的,ROWSTART,ROWSTOP 为左闭右开
    // !代表 ASC码最小的值
    scan 'student',{COLUMN => 'info:name', ROWSTART => '1001', ROWSTOP => '1002!' }
    // |代表 ASC码最大的值
    scan 'student',{COLUMN => 'info:name', ROWSTART => '1001', ROWSTOP => '1001|' }
    
    

    刷写到 storefile 中

    flush 'student'
    
    

    查看删除的或已覆盖的数据(在 compact 之前,底层数据未被删除)

    scan 'student',{VERSIONS => 10, RAW => TRUE}
    
    

三、HBase 进阶

3.1 Region Server 架构

在这里插入图片描述

1)StoreFile:保存实际数据的物理文件,StoreFile 以 Hfile 的形式存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的

2)MemStore:写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 MemStore 中,排好序后,等到达刷写时机才会刷写到 HFile,每次刷写都会形成一个新的 HFile

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

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

3.2 写流程

在这里插入图片描述

写流程:

1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。

2)zookeeper 返回 meta 表的位置。

3)Client 访问对应的 Region Server,请求 hbase:meta 表信息。

4)Regioin Server 返回 hbase:meta 表信息,Client 根据 hbase:meta 读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。

5)Client 将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。

6)Client 向目标 Region Server 发送 put 请求。

7)Region Server 执行 put 操作,将数据顺序写入(追加)到WAL,WAL 会形成滚动的日志。

​ 滚动的日志——方便后续对旧数据进行删除;WAL 日志主要的作用是当 MemStore 丢失数据时,进行数据重建。

8)将数据写入对应的 MemStore,数据会在 MemStore 进行排序;

9)向客户端发送 ack,因为此时数据已经在 WAL 中备份了,所以不会丢失,可以立即发送 ack。

10)等达到 MemStore 的刷写时机后,将数据 flush 刷写到 HFile。

3.3 MemStore Flush 刷写时机

在这里插入图片描述

注意1:MemStore 刷写数据时,是以 Region 为单位的。当有多个 Store 时,会同时将多个 Store 的数据刷写入 Store File 中,并且一个 Store 一次刷写形成一个 Store File。

注意2:基于以上刷写机制,可能会出现某个 Store 数据量饱和导致 Region 整体刷写,而其他 Store 此时数据量过小,但也被刷成 Store File 而在 HDFS 上形成小文件的情况。为避免此种情况,应尽量控制一个 Region 中的 Store 数,也就是控制 列族数<3。

① 单个 memstore 大小

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

当 memstore 的大小达到了 hbase.hregion.memstore.flush.size(默认值128M)* hbase.hregion.memstore.block.multiplier(默认值4)时,会暂时阻止 WAL 往该 memstore 写数据。

② memstore 总大小

当 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 的总大小达到 java_heapsize * hbase.regionserver.global.memstore.size(默认值0.4)时,会暂时阻止 WAL 往所有的 memstore 写数据。

③ 自动刷写时间

到达自动刷写的时间,也会触发 memstore flush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)。

④ WAL 文件的数量

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

3.4 读流程

在这里插入图片描述

在这里插入图片描述

读流程:

1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。

2)zookeeper 返回 meta 表的位置。

3)Client 访问对应的 Region Server,请求 hbase:meta 表信息。

4)Regioin Server 返回 hbase:meta 表信息,Client 根据 hbase:meta 读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。

5)Client 将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。

6)Client 向目标 Region Server 发送 get 请求。

7)会两个 Scaner 分别在 MemStore、StoreFlie(HFile)中查找,在 HFile 中查找时,会先扫描其索引快速查找,同时也会扫描 Block Cache 已缓存的数据。查找到的数据包含 rowkey 的不同版本。

8)查到的所有数据进行合并 Merge。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。

9)将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到 Block Cache,方便下次查询。

10)将合并后的最终结果返回给 Client,也可能不返回(合并后确定数据删除)。

3.5 StoreFile Compaction 压缩

​ 由于 memstore 每次刷写都会生成一个新的 HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,因此查询时需要遍历所有的 HFile。为了减少 HFile 的个数,以及清理掉过期和删除的数据,会进行 StoreFile Compaction。

​ Compaction 分为两种,分别是 Minor CompactionMajor Compaction。Minor Compaction 会将临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据。Major Compaction会将一个Store下的所有的 HFile 合并成一个大 HFile,并且会清理掉所有过期和删除的数据

在这里插入图片描述

注意:在生产环境中,为避免 Major compaction 影响到 HBase 的使用性能。一般会将自动 Major compaction 关闭,在访问量较低的时间点,定期手动进行 Major compaction。

3.6 Region Split

​ 默认情况下,每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑,HMaster 有可能会将某个 Region 转移给其他的 Region Server。

Region Split 时机:

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

  2. 当 1 个 region 中的某个 Store 下所有 StoreFile 的总大小超过 Min(initialSize * R^3, hbase.hregion.max.filesize)

    该 Region 就会进行拆分。其中 initialSize 的默认值为 2 * hbase.hregion.memstore.flush.size(默认 128M)

    R 表示当前 Region Server 中属于该 Table 的 Region 个数,每次 split 后 R+1。(0.94版本之后)。

    具体的切分策略为:

    第一次:当 StoreFile 的总大小超过 256MB(1^3 * 256 )就会 split。

    第二次:当 StoreFile 的总大小超过 2048MB (2^3 * 256 )就会 split。

    第三次:当 StoreFile 的总大小超过 6912MB (3^3 * 256 )就会 split。

    第四次:4^3 * 256 = 16384MB > 10GB,因此取较小的值10GB ,后面每次 split 的 size 都是 10GB 了。

四、HBase API

4.1 官网 API 介绍

https://hbase.apache.org/apidocs/index.html

在这里插入图片描述

  1. 通过 ConnectionFactory 获取 Connection
  2. Connection 是一个重量级连接,其中包含与 master、regions、zookeeper 的连接,且线程安全,故创建其单例。
  3. Table 和 Admin 对象从 Connection 获得。
  4. Admin 负责表的创建、修改、删除,类似于 master 的作用。
  5. Table 负责数的插入、查询、删除,类似于 region server 的作用。

4.2 DDL 操作

建立连接

创建一个 HBaseUtils 类,并建立连接

public class HBaseUtils {
    private static Connection connection = null;
    /**
     * 建立连接
     */
    static{
        try {
            // 配置参数
            Configuration conf = HBaseConfiguration.create();
            conf.set("hbase.zookeeper.quorum", "hadoop102,hadoop103,hadoop104");
            conf.set("hbase.zookeeper.property.clientPort", "2181");
            // 获取连接
            connection = ConnectionFactory.createConnection(conf);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

① 创建表
/**
 * 创建表
 */
public static void createTable(String tableName, String ... families) throws IOException {
    Admin admin = null;
    try {
        // 获取资源,admin 代表 Hmaster
        admin = connection.getAdmin();
        // 判断表是否存在
        if (admin.tableExists(TableName.valueOf(tableName))) {
            System.err.println("table" + tableName + "already exists!");
            return;
        }
        // 获取表描述器
        HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName));
        // 获取列族描述器
        for (String family : families) {
            HColumnDescriptor familyDesc = new HColumnDescriptor(family);
            tableDesc.addFamily(familyDesc);
        }
        // 创建表
        admin.createTable(tableDesc);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭资源
        admin.close();
    }
}

② 修改表
/**
 * 修改表中的某个列族
 */
public static void modifyTable(String tableName, String family) throws IOException {
    Admin admin = null;
    try {
        // 获取 admin
        admin = connection.getAdmin();
        // 判断表是否存在
        if (!admin.tableExists(TableName.valueOf(tableName))) {
            System.err.println("table" + tableName + "doesn't exists!");
            return;
        }
        // 创建列描述器
        HColumnDescriptor familyDesc = new HColumnDescriptor(family);
        // 修改列操作
        admin.modifyColumn(TableName.valueOf(tableName), familyDesc);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭资源
        admin.close();
    }
}

③ 删除表
/**
 * 删除表
 */
public static void dropTable(String tableName) throws IOException {
    Admin admin = null;
    try {
        // 获取 admin
        admin = connection.getAdmin();
        // 判断表是否存在
        if (!admin.tableExists(TableName.valueOf(tableName))) {
            System.err.println("table" + tableName + "doesn't exists!");
            return;
        }
        // 修改表的可删除性
        admin.disableTable(TableName.valueOf(tableName));
        // 执行删除操作
        admin.deleteTable(TableName.valueOf(tableName));
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭资源
        admin.close();
    }
}

4.3 DML 操作

① put:插入一个列的值
/**
 * put 操作:插入一个列的值
 */
public static void putCell(String tableName, String rowKey, String family, String column, String value) throws IOException {
    // 获取table
    Table table = connection.getTable(TableName.valueOf(tableName));
    // 获取put对象
    Put put = new Put(Bytes.toBytes(rowKey));
    // 将列信息封装到put对象中
    put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), Bytes.toBytes(value));
    // 执行put操作
    table.put(put);
    // 关闭资源
    table.close();
}

② get:获取一行数据
/**
 * get 操作:获取一行数据
 */
public static void getRow(String tableName, String rowKey) throws IOException {
    // 获取table对象,相当于 Region Server
    Table table = connection.getTable(TableName.valueOf(tableName));
    // 指定 rowKey
    Get get = new Get(Bytes.toBytes(rowKey));
//        get.addColumn();
//        get.setMaxVersions();
    // 执行get
    Result result = table.get(get);
    // 获取返回值
    Cell[] cells = result.rawCells();
    for (Cell cell : cells) {
        // 使用CellUtil工具类获取value字节数组
        byte[] valueBytes = CellUtil.cloneValue(cell);
        // 使用CellUtil工具类获取column字节数组
        byte[] columnBytes = CellUtil.cloneQualifier(cell);
        System.out.println(Bytes.toString(columnBytes) + "-" + Bytes.toString(valueBytes));
    }
    // 关闭资源
    table.close();
}

③ scan 获取指定 value 的多条数据
/**
 * 两个 filter ,其中一个指定 sex 列的值为 male
 */
public static void getRowsByColumn(String tableName, String family, String column, String value) throws IOException {
    Table table = connection.getTable(TableName.valueOf(tableName));
    Scan scan = new Scan();
    // 过滤器1
    SingleColumnValueFilter filter = new SingleColumnValueFilter(Bytes.toBytes(family), Bytes.toBytes(column),CompareFilter.CompareOp.EQUAL, Bytes.toBytes(value));
    // 过滤器2,要求 sex = male
    SingleColumnValueFilter filter1 = new SingleColumnValueFilter(Bytes.toBytes(family), Bytes.toBytes("sex"),CompareFilter.CompareOp.EQUAL, Bytes.toBytes("male"));
    // 如果该行不触发过滤条件,默认将其过滤掉
    filter.setFilterIfMissing(true);
    // 将两个filter配置的关系,并放入一个list
    FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
    filterList.addFilter(filter);
    filterList.addFilter(filter1);
    // 将 filterList 配置到 scan 中
    scan.setFilter(filterList);
    // 执行 scan 操作 得到一个结果集
    ResultScanner scanner = table.getScanner(scan);
    // 遍历结果集
    for (Result result : scanner) {
        // 获取返回值
        Cell[] cells = result.rawCells();
        for (Cell cell : cells) {
            // 使用 CellUtil 工具类获取 value 字节数组
            byte[] valueBytes = CellUtil.cloneValue(cell);
            // 使用 CellUtil 工具类获取 column 字节数组
            byte[] columnBytes = CellUtil.cloneQualifier(cell);
            // 使用 CellUtil 工具类获取 rowKey 字节数组
            byte[] rowBytes = CellUtil.cloneRow(cell);
            System.out.println(Bytes.toString(rowBytes) + ":" + Bytes.toString(columnBytes) + "-" + Bytes.toString(valueBytes));
        }
    }
    // 关闭资源
    scanner.close();
    table.close();
}

④ scan:获取多行数据
/**
 * scan 操作:获取多行数据
 */
public static void getRowsByRowRange(String tableName, String startRow, String stopRow) throws IOException {
    Table table = connection.getTable(TableName.valueOf(tableName));
    // 获取一个scan
    Scan scan = new Scan(Bytes.toBytes(startRow), Bytes.toBytes(stopRow));
    // 执行scan 获取一个结果集
    ResultScanner scanner = table.getScanner(scan);
    // 遍历结果集
    for (Result result : scanner) {
        // 获取返回值
        Cell[] cells = result.rawCells();
        for (Cell cell : cells) {
            // 使用 CellUtil 工具类获取 value 字节数组
            byte[] valueBytes = CellUtil.cloneValue(cell);
            // 使用 CellUtil 工具类获取 column 字节数组
            byte[] columnBytes = CellUtil.cloneQualifier(cell);
            // 使用 CellUtil 工具类获取 rowKey 字节数组
            byte[] rowBytes = CellUtil.cloneRow(cell);
            System.out.println(Bytes.toString(rowBytes) + ":" + Bytes.toString(columnBytes) + "-" + Bytes.toString(valueBytes));
        }
    }
    // 关闭资源
    scanner.close();
    table.close();
}

⑤ delete:删除一行
/**
 * 删除一行数据
 */
public static void deleteRow(String tableName, String rowKey) throws IOException {
    // 获取table
    Table table = connection.getTable(TableName.valueOf(tableName));
    // 创建delete
    Delete delete = new Delete(Bytes.toBytes(rowKey));
    // 执行delete
    table.delete(delete);
    // 关闭资源
    table.close();
}

⑥ delete:删除一行数据指定列的所有版本
/**
 * 删除一行数据指定列的所有版本
 */
public static void deleteColumn(String tableName, String family, String rowKey, String column) throws IOException {
    // 获取table
    Table table = connection.getTable(TableName.valueOf(tableName));
    // 创建delete
    Delete delete = new Delete(Bytes.toBytes(rowKey));
    // 删除最新版本
    // delete.addColumn()
    // 删除所有版本
    delete.addColumns(Bytes.toBytes(family), Bytes.toBytes(column));
    // 执行删除操作
    table.delete(delete);
    // 关闭资源
    table.close();
}

4.3 MapReduce

​ 通过 HBase 的相关 JavaAPI,我们可以实现伴随 HBase 操作的 MapReduce 过程,比如使用 MapReduce 将数据从本地文件系统导入到 HBase 的表中,比如我们从 HBase 中读取一些原始数据后使用 MapReduce 做数据分析。

① 官方 HBase-MapReduce
  1. 查看 HBase 的 MapReduce 任务的执行所需要的 jar 包

    bin/hbase mapredcp
    
    
  2. 直接在 hadoop-env.sh 中配置 hbase 需要的所有 jar 包

    # 并在 hadoop-env.sh 中配置:(注意:在for循环之后配)
    export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/opt/module/hbase/lib/*
    
    
  3. 运行官方案例

    /opt/module/hadoop-2.7.2/bin/yarn jar lib/hbase-server-1.3.1.jar rowcounter student
    
    

    计算出 student 表的行数 ROWS = 3

  4. 使用本地案例

    在本地创建一个 tsv 格式的文件,fruit.tsv

    1001	Apple	Red
    1002	Pear	Yellow
    1003	Pineapple	Yellow
    
    

    创建 HBase 表

    hbase(main):001:0> create 'fruit','info'
    
    

    在 HDFS 中创建 input_fruit 文件夹并上传 fruit.tsv 文件

    bin/hdfs dfs -mkdir /input_fruit/
    bin/hdfs dfs -put fruit.tsv /input_fruit/
    
    

    执行读取HDFS 上的文件,使用 MapReduce 到 HBase 的 fruit 表中

    /opt/module/hadoop-2.7.2/bin/yarn jar lib/hbase-server-1.3.1.jar importtsv \
    -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:color fruit \
    hdfs://hadoop102:9000/input_fruit
    
    

在这里插入图片描述

② HBase-MR-HBase 示例

​ 需求:将 fruit 表中的一部分数据,通过 MR 迁入到 fruit_mr 表中

  1. 构建 ReadMapper 类,用于读取 fruit 表中的数据

    public class ReadMapper extends TableMapper<ImmutableBytesWritable, Put> {
        // 从HBase中读取数据,传入的key为每行数据的rowKey,value为每个rowKey查询返回的结果集Result
        @Override
        protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {
            // 创建 put 对象,传入 rowKey
            Put put = new Put(key.get());
            // 获取结果集中的数据
            Cell[] cells = value.rawCells();
            for (Cell cell : cells) {
                // 将列名为name的数据放入到 put 中
                if ("name".equals(Bytes.toString(CellUtil.cloneQualifier(cell)))) {
                    put.add(cell);
                }
            }
            // 写出
            context.write(key, put);
        }
    }
    
    
  2. 构建 WriteReducer 类,用于将读取到的 fruit 表中的数据写入到 fruit_mr 表中

    public class WriteReducer extends TableReducer<ImmutableBytesWritable, Put, NullWritable> {
        // 写出到HBase的表中,在Driver中控制
        @Override
        protected void reduce(ImmutableBytesWritable key, Iterable<Put> values, Context context) throws IOException, InterruptedException {
            // 循环写出
            for (Put value : values) {
                context.write(NullWritable.get(), value);
            }
        }
    }
    
    
  3. 构建 Driver 类,用于配置 job,提交job

    public class Driver {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            // 获取conf对象
            Configuration conf = HBaseConfiguration.create();
            // 封装conf对象
            conf.set("hbase.zookeeper.quorum", "hadoop102,hadoop103,hadoop104");
            // 创建job任务
            Job job = Job.getInstance(conf);
            // 关联jar包
            job.setJarByClass(Driver.class);
            // 创建scan对象
            Scan scan = new Scan();
            // 配置读取表的位置,关联Mapper类,输入输出的类型
            TableMapReduceUtil.initTableMapperJob(
                    "fruit", scan, ReadMapper.class, ImmutableBytesWritable.class, Put.class, job);
            // 预设reduce个数
            job.setNumReduceTasks(100);
            // 配置输出表的位置,关联Reducer类,通过RegionPartitioner确定reduce个数
            TableMapReduceUtil.initTableReducerJob("fruit_mr", WriteReducer.class, job, HRegionPartitioner.class);
            // 提交
            job.waitForCompletion(true);
        }
    }
    
    
  4. 在 HBase 中创建输出表

    hbase(main):005:0> create 'fruit_mr','info'
    
    
  5. 打包运行任务

    hadoop jar ./hbase-maben-1.0-SNAPSHOT.jar com.atguigu.hbaseMR.Driver
    
    
  6. 查询结果

在这里插入图片描述

③ HDFS-MR-HBase 示例

​ 需求:将 HDFS 中的数据写入到 HBase 表中。

  1. 构建 ReadMapper类,用于读取 HDFS 中的文件数据

    public class ReadMapper extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put> {
        // 从HDFS上读取数据
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 读取一行
            String line = value.toString();
            // 切分
            String[] split = line.split("\t");
            // 处理脏数据
            if (split.length < 3) return;
            // 创建 put 对象,传入rowKey
            Put put = new Put(Bytes.toBytes(split[0]));
            // 封装指定的列到put中
            put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes(split[1]));
            put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("color"), Bytes.toBytes(split[2]));
            // 写出
            context.write(new ImmutableBytesWritable(Bytes.toBytes(split[0])), put);
        }
    }
    
    
  2. 构建 WriteReducer 类,用于将读取到的 fruit.tsv 中的数据写入到 fruit_mr 表中

    public class WriteReducer extends TableReducer<ImmutableBytesWritable, Put, NullWritable> {
        // 写出到HBase的表中,在Driver中控制
        @Override
        protected void reduce(ImmutableBytesWritable key, Iterable<Put> values, Context context) throws IOException, InterruptedException {
            // 循环写出
            for (Put value : values) {
                context.write(NullWritable.get(), value);
            }
        }
    }
    
    
  3. 构建 Driver 类,用于配置job,提交job

    public class Driver {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            // 获取conf对象
            Configuration conf = HBaseConfiguration.create();
            // 封装conf对象
            conf.set("hbase.zookeeper.quorum", "hadoop102,hadoop103,hadoop104");
            // 创建job任务
            Job job = Job.getInstance(conf);
            // 关联jar包
            job.setJarByClass(com.atguigu.hbaseMR2.Driver.class);
            // 关联Mapper类
            job.setMapperClass(ReadMapper.class);
            // 配置Map输出Key和Value的类型
            job.setMapOutputKeyClass(ImmutableBytesWritable.class);
            job.setMapOutputValueClass(Put.class);
            // 配置输入路径
            FileInputFormat.setInputPaths(job, new Path("/input_fruit"));
            //预设reduce个数
            job.setNumReduceTasks(100);
            // 配置输出表的位置,关联Reducer类,通过RegionPartitioner确定reduce个数
            TableMapReduceUtil.initTableReducerJob("fruit_mr", WriteReducer.class, job, HRegionPartitioner.class);
            // 提交job
            job.waitForCompletion(true);
        }
    }
    
    
  4. 在 HBase 中创建输出表

    hbase(main):005:0> create 'fruit_mr','info'
    
    
  5. 打包运行任务

    hadoop jar ./hbase-maben-1.0-SNAPSHOT.jar com.atguigu.hbaseMR2.Driver
    
    
  6. 查询结果

在这里插入图片描述

4.4 HBase 与 Hive 的集成

① HBase 与 Hive 的对比

Hive

  1. 数据仓库

    Hive 的本质其实就相当于将 HDFS 中已经存储的文件在 Mysql 中做了一个双射关系,以方便使用HQL去管理查询。

  2. 用于数据分析、清洗

    Hive 适用于离线的数据分析和清洗,延迟较高。

  3. 基于 HDFS、MapReduce

    Hive 存储的数据依旧在 DataNode 上,编写的 HQL 语句终将是转换为 MapReduce 代码执行。

HBase

  1. 数据库

    是一种面向列存储的非关系型数据库。

  2. 用于存储结构化和非结构化的数据

    适用于单表非关系型数据的存储,不适合做关联查询,类似 JOIN 等操作。

  3. 基于HDFS

    数据持久化存储的体现形式是 Hfile,存放于 DataNode 中,被 ResionServer 以 region 的形式进行管理。

  4. 延迟较低,接入在线业务使用

    面对大量的企业数据,HBase 可以直线单表大量数据的存储,同时提供了高效的数据访问速度。

② HBase 与 Hive 集成使用

提示:HBase 与 Hive 的集成在最新的两个版本中无法兼容。需要重新编译:hive-hbase-handler-1.2.2.jar

环境准备

操作 Hive 的同时对 HBase 也会产生影响,故 Hive 需要持有操作 HBase 的 Jar,拷贝 Hive 所依赖的 Jar 包。(或者使用软连接的形式)。

export HBASE_HOME=/opt/module/hbase
export HIVE_HOME=/opt/module/hive

ln -s $HBASE_HOME/lib/hbase-common-1.3.1.jar  $HIVE_HOME/lib/hbase-common-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-server-1.3.1.jar $HIVE_HOME/lib/hbase-server-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-client-1.3.1.jar $HIVE_HOME/lib/hbase-client-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-protocol-1.3.1.jar $HIVE_HOME/lib/hbase-protocol-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-it-1.3.1.jar $HIVE_HOME/lib/hbase-it-1.3.1.jar
ln -s $HBASE_HOME/lib/htrace-core-3.1.0-incubating.jar $HIVE_HOME/lib/htrace-core-3.1.0-incubating.jar
ln -s $HBASE_HOME/lib/hbase-hadoop2-compat-1.3.1.jar $HIVE_HOME/lib/hbase-hadoop2-compat-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-hadoop-compat-1.3.1.jar $HIVE_HOME/lib/hbase-hadoop-compat-1.3.1.jar

在 hive-site.xml 中修改 zookeeper 的属性

<property>
  <name>hive.zookeeper.quorum</name>
  <value>hadoop102,hadoop103,hadoop104</value>
  <description>The list of ZooKeeper servers to talk to. This is only needed for read/write locks.</description>
</property>
<property>
  <name>hive.zookeeper.client.port</name>
  <value>2181</value>
  <description>The port of ZooKeeper servers to talk to. This is only needed for read/write locks.</description>
</property>

**案例一:**建立 Hive 表,关联 HBase 表,插入数据到 Hive 表的同时能够影响 HBase 表。

  1. 在 Hive 中创建表同时关联 HBase

    CREATE TABLE hive_hbase_emp_table(
    empno int,
    ename string,
    job string,
    mgr int,
    hiredate string,
    sal double,
    comm double,
    deptno int)
    STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
    WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno")
    TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");
    
    

    可以分别进入 Hive 和 HBase 查看,都生成了对应的表

  2. 在 Hive 中创建临时中间表,用于 load 文件中的数据

    提示:如果直接将数据 load 到 hive_hbase_emp_table 表中,会将 HDFS 中的文件移动到 hive 中,所以要创建一张中间表,先将数据加载到中间表,然后用 insert 查询结果的方式将数据插入到 hive_hbase_emp_table 表中。

    CREATE TABLE emp(
    empno int,
    ename string,
    job string,
    mgr int,
    hiredate string,
    sal double,
    comm double,
    deptno int)
    row format delimited fields terminated by '\t';
    
    
  3. 向 Hive 中间表中 load 数据

    hive> load data local inpath '/home/admin/softwares/data/emp.txt' into table emp;
    
    
  4. 通过 insert 命令将中间表中的数据导入到 Hive 关联 HBase 的那张表中

    hive> insert into table hive_hbase_emp_table select * from emp;
    
    
  5. 查看 Hive 以及关联的 HBase 表中是否已经成功的同步插入了数据

    hive> select * from hive_hbase_emp_table;
    
    
    hbase> scan ‘hbase_emp_table’
    
    

案例二:在 HBase 中已经存储了某一张表 hbase_emp_table,然后在 Hive 中创建一个外部表来关联 HBase 中的 hbase_emp_table 这张表,使之可以借助 Hive 来分析 HBase 这张表中的数据。

  1. 在 Hive 中创建外部表,

    CREATE EXTERNAL TABLE relevance_hbase_emp(
    empno int,
    ename string,
    job string,
    mgr int,
    hiredate string,
    sal double,
    comm double,
    deptno int)
    STORED BY 
    'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
    WITH SERDEPROPERTIES ("hbase.columns.mapping" = 
    ":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno") 
    TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");
    
    
  2. 关联后就可以使用 Hive 函数进行一些分析操作了

    hive (default)> select * from relevance_hbase_emp;
    
    

五、HBase 优化

5.1 高可用

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

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

    bin/stop-hbase.sh
    
    
  2. 在 hbase/conf 目录下创建 backup-masters 文件,并配置高可用 HMaster 节点

    vim conf/backup-masters
    hadoop103
    
    
  3. 分发 backup-masters

    xsync backup-masters
    
    
  4. 重启 HBase 集群,打开 HMaster web 页面,查看 BackupMaster

    http://hadooo102:16010

在这里插入图片描述

  1. 关闭 hadoop102 的 HMaster,观察 hadoop103 的 HMater 是否激活

    [maben996@hadoop102] $ kill -9 HMaster端口号
    
    

在这里插入图片描述

  1. 再次恢复启动 hadoop102 的 HMaster

    bin/hbase-daemon.sh start master
    
    

    再次恢复启动 hadoop102 的 HMaster,此时102会成为 Backup Master

在这里插入图片描述

5.2 预分区

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

  1. 手动设定预分区

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

在这里插入图片描述

  1. 生成16进制序列预分区

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

在这里插入图片描述

  1. 按照文件中设置的规则预分区

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

    aaaa
    bbbb
    cccc
    dddd
    
    

    然后引用文件创建分区表

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

在这里插入图片描述

  1. 使用 JavaAPI 创建预分区

    /**
     * 创建带有预分区的表
     */
    public static void createTableOfRegion(String tableName) throws IOException {
        Admin admin = connection.getAdmin();
        HTableDescriptor tableDes = new HTableDescriptor(TableName.valueOf(tableName));
        HColumnDescriptor columnDesc = new HColumnDescriptor("info");
        tableDes.addFamily(columnDesc);
    
        //自定义算法生成或手动指定分区,放入一个在二维字节数组中
        byte[][] splitKeys = new byte[3][];
        splitKeys[0] = Bytes.toBytes("aaa");
        splitKeys[1] = Bytes.toBytes("bbb");
        splitKeys[2] = Bytes.toBytes("ccc");
    
        // 在创建表示传入二维字节数组即可
        admin.createTable(tableDes, splitKeys);
        admin.close();
    }
    
    

在这里插入图片描述

5.3 RowKey 设计

设计 rowkey 的主要目的 ,1.让数据的读写满足业务需求。2. 均匀的分布于所有的 region 中,在一定程度上防止数据倾斜。

  1. 使用相应算法生成前缀,达到均匀分布的目的

    // 以谷粒微博案例中的 rowKey 为例,
    fans:follow:star
    
    // 目标分区为如下 5 个分区
    -∞,1
    1,2
    2,3
    3,4
    4+// 分析:
    // 相同的 'fans:follow:'前缀经过相应算法后能够进入同一个 region,并在region内连续,使之能够集中查询到。
    // 不同的 'fans:follow:'前缀经过相应算法后进入不同的 region,达到均匀分配的效果。
    
    // 实现:
    'fans:follow:'.hashCode%5_fans:follow:star
    
    
  2. 字符串反转

    // 例如时间戳 1566099383517,它是单调递增的,前半部分基本不变。
    // 业务需要 rowKey 必须加上时间戳。
    // 如果以时间戳作为前缀,会导致新的数据只进入最后的一个 region,造成region的热点问题。
    // 解决方法:将时间戳反转,使之进入不同的 region。
    
    
  3. 字符串拼接

    这是最常见的 rowKey 设计手段,根据业务需求进行设计即可。

5.4 内存优化

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

​ 修改 HBase 的 堆内存参数,只需要在 hbase/conf/hbase.env.sh 文件中修改以下参数即可

​ 默认大小是 1G,可根据需要调整。

# The maximum amount of heap to use. Default is left to JVM default.
# export HBASE_HEAPSIZE=1G

5.5 基础优化

  1. 允许在HDFS的文件中追加内容

    hdfs-site.xml、hbase-site.xml

    属性:dfs.support.append
    解释:开启 HDFS 追加同步,可以优秀的配合 HBase 的数据同步和持久化。默认值为true。
    
    
  2. 优化 DataNode 允许的最大文件打开数

    hdfs-site.xml

    属性:dfs.datanode.max.transfer.threads
    解释:HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为 4096 或者更高。
    默认值为:4096
    
    
  3. 优化延迟高的数据操作的等待时间

    hdfs-site.xml

    属性:dfs.image.transfer.timeout
    解释:如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的时间,建议把该值设置为更大的值(默认60000毫秒),以确保 socket 不会被 timeout 掉。
    
    
  4. 优化数据的写入效率

    mapred-site.xml

    属性:
    mapreduce.map.output.compress
    mapreduce.map.output.compress.codec
    解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec或者其他压缩方式。
    
    
  5. 设置RPC监听数量

    hbase-site.xml

    属性:hbase.regionserver.handler.count
    解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。
    
    
  6. 优化 HStore 文件大小

    hbase-site.xml

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

    hbase-site.xml

    属性:hbase.client.write.buffer
    解释:用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。
    
    
  8. 指定 scan.next 扫描 HBase 所获取的行数

    hbase-site.xml

    属性:hbase.client.scanner.caching
    解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。默认1行(MR任务,若map段逻辑复杂,该值需设置较小,反之较大)
    
    
  9. 关闭自动 majorcompation

    hbase-site.xml

    hbase.hregion.majorcompaction
    majorcompaction 时性能消耗巨大,默认 7天一次,设成0,可关闭自动majorcompaction,在适当的时候手动进行majorcompact。
    
    
  10. 读写缓存调优

    hfile.block.cache.size
    默认0.4,读请求比较多的情况下,可适当调大
    
    hbase.regionserver.global.memstore.size
    默认0.4,写请求较多的情况下,可适当调大
    
    
  11. 关闭自动 region_split

    hbase.regionserver.region.split.policy=org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy
    关闭自动region_split
    
    

六、HBase 谷粒微博案例

6.1 需求分析

  1. 微博内容的浏览,数据库表设计
  2. 用户社交体现:关注用户,取关用户
  3. 拉取关注的人的微博内容

6.2 代码设计

  1. 创建命名空间以及表名的定义
  2. 创建微博内容表
  3. 创建用户关系表
  4. 创建用户微博内容接收邮件表
  5. 发布微博内容
  6. 添加关注用户
  7. 移除(取关)用户
  8. 获取关注的人的微博内容

七、扩展

7.1 HBase 在商业项目中的能力

  1. 消息量:发送和接收的消息数超过60亿
  2. 将近1000亿条数据的读写
  3. 高峰期每秒150万左右操作
  4. 整体读取数据占有约55%,写入占有45%
  5. 超过2PB的数据,涉及冗余共6PB数据
  6. 数据每月大概增长300千兆字节。

7.2 布隆过滤器

​ 在 HBase 的查询操作时,Region Server 会在多个 HFile 中查找对应的 rowKey,使用 Bloom Filter 就可以快速过滤掉绝对不含有当前 rowKey 的 HFile,避免了扫描所有的 HFile,极大的提高了查询效率。

​ Bloom Filter 是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。

​ Bloom Filter 的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter 不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter 通过极少的错误换取了存储空间的极大节省。

​ Bloom Filter概念和原理:https://blog.csdn.net/jiaomeng/article/details/1495500

7.3 HBase2.0新特性

2017年8月22日凌晨2点左右,HBase发布了2.0.0 alpha-2,相比于上一个版本,修复了500个补丁,我们来了解一下2.0版本的HBase新特性。

最新文档:

http://hbase.apache.org/book.html#ttl

官方发布主页:

http://mail-archives.apache.org/mod_mbox/www-announce/201706.mbox/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值