HBase

1.hbase的介绍

hbase的基本简介:hbase依赖于hdfs,hbase是一个nosql数据库,是一个非关系型数据库,支持读写查询操作

hbase当中所有的数据都是byte[]

HBase中的表有这样的特点:

  • 海量存储:一个表可以有上十亿行,上百万列
  • 面向列:面向列(族)的存储和权限控制,列(族)独立检索。
  • 易扩展:Hbase的扩展性主要体现在两个方面,一个是基于上层处理能力(RegionServer)的扩展,一个是基于存储的扩展(HDFS)。
  • 高并发:开发情况下,hbase单个io延迟下降不多。实现高并发,低延迟
  • 稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。

2.hbase与hadoop的关系

1、HDFS

* 为分布式存储提供文件系统

* 针对存储大尺寸的文件进行优化,不需要对HDFS上的文件进行随机读写

* 直接使用文件

* 数据模型不灵活

* 使用文件系统和处理框架

* 优化一次写入,多次读取的方式

2、HBase

* 提供表状的面向列的数据存储

* 针对表状数据的随机读写进行优化

* 使用key-value操作数据

* 提供灵活的数据模型

* 使用表状存储,支持MapReduce,依赖HDFS

* 优化了多次读,以及多次写

hbase是基于hdfs的,hbase的数据都是存储在hdfs上的,hbase是一个数据库,支持随机读写

3.Hbase数据存储架构

主节点:HMaster

  • 用于监控regionServer的健康状态,
  • 处理regionServer的故障转移
  • 处理元数据变更
  • 处理region的分配或者移除
  • 空闲时间做数据的负载均衡

从节点:HRegionServer

  • 负责存储HBase的实际数据
  • 处理分配给他的region
  • 刷新缓存数据到HDFS上
  • 维护HLog
  • 负责处理region的切片

一个HRegionServer=1个HLog+很多个region

一个region=很多个store模块

一个store模块=1个memoryStore+很多个storeFile

组件:

Write-Ahead logs:Hbase读写数据的时候数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入内存中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。

HFile:这是在磁盘中保存原始数据的事迹物理文件,是实际的存储文件

Store:HFile存储在Store中,一个Store对应Hbase表中的一个列族

MemStore:内存存储,位于内存中,用来保存当前数据操作,所有当数据保存在WAL中后,RegionServer会在内存中存储键值对

Region:Hbase表的分片,HBase表会根据RowKey值被切分成不同的region存储在RegionServer中,在一高RegionServer中可以有多个不同的region

4.Hbase的集群环境搭建

注意事项:Hbase强依赖于HDFS以及zookeeper,所以安装Hbase之前一定要保证Hadoop和zookeeper正常启动

第一步:下载对应的HBase的安装包

下载Hbase的安装包,下载地址如下:

http://archive.apache.org/dist/hbase/2.0.0/hbase-2.0.0-bin.tar.gz

第二步:HBASE集群部署

  • 目标实现Hbase分布式集群部署

  • 实施

    • 解压安装

      • 上传HBASE安装包到第一台机器的/export/software目录下

        cd /export/software/
        rz
        
      • 解压安装

        tar -zxvf hbase-2.1.0.tar.gz -C /export/server/
        cd /export/server/hbase-2.1.0/

        修改配置

      • 切换到配置文件目录下

        cd /export/server/hbase-2.1.0/conf/
        
      • 修改hbase-env.sh

        #28行
        export JAVA_HOME=/export/server/jdk1.8.0_65
        #125行
        export HBASE_MANAGES_ZK=false
        
      • 修改hbase-site.xml

        cd /export/server/hbase-2.1.0/
        mkdir datas
        vim conf/hbase-site.xml
          <property >
            <name>hbase.tmp.dir</name>
            <value>/export/server/hbase-2.1.0/datas</value>
          </property>
            <property >
            <name>hbase.rootdir</name>
            <value>hdfs://node1:8020/hbase</value>
          </property>
          <property >
            <name>hbase.cluster.distributed</name>
            <value>true</value>
          </property>
          <property>
            <name>hbase.zookeeper.quorum</name>
            <value>node1,node2,node3</value>
          </property>
          <property>
            <name>hbase.unsafe.stream.capability.enforce</name>
            <value>false</value>
          </property>
      • 修改regionservers

        vim conf/regionservers
        node1
        node2
        node3
        
      • 配置环境变量

        vim /etc/profile
        #HBASE_HOME
        export HBASE_HOME=/export/servers/hbase-2.1.0
        export PATH=:$PATH:$HBASE_HOME/bin
        source /etc/profile
      • 复制jar包

        cp lib/client-facing-thirdparty/htrace-core-3.1.0-incubating.jar lib/
        
    • 分发

      cd /export/server/
      scp -r hbase-2.1.0 node2:$PWD
      scp -r hbase-2.1.0 node3:$PWD
      
    • 服务端启动与关闭

      • step1:启动HDFS

      • step2:启动ZK

      • step3:启动Hbase

        start-hbase.sh
        
      • 关闭:先关闭Hbase再关闭zk

        stop-hbase.sh
        stop-zk-all.sh
        stop-dfs.sh
        
    • 测试

      • 访问Hbase Web UI

        node1:16010
        
        Apache Hbase 1.x之前是60010,1.x开始更改为16010
        CDH版本:一直使用60010
        
    • Web无法访问的几个问题及原因
      1.如果报错404,那么则是网页访问不了
      这个问题首先检查hbase-site.xml中的端口是否和自己的hdfs端口一至,我的就是这个问题连接不上
    • <property>
              <name>hbase.rootdir</name>
              <value>hdfs://node01:9000/HBase</value>
      </property>


      其次更换版本
      我更换成了2.2.4版本
      首先:删除zookeeper的注册信息;删除hbase的数据目录
      报错:
      2.报错500连接不上hadoop102:16010的web页面情况2:org.apache.hadoop.hbase.PleaseHoldException: Master is initializing

      可能前面启动失败一次或者安装过其他版本导致,因为hbase基于zookeeper,所以在zookeeper上已经注册了之前的hbase信息,导致第二次启动失败,所以先在zookeeper中删除hbase的注册信息:
       

      # 切换到zookeeper的bin目录下
      cd zookeeper/bin
      2
      # 然后执行 ./zkCli.sh 命令
      [XXhadoop102 bin]$ ./zkCli.sh
      # 输入 ls / 命令行查看所有的内容
      [zk: localhost:2181(CONNECTED) 0] ls /
      [hbase, kafka, servers, spark, zookeeper]
      # 使用 rmr /hbase 或者 deleteall /hbase 删除zookeeper中的所有 hbase的目录
      [zk: localhost:2181(CONNECTED) 3] rmr /hbase
      # 此时,可以看到Zookeeper中已经没有HBase了
      [zk: localhost:2181(CONNECTED) 5] ls /
      [kafka, servers, spark, zookeeper]
      
    • 搭建Hbase HA

      • 关闭Hbase所有节点

        stop-hbase.sh
      • 创建并编辑配置文件

        vim conf/backup-masters
        node2
      • 启动Hbase集群

    • 测试HA

      • 启动两个Master,强制关闭Active Master,观察StandBy的Master是否切换为Active状态

        hbase-daemon.sh stop master
      • 【测试完成以后,删除配置,只保留单个Master模式】

  • 小结

    • 实现Hbase分布式集群部署

node01

node02

node03

创建back-masters配置文件,实现HMaster的高可用

node01机器进行修改配置文件

cd /export/servers/hbase-2.0.0/conf

vim backup-masters

node02

第四步:安装包分发到其他机器

将我们node01服务器的hbase的安装包拷贝到其他机器上面去

cd /export/servers/

scp -r hbase-2.0.0/ node02:$PWD

scp -r hbase-2.0.0/ node03:$PWD

第五步:三台机器创建软连接

因为hbase需要读取hadoop的core-site.xml以及hdfs-site.xml当中的配置文件信息,所以我们三台机器都要执行以下命令创建软连接

ln -s /export/servers/hadoop-2.7.5/etc/hadoop/core-site.xml /export/servers/hbase-2.0.0/conf/core-site.xml

ln -s /export/servers/hadoop-2.7.5/etc/hadoop/hdfs-site.xml /export/servers/hbase-2.0.0/conf/hdfs-site.xml

第六步:三台机器添加HBASE_HOME的环境变量

三台机器执行以下命令,添加HBASE_HOME环境变量

vim /etc/profile

export HBASE_HOME=/export/servers/hbase-2.0.0

export PATH=:$HBASE_HOME/bin:$PATH

第七步:HBase集群启动

第一台机器执行以下命令进行启动

cd /export/servers/hbase-2.0.0

bin/start-hbase.sh

警告提示:HBase启动的时候会产生一个警告,这是因为jdk7与jdk8的问题导致的,如果linux服务器安装jdk8就会产生这样的一个警告

我们可以只是掉所有机器的hbase-env.sh当中的

“HBASE_MASTER_OPTS”和“HBASE_REGIONSERVER_OPTS”配置 来解决这个问题。不过警告不影响我们正常运行,可以不用解决

另外一种启动方式:

我们也可以执行以下命令单节点进行启动

启动HMaster命令

bin/hbase-daemon.sh start master

启动HRegionServer命令

bin/hbase-daemon.sh start regionserver

第八步:页面访问

浏览器页面访问

http://node01:16010/master-status

5.Hbase的表模型

rowkey:行键,每一条数据都是使用行键来进行唯一标识的

columnFamily:列族,列族下面可以有很多列

column:列,每一个列都必须归属于某一个列族

timestamp:时间戳,每条数据都会有时间戳的概念

versionNum:版本号,每条数据都有版本号,当数据更新时,版本号也改变

创建一张HBase表最少需要两个条件:表名+列族名

注意:rowkey是我们在插入数据的时候自己指定的,列名也是插入数据的时候动态指定的

时间戳是自动生成的,versionNum也是自己维护的

6、HBase常用shell操作

1、进入HBase客户端命令操作界面

node01服务器执行以下命令进入hbase的shell客户端

cd /export/servers/hbase-2.0.0

bin/hbase shell

2、查看帮助命令

hbase(main):001:0> help

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

hbase(main):002:0> list

4、创建一张表

创建user表,包含info、data两个列族

hbase(main):010:0> create 'user', 'info', 'data'

或者

hbase(main):010:0> create 'user', {NAME => 'info', VERSIONS => '3'},{NAME => 'data'}

5、添加数据操作

向user表中插入信息,row key为rk0001,列族info中添加name列标示符,值为zhangsan

hbase(main):011:0> put 'user', 'rk0001', 'info:name', 'zhangsan'

向user表中插入信息,row key为rk0001,列族info中添加gender列标示符,值为female

hbase(main):012:0> put 'user', 'rk0001', 'info:gender', 'female

向user表中插入信息,row key为rk0001,列族info中添加age列标示符,值为20

hbase(main):013:0> put 'user', 'rk0001', 'info:age', 20

向user表中插入信息,row key为rk0001,列族data中添加pic列标示符,值为picture

hbase(main):014:0> put 'user', 'rk0001', 'data:pic', 'picture'

6、查询数据操作

1、通过rowkey进行查询

获取user表中row key为rk0001的所有信息

hbase(main):015:0> get 'user', 'rk0001'

2、查看rowkey下面的某个列族的信息

获取user表中row key为rk0001,info列族的所有信息

hbase(main):016:0> get 'user', 'rk0001', 'info'

3、查看rowkey指定列族指定字段的值

获取user表中row key为rk0001,info列族的name、age列标示符的信息

hbase(main):017:0> get 'user', 'rk0001', 'info:name', 'info:age'

4、查看rowkey指定多个列族的信息

获取user表中row key为rk0001,info、data列族的信息

hbase(main):018:0> get 'user', 'rk0001', 'info', 'data'

或者你也可以这样写

hbase(main):019:0> get 'user', 'rk0001', {COLUMN => ['info', 'data']}

或者你也可以这样写,也行

hbase(main):020:0> get 'user', 'rk0001', {COLUMN => ['info:name', 'data:pic']}

4、指定rowkey与列值查询

获取user表中row key为rk0001,cell的值为zhangsan的信息

hbase(main):030:0> get 'user', 'rk0001', {FILTER => "ValueFilter(=, 'binary:zhangsan')"}

5、指定rowkey与值模糊查询

获取user表中row key为rk0001,列标示符中含有a的信息

hbase(main):031:0> get 'user', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}

继续插入一批数据

hbase(main):032:0> put 'user', 'rk0002', 'info:name', 'fanbingbing'

hbase(main):033:0> put 'user', 'rk0002', 'info:gender', 'female'

hbase(main):034:0> put 'user', 'rk0002', 'info:nationality', '中国'

hbase(main):035:0> get 'user', 'rk0002', {FILTER => "ValueFilter(=, 'binary:中国')"}

6、查询所有数据

查询user表中的所有信息

scan 'user'

7、列族查询

查询user表中列族为info的信息

scan 'user', {COLUMNS => 'info'}

scan 'user', {COLUMNS => 'info', RAW => true, VERSIONS => 5}

scan 'user', {COLUMNS => 'info', RAW => true, VERSIONS => 3}

8、多列查询

查询user表中列族为info和data的信息

scan 'user', {COLUMNS => ['info', 'data']}

scan 'user', {COLUMNS => ['info:name', 'data:pic']}

9、指定列族与某个列名查询

查询user表中列族为info、列标示符为name的信息

scan 'user', {COLUMNS => 'info:name'}

10、指定列族与列名以及限定版本查询

查询user表中列族为info、列标示符为name的信息,并且版本最新的5个

scan 'user', {COLUMNS => 'info:name', VERSIONS => 5}

11、指定多个列族与按照数据值模糊查询

 查询user表中列族为info和data且列标示符中含有a字符的信息

scan 'user', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}

12、rowkey的范围值查询

查询user表中列族为info,rk范围是[rk0001, rk0003)的数据

scan 'user', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

13、指定rowkey模糊查询

查询user表中row key以rk字符开头的

scan 'user',{FILTER=>"PrefixFilter('rk')"}

14、指定数据范围值查询

查询user表中指定范围的数据

scan 'user', {TIMERANGE => [1392368783980, 1392380169184]}

7.Hbase 总结一:

  1. Hbase的功能和应用场景是什么?
    功能:能够实现实时分布式随机数据存储
    场景:大量的结构化数据,实时,随机,持久化存储
  2. Hbase的基本存储结构是什么?
    设计:分布式大量数据实时存储
              分布式内存【进程】+分布式磁盘【HDFS】
    实现:NameSpace:类似于数据库概念,访问表的时候必须加上NS
               Table:就是表概念,表是分布式的,一张表可以有多个分区Region,每个分区可以                           存储在不同的节点上
                Rowkey:类似于主键的概念,唯一标识一行,作为Hbase的唯一索引,每张表都自                                    带一列,值由用户自定义
               ColumnFamily:对列的分组,将不同的列分到不同的组中,用于加快查询效率,任                                         何一列都必须数据某个列族
               Qualifier:列标签,按照普通的列名称进行标记,不许使用列族+对应的列的名称才                                能唯一标记一列
                VERSIONS:列族级别,可以指定某个列族下的列允许存储多个版本的值,默认只                                      查询最新版本,根据timestamp来区分
    存储:KV结构:一列就是一条KV数据
               K:rowkey+cf+col+ts。所有的Kiev写入底层存储都按照K进行排序
     
               V:   值。存储类型:字节
  3. Hbase的架构和角色是什么?
    1.Hbase:分布式主从架构
    主:HMaster:管理
    从:HRegionServer:存储:构建分布式内存
    2.Zooleeper:辅助Master选举,存储管理元数据
    3.HDFS:构建分布式磁盘
  4. Hbase的常用命令是什么?
    DDL:create_namespace,list_namespace.create[表名+列族],drop,exist,desc,disable,enable
    DML:
    put 'ns:tbname','rowkey','cf:col',value
    delete 'ns:tbname','rowkey','cf:col'
    get 'ns:tbname','rowkey','cf:col'
    scan 'ns:tbname',Filter
  5. Hbase的JavaAPI如何实现DDL
    1.构建连接:Connection
    2.构建操作:DDL:HbaseAdmin   DML:Table
    3.释放资源

8.JavaAPI

DML:Table

使用Hbase API实现Table的实例开发

DML的所有操作都必须构建Hbase表的对象进行操作

代码:

 public Table getHbaseTable() throws IOException {
        TableName tbname = TableName.valueOf("itcast:t1");
        Table table = conn.getTable(tbname);
        return table;
    }

DML:Put

实现Put插入或者更新数据

代码:

 @Test
    public void testPut() throws IOException {
        Table table = getHbaseTable();
        //构建Put对象,一个Put对象表示写入一个Rowkey的数据
        Put put = new Put(Bytes.toBytes("20210101_001"));
        //添加列的信息
        put.addColumn(Bytes.toBytes("basic"),Bytes.toBytes("name"),Bytes.toBytes("laoda"));
        put.addColumn(Bytes.toBytes("basic"),Bytes.toBytes("age"),Bytes.toBytes("18"));
        put.addColumn(Bytes.toBytes("other"),Bytes.toBytes("phone"),Bytes.toBytes("110"));
        //执行Put
        table.put(put);
        table.close();
    }

小结:

  • 调用:Table.put(Put)

  • Put:Put操作类对象

  • new Put(rowkey)

    • .addColumn(cf,col,value)

DML:Get

实现Get读取数据

先用命令行插入数据,便于测试:

put 'itcast:t1','20210201_000','basic:name','laoda'
put 'itcast:t1','20210201_000','basic:age',18

put 'itcast:t1','20210101_001','basic:name','laoer'
put 'itcast:t1','20210101_001','basic:age',20
put 'itcast:t1','20210101_001','basic:sex','male'

put 'itcast:t1','20210228_002','basic:name','laosan'
put 'itcast:t1','20210228_002','basic:age',22
put 'itcast:t1','20210228_002','other:phone','110'

put 'itcast:t1','20210301_003','basic:name','laosi'
put 'itcast:t1','20210301_003','basic:age',20
put 'itcast:t1','20210301_003','other:phone','120'
put 'itcast:t1','20210301_003','other:addr','shanghai'

代码: 

 @Test
    public void testGet() throws IOException {
        Table table = getHbaseTable();
        //构建Get:get tbname,rowkey,[cf:col]
        Get get = new Get(Bytes.toBytes("20210301_003"));
        //配置Get
        get.addFamily(Bytes.toBytes("basic"));//指定列族读取
//        get.addColumn()//指定列读取
        //执行:一个Result代表一个Rowkey的数据
        Result result = table.get(get);
        //打印这个rowkey每一列的结果:一个Cell对象就是一列的对象:20210301_003 column=other:phone, timestamp=1624590747738, value=120
        for(Cell cell : result.rawCells()){
            System.out.println(
                    Bytes.toString(CellUtil.cloneRow(cell)) + "\t" +
                    Bytes.toString(CellUtil.cloneFamily(cell)) + "\t" +
                    Bytes.toString(CellUtil.cloneQualifier(cell)) + "\t" +
                    Bytes.toString(CellUtil.cloneValue(cell)) + "\t" +
                    cell.getTimestamp()
            );
        }
        table.close();
    }

小结:

  • Get:实现Get操作对象

  • new Get(rowkey)

    • .addFamily:指定读取这个rowkey的列族

    • .addColumn:指定读取某一列

  • table.get(Get):实现get操作

  • Result:一个Result代表一个Rowkey的数据

    • rawCells/listCells

  • Cell:一个Cell代表一列的数据

  • CellUtil:用于对Cell取值的工具类

    • cloneValue

    • cloneRow

    • cloneQualifier
    • cloneFamily

DML:Delete

实现Delete删除数据

代码:

 @Test
    public void testDel() throws IOException {
        Table table = getHbaseTable();
        //构建Delete
        Delete del = new Delete(Bytes.toBytes("20210301_003"));
        //删除列族
//        del.addFamily()
        //删除列
        del.addColumn(Bytes.toBytes("other"),Bytes.toBytes("phone"));
//        del.addColumns(Bytes.toBytes("other"),Bytes.toBytes("phone"));//删除所有版本
        //执行删除
        table.delete(del);
        table.close();
    }

小结:

  • Delete:删除操作对象

    • .addColumn:指定删除的列

  • table.delete(Delete)

DML:Scan

实现Scan读取数据

代码:

  @Test
    public void testScan () throws IOException {
        Table table = getHbaseTable();
        //构建Scan对象
        Scan scan = new Scan();
        //执行scan:ResultScanner用于存储多个Rowkey的数据,是Result的集合
        ResultScanner scanner = table.getScanner(scan);
        //取出每个Rowkey的数据
        for (Result result : scanner) {
            //先打印当前这个rowkey的内容
            System.out.println(Bytes.toString(result.getRow()));
            for(Cell cell : result.rawCells()){
                System.out.println(
                        Bytes.toString(CellUtil.cloneRow(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneFamily(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneQualifier(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneValue(cell)) + "\t" +
                                cell.getTimestamp()
                );
            }
            System.out.println("------------------------------------------------------------");
        }
        table.close();
    }

存储设计:Table,Region,RS的关系

  • 问题:客户端操作的是表,数据最终存在RegionServer中,表和RegionServer的关系是什么?

  • 分析

    • Table:是一个逻辑对象,物理上不存在,供用户实现逻辑操作,存储在元数据的一个概念

      • 数据写入表以后的物理存储:分区

      • 一张表会有多个分区Region,每个分区存储在不同的机器上

      • 默认每张表只有1个Region分区

    • Region:Hbase中数据负载均衡的最小单元

      • 类似于HDFS中Block,Kafka中Partition、用于实现Hbase中分布式

      • 就是分区的概念,每张表都可以划分为多个Region,实现分布式存储

        • 默认一张表只有一个分区

      • 每个Region由一台RegionServer所管理,Region存储在RegionServer

      • 一台RegionServer可以管理多个Region

    • RegionServer:是一个物理对象,Hbase中的一个进程,管理一台机器的存储

      • 类似于HDFS中DataNode或者Kafka中的Broker

      • 一个Regionserver可以管理多个Region

      • 一个Region只能被一个RegionServer所管理

  • 小结
    Hbase中Table与Region,RS三者的关系是什么?
    1.Table:提供用户读写的逻辑概念,并不是实际存在的
    2.Region:分区的概念,一张表可以划分为多个分区,每个分区都被某一台RegionServer所管理
    3.RegionServer:真正的存储数据的物理概念

存储设计:Region及数据的划分规则

  • 回顾:划分规则
  1. HDFS:划分分区规则:按照大小划分,文件按照每128M划分为一个Block
  2. Redis:将0~16383划分为多个段,每个小的集群分配一个段的内容
  3. Kafka:自己制定一个Topic有多少个分区,数据分配规则:指定分区,按照Key的hash区域,或者粘性分区,自定义分区
  • Hbase分区划分规则:范围划分【根据Rowkey范围】
           任何一个Region都会对应一个范围,
    • 如果只有一个Region,范围:-oo ~ +oo
      • 范围划分:从整个-oo ~ +oo区间上进行范围划分

        • 每个分区都会有一个范围:根据Rowkey属于哪个范围就写入哪个分区

          [startKey,stopKey)
          • 前闭后开区间

      • 默认:一张表创建时,只有一个Region

        • 范围:-oo ~ +oo

      • 自定义:创建表时,指定有多少个分区,每个分区的范围

        • 举个栗子:创建一张表,有2个分区Region

          create 'itcast:t3',{SPLITS => [50]}
      • region0:-oo ~ 50

      • region1:50 ~ +oo

    • 数据分配的规则:==根据Rowkey属于哪个范围就写入哪个分区==

      • 举个栗子:创建一张表,有4个分区Region,20,40,60

        create 'itcast:t3',{SPLITS => [20,40,60]}
      • 前闭后开

      • region0:-oo ~ 20

        • region1:20 ~ 40

        • region2:40 ~ 60

        • region3:60 ~ +oo

      • 写入数据的rowkey:比较是按照ASC码比较的,不是数值比较

      • 例如以下划分举例

      •         A1234:region3

        • c6789:region3

        • 00000001:region0

        • 2:region1

        • 99999999:region3

        • 9:region3

      • 比较规则:ASCII码前缀逐位比较

  • 小结
  1. 分区划分规则:将整个-00到+00区间进行划分,划分多个分区段
    根据Rowkey进行划分
  2. 数据分区规则:根据rowkey的asc码逐位匹配,rowkey属于那个范围,就写入那个分区

存储设计:Region的内部结构

  • 数据在Region的内部是如何存储的?
    put tbname,rowkey,cf:col,value
  1. tbname:决定了这张表的数据最终要读写那些分区
  2. rowkey:决定了具体读写哪个分区
  3. cf:决定了具体写入哪个Store
  • Table/RegionServer:数据指定写入哪张表,提交给对应的某台regionserver
  • Region:对整张表的数据划分,按照范围划分,实现分步式存储
  • Store:对分区的数据进行划分,按照列族划分,一个列族对应一个Store
    不同列族的数据写入不同的Store中,实现了按照列族将列进行分组
    根据用户查询时指定的列族,可以快速的读取对应的store
    MemStore:每个Store都有一个,内存存储区域
    数据写入memstore后直接返回
  • StoreFile:每个Store中可能有0个或者多个StoreFile文件
    逻辑上:Store
    物理上:HDFS:HFILE(二进制文件)

  • HDFS中的存储
  1. 问题:Hbase的数据是如何在HDFS中存储的?
  2. 分析:整个Hbase在HDFS中的存储目录

    hbase.rootdir=hdfs://node1:8020/hbase
  3. NameSpace:目录结构

  4. Table:目录结构

  5. Region:目录结构

  6. Store/ColumnFamily:目录结构

  7. StoreFile

    • 如果HDFS上没有storefile文件,可以通过flush,手动将表中的数据从内存刷写到HDFS中

      flush 'itcast:t3'
      
  • 小结

    • Region的内部存储结构是什么样的?

      • NS:Table|RegionServer:整个Hbase数据划分

        • Region:划分表的数据,按照Rowkey范围划分

          • Store:划分分区数据,按照列族划分

            • MemStore:物理内存存储

            • StoreFile:物理磁盘存储

              • 逻辑:Store

              • 物理:HDFS[HFile]

Hbase读写流程:基本流程

  • 实施

    • step1:根据表名获取这张表对应的所有Region的信息

      • 整个Hbase的所有Regionserver中有很多个Region:100

      • 先根据表名找到这张表有哪些region:5

    • step2:根据Rowkey判断具体写入哪个Region

      • 知道了这张表的所有region

      • 根据rowkey属于哪个region范围,来确定具体写入哪个region

    • step3:将put操作提交给这个Region所在的RegionServer

      • 获取这个Region所在的RegionServer地址

    • step4:RegionServer将数据写入Region,根据列族判断写入哪个Store

    • step5:将数据写入MemStore中

  • 小结

    • 表名:决定了这条数据要写入哪些region中

    • Rowkey:决定了这条数据具体写入哪个Region中

    • 列族:决定了写入这个region哪个Store中

Hbase读写流程:meta表

  • 实施

    • Hbase自带的两张系统表

      • hbase:namespace:存储了Hbase中所有namespace的信息

      • hbase:meta:存储了表的元数据

    • hbase:meta表结构

      • Rowkey:每张表每个Region的名称

        itcast:t3,20,1632627488682.fba4b18252cfa72e48ffe99cc63f7604
        表名,startKey,时间,唯一id
    • Hbase中每张表的每个region对应元数据表中的一个Rowkey

        • info:regioninfo

          STARTKEY => 'eeeeeeee', ENDKEY => ''
        • info:server/info:sn

          column=info:sn, timestamp=1624847993004, value=node1,16020,1624847978508  
    • 实现

      • 根据表名读取meta表,基于rowkey的前缀匹配,获取这张表的所有region信息

  • 小结

    • meta表的功能是什么?

      • 存储了表的元数据

    • 每个Rowkey代表了一个Region的信息

      • Region的范围

      • Region的地址

Hbase读写流程:写入流程

  • 实施

    • step1:获取表的元数据

      • ==先连接zk,从zk获取meta表所在的regionserver==

      • 根据查询的表名读取meta表,获取这张表的所有region的信息

        • meta表是一张表,数据存储在一个region,这个region存在某个regionserver上

        • 怎么知道meta表的regionserver在哪?

        • 这个地址记录在ZK中

      • 得到这张表的所有region的范围和地址

    • step2:找到对应的Region

      • 根据Rowkey和所有region的范围,来匹配具体写入哪个region

      • 获取这个region所在的regionserver的地址

    • step3:写入数据

      • 请求对应的regionserver

      • regionserver根据提交的region的名称和数据来操作对应的region

      • 根据列族来判断具体写入哪个store

        • ==先写WAL==:write ahead log

          • 为了避免内存数据丢失,所有数据写入内存之前会

          • 先记录这个内存操作

        • 然后写入==这个Store的Memstore中==

    • 思考:hbase的region没有选择副本机制来保证安全,如果RegionServer故障,Master发现故障,怎么保证数据可用性?

      • step1:Master会根据元数据将这台RegionServe中的Region恢复到别的机器上

      • step2:怎么实现数据恢复?

        • Memstore:WAL进行恢复

          • 怎么保证WAL安全性:WAL记录在HDFS上

        • StoreFile:HDFS有副本机制

Hbase读写流程:读取流程

  • 实施
  1. 获取元数据
    客户端请求Zookeeper,获取meta表所在的regionserver的地址
    读取meta表的数据
    注意:客户端会缓存meta表的数据,只有第一次会连接ZK,读取meta表的数据,缓存会定期失效,要重新缓存,避免每次请求都要先连接zk,再读取meta表
  2. 找到对应的Region
    根据meta表中的元数据,找到表对应的region
    根据region的范围和读取的RowKey,判断需要读取具体哪一个Region
    根据region的RegionServer地址,请求对应的RegionServer
  3. 读取数据
    先查询memstore,如果开启了缓存,就读BlockCache,如果缓存中没有,就读storefile,从storefile读取完成之后,放入缓存中,如果没有缓存,就读StoreFile
    第一次查询一定是先度memstore,然后storefile如果开启了缓存,就将这次读取到的数据放到缓存中
     

LSM模型:Flush

  • 功能:将内存memstore中的数据溢写到HDFS中变成磁盘文件storefile【HFILE】
  • 关闭集群:自动Flush

  • 参数配置:自动触发机制
     

    #2.x版本以后的机制
    #设置了一个flush的最小阈值
    #memstore的判断发生了改变:max("hbase.hregion.memstore.flush.size / column_family_number",hbase.hregion.percolumnfamilyflush.size.lower.bound.min)
    #如果memstore高于上面这个结果,就会被flush,如果低于这个值,就不flush,如果整个region所有的memstore都低于,全部flush
    #水位线 = max(128 / 列族个数,16),列族一般给3个 ~ 42M
    #如果memstore的空间大于42,就flush,如果小于就不flush,如果都小于,全部flush
    举例:3个列族,3个memstore,90/30/30   90会被Flush
    举例:3个列族,3个memstore,30/30/30  全部flush
    hbase.hregion.percolumnfamilyflush.size.lower.bound.min=16M
    #2.x中多了一种机制:In-Memory-compact,如果开启了【不为none】,会在内存中对需要flush的数据进行合并
    #合并后再进行flush,将多个小文件在内存中合并后再flush
    hbase.hregion.compacting.memstore.type=None|basic|eager|adaptive

    小结

  • Hbase利用Flush实现将内存数据溢写到HDFS,保持内存中不断存储最新的数据

  • 注意:工作中一般进行手动Flush

    • 原因:避免大量的Memstore将大量的数据同时Flush到HDFS上,占用大量的内存和磁盘的IO带宽,会影响业务

    • 解决:手动触发,定期执行

      hbase> flush 'TABLENAME'
      hbase> flush 'REGIONNAME'
      hbase> flush 'ENCODED_REGIONNAME'
      hbase> flush 'REGION_SERVER_NAME'
    • 封装一个文件,通过hbase shell filepath来定期的运行这个脚本

LSM模型:Compaction

    • 实现

      • 功能:什么是Compaction?

        • 将多个单独有序StoreFile文件进行合并,合并为整体有序的大文件,加快读取速度

        • file1:1 2 3 4 5

        • file2:6 7 9

        • file3 :1 8 10

        • || 每个文件都读取,可能读取无效的数据

        • file:1 1 2 3 4 5 6 7 8 9 10

      • 版本功能

        • 2.0版本之前,只有StoreFile文件的合并

          • 磁盘中合并:minor compaction、major compaction

        • 2.0版本开始,内存中的数据也可以先合并后Flush

          • 内存中合并:In-memory compaction

          • 磁盘中合并:minor compaction、major compaction

      • In-memory compaction:2.0版本开始新增加的功能

        • 原理:将当前写入的数据划分segment【数据段】

          • 当数据不断写入MemStore,划分不同的segment,最终变成storefile文件

        • 如果开启了内存合并,先将第一个segment放入一个队列中,与其他的segment进行合并

          • 合并以后的结果再进行flush

        • 内存中合并的方式

          hbase.hregion.compacting.memstore.type=None|basic|eager|adaptive
          none:不开启,不合并
        • basic(基础型)

          Basic compaction策略不清理多余的数据版本,无需对cell的内存进行考核
          basic适用于所有大量写模式
        • eager(饥渴型)

          eager compaction会过滤重复的数据,清理多余的版本,这会带来额外的开销
          eager模式主要针对数据大量过期淘汰的场景,例如:购物车、消息队列等
        • adaptive(适应型)

          adaptive compaction根据数据的重复情况来决定是否使用eager策略
          该策略会找出cell个数最多的一个,然后计算一个比例,如果比例超出阈值,则使用eager策略,否则使用basic策略
          
      • minor compaction:轻量级

        • 功能:将最早生成的几个小的StoreFile文件进行合并,成为一个大文件,不定期触发

        • 特点

          • 只实现将多个小的StoreFile合并成一个相对较大的StoreFile,占用的资源不多

          • 不会将标记为更新或者删除的数据进行处理

        • 属性

          hbase.hstore.compaction.min=3
          
      • major compaction:重量级合并

        • 功能:将整个Store中所有StoreFile进行合并为一个StoreFile文件,整体有序的一个大文件

        • 特点

          • 将所有文件进行合并,构建整体有序

          • 合并过程中会进行清理过期和标记为删除的数据

          • 资源消耗比较大

        • 参数配置

          hbase.hregion.majorcompaction=7天
          
    • 小结

      • Hbase通过Compaction实现将零散的有序数据合并为整体有序大文件,提高对HDFS数据的查询性能

      • 在工作中要避免自动触发majorcompaction,影响业务

        hbase.hregion.majorcompaction=0
      • 在不影响业务的时候,手动处理,每天在业务不繁忙的时候,调度工具实现手动进行major compact

        Run major compaction on passed table or pass a region row
                  to major compact an individual region. To compact a single
                  column family within a region specify the region name
                  followed by the column family name.
                  Examples:
                  Compact all regions in a table:
                  hbase> major_compact 't1'
                  hbase> major_compact 'ns1:t1'
                  Compact an entire region:
                  hbase> major_compact 'r1'
                  Compact a single column family within a region:
                  hbase> major_compact 'r1', 'c1'
                  Compact a single column family within a table:
                  hbase> major_compact 't1', 'c1'
                  Compact table with type "MOB"
                  hbase> major_compact 't1', nil, 'MOB'
                  Compact a column family using "MOB" type within a table
                  hbase> major_compact 't1', 'c1', 'MOB'

Region分裂Split设计及规则

  • 分析

    • 什么是Split分裂机制?

      • 为了避免一个Region存储的数据过多,提供了Region分裂机制

      • 实现将一个Region分裂为两个Region

      • 由RegionServer实现Region的分裂,得到两个新的Region

      • 由Master负责将两个新的Region分配到Regionserver上

  • 实现

    • 参数配置
       

      #规则:return tableRegionsCount  1  ? this.initialSize : getDesiredMaxFileSize();
      #判断region个数是否为1,如果为1,就按照256分,如果不为1,就按照10GB来分
      hbase.regionserver.region.split.policy=org.apache.hadoop.hbase.regionserver.SteppingSplitPolicy
  • 小结

    • Hbase通过Split策略来保证一个Region存储的数据量不会过大,通过分裂实现分摊负载,避免热点,降低故障率

    • 注意:工作作中避免自动触发,影响集群读写,建议关闭

      hbase.regionserver.region.split.policy=org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy
    • 手动操作

      split 'tableName'
      split 'namespace:tableName'
      split 'regionName' # format: 'tableName,startKey,id'
      split 'tableName', 'splitKey'
      split 'regionName', 'splitKey'

热点问题:现象及原因

  • 现象

    在某个时间段内,大量的读写请求全部集中在某个Region中,导致这台RegionServer的负载比较高,其他的Region和RegionServer比较空闲
  • 原因:本质上的原因,数据分配不均衡
    • 情况一:Region范围不合理

      • Rowkey:字母开头

      • Region:3个分区

        • region0:-oo ~ 30

        • region1:30 ~ 70

        • region2:70 ~ +oo

      • 所有数据都写入了region2

    • 情况二:如果这张表有多个分区,而且你的Rowkey写入时是连续的

      • 一张表有5个分区

        region0:-oo  20
        region1:20   40
        region2:40   60
        region3:60    80
        region4:80    +oo
      • 000001:region0

      • 000002:region0

      • ……

      • 199999:region0

      • 都写入了同一个region0分区

      • 200000:region1

      • 200001:region1

      • ……

      • 399999:region1

    • 情况三:如果这张表只有一个分区

      • 所有数据都存储在一个分区中,这个分区要响应所有读写请求,出现了热点

    • 解决:避免热点的产生

      • 构建多个分区,分区范围必须要Rowkey设计

      • 构建不连续的rowkey

分布式设计:预分区

  • 实施

    • 需求:在创建表的时候,指定一张表拥有多个Region分区

    • 规则

      • 划分的目标:划分多个分区,实现分布式并行读写,将无穷区间划分为几段,将数据存储在不同分区中,实现分区的负载均衡

      • 划分的规则==Rowkey或者Rowkey的前缀来划分==

        • 如果不按照这个规则划分,预分区就可能没有作用

        • Rowkey:00 ~ 99

          • region0: -oo ~ 30

          • ……

          • regionN : 90 ~ +oo

        • 如果分区的设计不按照rowkey来

          • region0:-oo ~ b

          • region1: b ~ g

          • ……

          • regionN:z ~ +oo

    • 实现

      • 方式一:指定分隔段,实现预分区

        • 前提:先设计rowkey

        create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40']
        #将每个分割的段写在文件中,一行一个
        create 't1', 'f1', SPLITS_FILE => 'splits.txt'
      • 方式二:指定Region个数,自动进行Hash划分:==字母和数字的组合==

        #你的rowkey的前缀是数字和字母的组合 
        create 'itcast:t4', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
        
      • 方式三:Java API

        HBASEAdmin admin = conn.getAdmin
        admin.create(表的描述器对象,byte[][] splitsKey)
  • 小结

    • 实现建表时指定多个分区

Hbase表设计:Rowkey设计

设计规则

  • 业务原则:Rowkey的设计必须贴合业务的需求,一般选择最常用的查询条件作为rowkey的前缀

    • 数据

      oid     uid     pid     stime ……
    • Rowkey:stime

      rowkey                  oid     uid     pid     stime ……
      20211201000000          o001    u001    p001    2021-12-01 00:00:00
    • 按照时间就走索引

  • 唯一原则:Rowkey必须具有唯一性,不能重复,一个Rowkey唯一标识一条数据

  • 组合原则:将更多的经常作为的查询条件的列放入Rowkey中,可以满足更多的条件查询可以走索引查询

    • Rowkey:stime

      • 缺点:不唯一、只有按照时间才走索引

    • Rowkey:stime_uid_oid

      rowkey                              oid     uid     pid     stime ……
      20211201000000_u001_o001            o001    u001    p001    2021-12-01 00:00:00
      20211201000000_u002_o002            o001    u001    p001    2021-12-01 00:00:00
      ……
      20211201000000_u00N_o00N            o001    u001    p001    2021-12-01 00:00:00
      ……
      20211201000001_u001_o001            o001    u001    p001    2021-12-01 00:00:00
      • 订单id唯一、一个用户在同一时间只能下一个订单

      • 索引查询:stime、s_time+uid、stime+uid+oid

      • 想查询订单id为001:不走

        • 全表扫表:对列的值进行过滤:SingleColumnValueFilter

  • 散列原则:为了避免出现热点问题,需要将数据的rowkey生成规则构建散列的rowkey

    • 举个栗子:一般最常用的查询条件肯定是时间

      • timestamp_userid_orderid:订单表

        1624609420000_u001_o001
        1624609420001_u002_o002
        1624609420002_u003_o003
        1624609421000_u001_o004
        ……
      • 预分区:数值

        • region0:-oo ~ 1624

        • region1:1624 ~ 1924

        • region2:1924 ~ 2100

        • region3:2100 -2400

        • region4:2400 ~ +oo

      • 问题:出现热点

    • 解决:构建散列

      • 方案一:更换不是连续的字段作为前缀,例如用户id

        dfd342
        3432sd
        • 缺点:可能与业务原则产生冲突

      • 方案二:反转

        • 一般用于时间作为前缀,查询时候必须将数据反转再查询

        0000249064261_u001_o001
        1000249064261_u002_o002
        2000249064261_u003_o003
        0010249064261_u001_o004
        ……
        • region0:-oo ~ 2

        • region1:2 ~ 4

        • region2:4 ~ 6

        • region3:6 ~ 8

        • region4:8 ~ +oo

      • ==方案三:加盐(Salt)==,本质对数据进行编码,生成数字加字母组合的结果

        1624609420000_u001_o001
        1624609420001_u002_o002
        1624609420002_u003_o003
        1624609421000_u001_o004
        |
        df34343jed_u001_o001
        09u9jdjkfd_u002_o002
        • 缺点:查询时候,也必须对查询条件加盐以后再进行查询

  • 长度原则:在满足业务需求情况下,rowkey越短越好,一般建议Rowkey的长度小于100字节

    • 原因:rowkey越长,比较性能越差,rowkey在底层的存储是冗余的

    • 问题:为了满足组合原则,rowkey超过了100字节怎么办?

    • 解决:实现编码,将一个长的rowkey,编码为8位,16位,32位

小结:

rowkey的设计要符合以下原则:

  • 业务原则:Rowkey设计贴合实际业务需求,尽量使用最常用的查询条件作为前缀
  • 唯一原则:每个Rowkey唯一标识是一条数据
  • 组合原则:尽量将更多的常用条件放入rowkey中
  • 散列原则:构建不连续的Rowkey
  • 长度原则:在满足上面原则的情况,rowkey越短越好

Hbase表设计:其他设计

  • NS的设计:类似于数据库名称的设计,明确标识每个业务域,一般包含业务域名称
  • 表明设计:类似于数据库中表明的设计包含业务名称即可
  • 列族设计:名称没有太多意义,个数建议一般不超过3个
  • 标签设计:按照实际业务字段名称标识即可,建议缩写,避免过长

BulkLoad的介绍

  • 问题:有一批大数据量的数据,要写入Hbase中,如果按照传统的方案来写入Hbase,必须先写入内存,然后内存溢写到HDFS,导致Hbase的内存负载和HDFS的磁盘负载过高,影响业务
  • 解决:
  • 方式一:构建Put对象,先写内存

  • 方式二:BulkLoad,直接将数据变成StoreFile文件,放入Hbase对应的HDFS目录中

    • 数据不经过内存,读取数据时可以直接读取到

  • 步骤:先将要写入的数据转换为HFILE文件,然后将HFILE加载到Hbase表中

  • 特点
    优点:不经过内存,降低了内存和磁盘的IO吞吐
    缺点:性能上相对来说要慢,所有的数据都不会在内存中被读取

上一篇:Unity功能——通过按键设置物体朝左/右旋转(含C#转xlua版)


下一篇:Flink本地idea运行环境配置webui