美文网首页大数据
分布式列式数据库 —— HBase

分布式列式数据库 —— HBase

作者: 小胡_鸭 | 来源:发表于2022-03-23 22:32 被阅读0次

    一、BigTable

    1、产生背景

      使用传统的关系型数据库时,需要根据应用系统设计一张张的数据表,数据表可以看成是一个实体(Entity),也可以看成是实体之间存在的联系(Relationship),为了实现 ACID 的特性,数据库需要添加很多的约束,处理的逻辑很重。

      当数据量达到几千万甚至上亿,并发量达到几百时,数据库的查询性能会急剧下降,即使使用了分表分库、在应用和数据库中间加一层缓存,甚至想方设法各种 “骚操作” 来深度优化数据库,都无法从根本上解决关系型数据库的容量和性能瓶颈,特别是针对大数据的应用场景更是捉襟见肘。

      我们无法同时满足 CAP 特性,又希望又性能容量上可以水平扩展,这时候需要换一种思路,降低对数据库的事务性约束和要求,谷歌的 BigTable 就是在这种背景下应运而生。


    2、什么是 BigTable

      BigTable 是一个分布式的结构化的数据存储系统,为处理海量数据而设计,可以扩展到PB级数据和上千台服务器。在 google 的很多产品中被使用,这些应用对 Bigtable提出了不同的挑战,比如数据规模的要求、延迟的要求。Bigtable能满足这些多变的要求,为这些产品成功地提供了灵活、高性能的存储解决方案。

      Bigtable看起来像一个数据库,采用了很多数据库的实现策略。但是Bigtable并不支持完整的关系型数据模型;而是为客户端提供了一种简单的数据模型,客户端可以动态地控制数据的布局和格式,并且利用底层数据存储的局部性特征。Bigtable将数据统统看成无意义的字节串,客户端需要将结构化和非结构化数据串行化再存入Bigtable。


    3、BigTable 的数据模型

      Bigtable不是关系型数据库,但是却沿用了很多关系型数据库的术语,像table(表)、row(行)、column(列)等。在理解 BigTable 的数据模型时最好不要代入关系型数据库的概念,这有助于我们更好地理解什么是 BigTable。

      Google 的论文中这样描述:“BigTable 是一个稀疏的、分布式的、持久化存储的多维度排序 Map”。

      首先 Map 就是一个键值对(key-value)的映射,多维度指 BigTable 的 key 是三维的,即行键(row key)、列键(column key)和时间戳(timestamp),行键和列键都是字节串,时间戳是64位整型;而值是一个字节串。

      可以用以下描述来表示一条数据。

    (row:string, column:string, time:int64)→string
    

      稀疏指同个表不同的行,列可能完全不一样;分布式指 BigTable 的数据存储基于 GFS,GFS 是一个分布式文件系统,除此之外,BigTable 本身也是一个主从分布式架构;持久化指 BigTable 的数据最终会以文件的形式保存到 GFS 磁盘中。

      既然 key 是多维的,就能建立多级索引。

      行是表的第一级索引,如果把该行的列、时间和值看成一个整体,就可以简化为一维键值映射,类似于:

    table {
      "1" : {"v1"},
      "2" : {"v2"},
      ...
    }
    

      列是第二级索引,每行拥有的列是不受限制的,可以随时增加减少。为了方便管理,列被分为多个列族(column family,是访问控制的单元),一个列族里的列一般存储相同类型的数据。一行的列族很少变化,但是列族里的列可以随意添加删除。列键按照family:qualifier格式命名的。这次我们将列拿出来,将时间和值看成一个整体,简化为二维键值映射,类似于:

    student {
      "1" : {
        "info:name" : {"Tom"},
        "info:age" : {"20"},
        "grade:math" : {"100"}
      },
      "2" : {
        "info:name" : {"Jerry"},
        "info:age" : {"21"},
        "grade:math" : {"90"}
      },  
    }
    

      也可以将列族当作一层新的索引,类似于:

    student {
      "1" : {
        "info" : {
          "name" : {"Tom"},
          "age" : {"20"}
        },
        "grade" : {
          "math" : "100"
        }
      }
    }
    

      时间戳是第三级索引。Bigtable允许保存数据的多个版本,版本区分的依据就是时间戳。时间戳可以由Bigtable赋值,代表数据进入Bigtable的准确时间,也可以由客户端赋值。数据的不同版本按照时间戳降序存储,因此先读到的是最新版本的数据。我们加入时间戳后,就得到了Bigtable的完整数据模型,类似于:

    student {
      "1" : {
        "info:name" : {
            1 : "Tom
        },
        "info:age" : {
            1 : "20"
        },
        "grade:math" : {
            7 : "90",
            10 : "100"
        }
      },
    }
    

      利用时间戳提供的多版本特性,可以查询指定版本的数据。查询时,如果只给出行列,那么返回的是最新版本的数据;如果给出了行列时间戳,那么返回的是时间小于或等于时间戳的数据。

      如查询 "1"/"grade:math",返回的值是 "100";查询 "1"/"grade:math"/7,返回的值是 "90";如果查询 "1"/"grade:math"/5,返回的结果为空。

    【参考】https://dzone.com/articles/understanding-hbase-and-bigtab

      HBase 的设计和实现借鉴了 BigTable 的思想和理念。



    二、列式存储 vs 行式存储

      组织关系数据库有两种方法:

    • 行式存储
    • 列式存储

      行式数据库中通过记录来组织数据,使所有数据与在内存中彼此相邻的记录相关联。行式数据库是传统组织数据的方式,并且仍然为快速存储数据提供一些关键优势,为读写进行了有效的优化。

      传统的关系型数据库都是行式存储:

    • Oracle
    • MySQL

      列式存储数据库是按照字段来组织数据的,使所有数据与在内存中彼此相邻的字段相关联。列式存储数据库已经变得流行,并且在数据查询上有更好的表现,为列上的读和计算进行了有效的优化。

      常见的列式存储数据库有:

    1、行式存储

      传统的数据库管理系统被设计用来存储数据。在读写一行数据上做了很多优化,这导致一系列的选择包括行存储的架构。

      在列式存储中,数据一行行存储,一行的第一列紧跟在上一行的最后一列后面。

      以 Facebook_Friends 表数据为例

      在行式数据库中,数据会被按照顺序逐行存储在磁盘上。

      这使得写入一行数据是很快的,因为需要完成的所有内容都要写入数据的另一行到达数据的末尾。

    行式数据库中的写

      现在要添加一行,只需要在数据文件后追加。

      行式数据库依然广泛地用于联机事务处理(OLTP)类型的应用,因为能够很好地写入数据库。但是数据库的另外一个使用场景是不需要事务性处理的数据分析,联机分析处理(OLAP)需要数据库能够支持对数据的即席(ad hoc)查询,在这个场景下,行式数据库比列式存储数据库要慢。****

    行式数据库中的读

      

      假设每个磁盘只能存储三个列,则上述数据需要3个磁盘存储。

      现在要计算所有人的年龄之和,那么必须读取每个磁盘的每一行,并且加载到内存中,虽然 name、city 这两行每用到,依然会作为一行数据整体加载到内存中,磁盘和内存的开销都比较大。


    2、列式存储

      不同行的相同列会放在一起存储

      发生一行新的记录的写的时候,必须找到每一列的位置再插入,很明显,写性能不如行式存储

      假设每个磁盘只能存储3列,则会按下图方式存储

      同样是计算年龄之和,现在只需要从磁盘3中读取加载数据到内存,并执行运算,磁盘内存的开销都减少了。

      查询时很高效,比如查询所有朋友姓名:select * from facebook_friend;

      行式存储会读取每一行数据并抽取出其中的 name 列,列式存储只需要从磁盘1中读取数据即可。

      注意不同列存储的位置是严格对应的,比如 name 列 Tim 存储在 Dave 之后,那么 Tim 的年龄也应该存储在 Dave 的年龄之后。


    3、总结

      优缺点对比如下:

    行式存储 列式存储
    优点 ① 数据被保存在一起
    ② INSERT/UPDATE容易
    ① 查询时只有涉及到的列会被读取, 投影(projection)很高效
    ② 任何列都能作为索引
    缺点 选择(Selection)时即使只涉及某几列,所有数据也都会被读取 选择完成时,被选择的列要重新组装
    INSERT/UPDATE比较麻烦


    4、数据压缩与查询优化

      我们可以根据列式存储的特点进行数据压缩,简单地说就是利用字典表,每个字符串在字典表里只出现一次,降低数据冗余度,达到压缩的目的。

      利用数据压缩可以优化查询性能

    select * from tab 
    where customer_name = 'Miller' and material = 'Refrigerator';
    

      有点类似于位图索引,针对每个查询条件的列生成一个位图,满足条件的记录对应的位为1,要同时满足多个查询条件,就对位图做与运算,计算结果中为1的位对应的记录就是我们想要的查询结果。



    三、体系架构

    1、Hadoop 生态圈

      在 Hadoop 生态圈中,存储层是 HDFS 和 HBase,HDFS 是最底层的数据存储文件系统,HBase 是基于 HDFS 的 NoSQL 数据库;在存储层之上,是资源调度和计算层 Yarn;基于 Yarn 之上是 MapReduce、Spark 等分布式计算引擎。

      最终的目的,我们是要通过 SQL 就能查询大数据,这就需要数据分析引擎来实现,主要由 Hive、Pig 来完成。

      要分析处理大数据,首先得采集数据,所以需要有数据采集引擎,由 Sqoop、Flume 完成,Sqoop 主要采集关系型数据库的数据,Flume 主要采集日志类型的数据。

      各种组件,为了避免单点故障,还需要实现 HA,通过分布式协调服务 ZooKeeper 实现。

      最后要把所有的组件集成到一个 web 控制台上,方便进行查询、操作和监控,通过集成管理工具 HUE 实现。


    2、HBase 设计

      HBase 是一个主从架构,主节点为 Master,从节点为 RegionServer。

      Master 存储表定义的元信息,RegionServer 直接负责存储表数据(底层存储依赖于 HDFS)。

      RegionServer 非常依赖于 ZooKeeper,ZooKeeper 管理了 HBase 所有 RegionServer 的信息,包括具体的数据段放在哪台 RegionServer 上。当客户端向 HBase 发起请求时,并不是直接请求 Master 或 RegionServer,而是先跟 ZooKeeper 通信,查询出要连接哪个 RegionServer,然后再连接 RegionServer,如下图所示:

      跟一般的主从架构不同,当 Master 故障时,HBase 依然能够提供正常的查询服务,但无法创建、修改和删除表。

      这是因为客户端查询时不需要 Master 的参与,Master 的职责是各种 RegionServer 之间的协调工作,比如建表、删表、移动 Region、合并等操作,它们的共性就是需要跨多个 RegionServer,这些操作由哪个 RegionServer 来执行都不合适,所以 HBase 就将这些操作放到了 Master 上。

      这种结构降低了对 Master 的依赖,减轻 Master 单点故障的影响,集群依然可以正常地运行,依然可以存储和删除数据。



    四、环境搭建

      一般生产环境中,HBase 底层数据存储是依赖于 HDFS 的,而 HDFS 又依赖于 Java 环境,所以在安装搭建 HBase 环境之前,应该先将 Hadoop 和 Java 环境先安装配置好。

      环境机器配置:

    1、本地模式

      本地模式不需要HDFS的支持,直接把数据存储在操作系统中。

      环境:虚拟机 192.168.190.111(bigdata111)

    IP HOST 配置
    192.168.190.111 bigdata111 2C4G
    192.168.190.112 bigdata112 2C4G
    192.168.190.113 bigdata113 2C4G
    192.168.190.114 bigdata114 2C4G

    (1)安装配置

      解压到安装目录

    tar -zxvf hbase-1.3.1-bin.tar.gz -C ~/training/
    

      配置环境变量(~/.bash_profile

    HBASE_HOME=/root/training/hbase-1.3.1
    export HBASE_HOME
    PATH=$HBASE_HOME/bin:$HIVE_HOME/bin:$PATH
    export PATH
    

      修改完后让配置生效

    source ~/.bash_profile
    

    (2)HBase 配置

      HBase 的配置文件目录位于 $HBASE_HOME/conf ,有两个文件需要修改配置。

      第一个是 hbase-env.sh,修改 JAVA_HOME

    ...
    # Set environment variables here.
    
    # This script sets variables multiple times over the course of starting an hbase process,
    # so try to keep things idempotent unless you want to take an even deeper look
    # into the startup scripts (bin/hbase, etc.)
    
    # The java implementation to use.  Java 1.7+ required.
    export JAVA_HOME=/root/training/jdk1.8.0_181
    ...
    

      第二个是 hbase-site.xml,配置 HBase 数据存储的路径

    <configuration>
        <!--数据存储位置-->
        <property>
            <name>hbase.rootdir</name>
            <!--数据保存在本地-->
            <value>file:///root/training/hbase-1.3.1/data</value>
        </property>
    </configuration>
    

    (3)启动

      在 $HBASE_HOME 目录下有很多的脚本,配置好环境变量后可以直接执行,本地模式的 hbase 将数据存储在本地文件系统上,不需要依赖 HDFS,可直接通过以下命令完成启动。

    start-hbase.sh
    

    (4)测试

      HBase shell 是 HBase 自带的命令行工具,可通过该工具测试 HBase 启动使用是否正常。

    # 进入 hbase shell
    [root@bigdata111 bin]# hbase shell
    ...
    # 创建一个 student 表,表中有两个列族 info、grade
    hbase(main):001:0> create 'student','info','grade'
    ...
    # 查看库中有哪些表
    hbase(main):002:0> list
    ...
    # 往表中插入数据
    hbase(main):003:0> put 'student','s01','info:name','Tom'
    ...
    # 查看表中数据
    hbase(main):004:0> scan 'student'
    ...
    # 退出
    hbase(main):005:0> exit
    

      查看保存 hbase 的本地文件目录,可以看到表和列族都会被存储为一个目录

    (5)停止

    stop-hbase.sh
    


    2、伪分布模式

      伪分布模式会在单机上模拟一个分布式的环境,具备HBase所有的功能,多用于开发和测试。

      环境:① HBase - bigdata111
         ② HDFS - bigdata111,bigdata112,bigdata113

    (1)HBase 配置

      修改 hbase-env.sh,启用 HBase 自带的 ZK

    export HBASE_MANAGES_ZK=true
    

      修改 hbase-site.xml

    <configuration>
        <!--数据存储位置-->
        <property>
            <name>hbase.rootdir</name>
            <!--数据保存在HDFS上-->
            <value>hdfs://bigdata111:9000/hbase</value>
        </property>
    
        <!--表示是一个分布式的环境-->
        <property>
            <name>hbase.cluster.distributed</name>
            <value>true</value>
        </property>  
    
        <!--ZK的地址-->
        <property>
            <name>hbase.zookeeper.quorum</name>
            <value>bigdata111</value>
        </property>
    
        <!--Region的冗余-->
        <property>
            <name>dfs.replication</name>
            <value>1</value>
        </property>  
    </configuration>
    

      修改 regionservers,表示 HBase 的从节点列表,这里使用本机的 host

    bigdata111
    

    (2)启动

      先启动 hadoop

    start-all.sh
    

      启动 HBase,可以看到先启动了自带的 zk

    start-hbase.sh
    

      查看 jps,可以看到 HDFS 的进程,HBase 的进程和自带的 ZK 的进程。

    (3)测试

      执行和本地模式同样的 hbase shell 测试,在 HDFS 的 /hbase 目录下生成了很多文件。

      其中 /hbase/data 下的文件目录与本地模式目录下生成的文件是一样的。


    3、全分布模式

      全分布模式下 HBase 会被部署在多台机器上,由一个主节点和多个从节点组成。

      环境:① HDFS - bigdata111,bigdata112,bigdata113
         ② HBase - bigdata111,bigdata112,bigdata113

    (1)HBase 配置

      先把安装包和配置在 bigdata111 上做好,再复制到 bigdata112、bigdata113 上即可。

      配置系统环境变量 ~/.bash_profile

    HBASE_HOME=/root/training/hbase-1.3.1
    export HBASE_HOME
    

      配置 $HBASE_HOME/conf/hbase-env.sh

    ...
    export JAVA_HOME=/root/training/jdk1.8.0_181
    ...
    export HBASE_MANAGES_ZK=true
    ...
    

      配置 $HBASE_HOME/conf/hbase-site/xml

    <configuration>
        <!--数据存储位置-->
        <property>
            <name>hbase.rootdir</name>
            <!--数据保存在HDFS上-->
            <value>hdfs://bigdata111:9000/hbase</value>
        </property>
    
        <!--表示是一个分布式的环境-->
        <property>
            <name>hbase.cluster.distributed</name>
            <value>true</value>
        </property>
    
        <!--ZK的地址-->
        <property>
            <name>hbase.zookeeper.quorum</name>
            <value>bigdata111</value>
        </property>
    
        <!--Region的冗余-->
        <property>
            <name>dfs.replication</name>
            <value>2</value>
        </property>
    </configuration>
    

      HBase 配置 bigdata111 为主节点,其余两个为从节点,修改 regionservers

    bigdata112
    bigdata113
    

      把111上的 hbase 复制到 112、113

    scp -r /root/training/hbase-1.3.1/ root@bigdata112:/root/training
    scp -r /root/training/hbase-1.3.1/ root@bigdata113:/root/training
    

    (2)启动

      在 bigdata111 上启动 hadoop 和 hbase

    start-all.sh
    start-hbase.sh
    

      启动之后查看 jps

    # bigdata111
    [root@bigdata111 conf]# jps
    2338 NameNode
    2530 SecondaryNameNode
    2692 ResourceManager
    5013 HQuorumPeer
    5290 Jps
    5084 HMaster
    
    # bigdata112
    [root@bigdata112 conf]# jps
    1461 NodeManager
    1941 Jps
    1350 DataNode
    
    # bigdata113
    [root@bigdata113 conf]# jps
    1880 Jps
    1307 DataNode
    1419 NodeManager
    

      bigdata112、bigdata113 上并没有 hbase 相关的进程,这是什么情况?查看 hbase 主节点的日志,发现不断产生大量报错

    2022-03-22 00:11:51,939 INFO  [bigdata111:16000.activeMasterManager] master.ServerManager: Waiting for region servers count to settle; currently checked in 0, slept for 428963 ms, expecting minimum of 1, maximum of 2147483647, timeout of 4500 ms, interval of 1500 ms.
    2022-03-22 00:11:53,461 INFO  [bigdata111:16000.activeMasterManager] master.ServerManager: Waiting for region servers count to settle; currently checked in 0, slept for 430485 ms, expecting minimum of 1, maximum of 2147483647, timeout of 4500 ms, interval of 1500 ms.
    

      原因是两个 RegionServer 跟 HMaster 的时间不同步,停掉 hbase,同步时间后

    # 同步方法1:所有机器直接同步到某个时间
    date -s "yyyy-MM-dd HH:mm:ss"
    
    # 同步方法2:跟时间服务器同步
    ntpdate [timeserver]
    

      同步完后重启 hbase

      可以看到 bigdata112、bigdata113 都有 RegionServer 的进程了

      访问 HBase 的 web console:http://192.168.190.111:16010/master-status,可以看到 master 为 bigdata111,Region Servers 为 bigdata112、bigdata113。

    (3)测试

      在 bigdata111 上开启 hbase shell,结果随便执行一个查询的操作抛出异常

      原因是读取了伪分布模式下 hbase 在 HDFS 上生成的文件是有问题的,停掉 hbase 服务,清空 /hbase 目录,删除 hbase 安装目录重新安装复制,重启 hbase 再测试,还是一样的报错,最终才定位到是因为 zk 还保留这个表的信息,所以认为表已存在无法创建,从 zk 上删除信息后,表创建成功。

    # 进入 hbase zkcli
    hbase zkcli
    ...
    # 查看存在的表信息
    [zk: bigdata111:2181(CONNECTED) 0] ls /hbase/table
    [hbase:meta, hbase:namespace, student]
    # 删除表信息
    [zk: bigdata111:2181(CONNECTED) 1] rmr /hbase/table/student
    # 再次查看是否删除成功
    [zk: bigdata111:2181(CONNECTED) 2] ls /hbase/table
    [hbase:meta, hbase:namespace]
    # 退出
    [zk: bigdata111:2181(CONNECTED) 3] quit
    

      重新开启 hbase shell 测试,命令执行正常

      查看 hbase web console,可以看到新创建的表和列族


    4、HA 模式

      HBase 实现高可用只需要在某个 Region Server 上开启一个 master 守护进程即可。

    hbase-daemon.sh start master
    

      开启之后查看 web console,可以看到备用的 master

    1

      现在杀死 bigdata111 上的 HMaster 进程,模拟生产主节点故障的情况

      可以看到 bigdata112 上出现了 HMaster 的进程,它成为了 hbase 的主节点

      这时候访问 hbase web console 的地址要变为:http://192.168.190.112:16010/master-status,可以看到这时 bigdata112 既是主节点,又是从节点。

      假设经过一段时间原来的主节点 bigdata111 从故障中恢复,要重新加入集群,并成为备用主节点,则执行

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

      查看控制台

      可以看到 hbase 的 HA,新主从节点的加入,操作非常简单。

      通过 ZooInspector,可以直接从 zk 了解 hbase 集群的状态,包括哪个节点是 master,哪个节点是 Region Server。



    五、操作 HBase

    1、HBase shell

    (1)create 建表

    # 语法:create 'tablename','column-family1','column-family2',...,'column-familyN'
    #
    # 创建一个 student 表,表中有两个列族 info、grade
    create 'student','info','grade'
    

    (2)list 查询数据库中的表

    # 语法:list
    #
    # 相当于SQL中 show tables 的效果
    hbase(main):001:0> list
    

    (3)describe 查看表结构

    # 也可以使用缩写 desc '表名'
    hbase(main):003:0> describe 'student'
    Table student is ENABLED                                                                                                                                                                
    student                                                                                                                                                                                 
    COLUMN FAMILIES DESCRIPTION                                                                                                                                                             
    {NAME => 'grade', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', COMPRESSION => 'NONE', MI
    N_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}                                                                                                
    {NAME => 'info', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', COMPRESSION => 'NONE', MIN
    _VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}                                                                                                 
    2 row(s) in 0.0860 seconds
    

    (4)put 插入数据

    # 语法:put 'tablename','rawkey','family1:column1','value1',...,'familyN:columnN','valueN'
    #
    # 往学生表插入一行数据,rawkey 为 s001,info列族的name属性为 Tom
    hbase(main):005:0> put 'student','s001','info:name','Tom'
    0 row(s) in 0.0550 seconds
    

    (5)scan 查看数据

    # 语法:scan 'tablename'
    #
    # 查询学生表中所有数据,相当于:select * from student;
    hbase(main):006:0> scan 'student'
    ROW                                COLUMN+CELL                                                  s001                               column=info:name, timestamp=1648041822338, value=Tom          1 row(s) in 0.0290 seconds
    

    (6)get 查询数据

      get 查询跟 scan 的差异:get 是根据 rawkey 查询单条数据。

    # 语法:get 'tablename','rawkey'
    #
    # 查询 student 表中 rawkey 为 's001' 的数据
    hbase(main):007:0> get 'student','s001'
    COLUMN                                          CELL                                              info:name                                      timestamp=1648041822338, value=Tom               1 row(s) in 0.0140 seconds
    

      插入的每一条数据都会生成一个时间戳,如果是同一行数据,对同一列插入不同数据,会保留数据的多个版本,可以通过指定时间戳获得对应的版本数据,默认情况下查询只会返回最新的版本。

    hbase(main):032:0> get 'student','s001'
    COLUMN                                         CELL                                             info:name                                      timestamp=1648043400032, value=Jerry             1 row(s) in 0.0040 seconds
    
    hbase(main):033:0> get 'student','s001',{ COLUMN => 'info:name', TIMESTAMP => 1648043355198 }
    COLUMN                                         CELL                                             info:name                                      timestamp=1648043355198, value=Tom               1 row(s) in 0.0060 seconds
    
    hbase(main):034:0> get 'student','s001',{ COLUMN => 'info:name', TIMESTAMP => 1648043400032 }
    COLUMN                                         CELL                                              info:name                                      timestamp=1648043400032, value=Jerry             1 row(s) in 0.0080 seconds
    

    (7)delete 删除数据

    # 语法:delete 'tablename','rawkey','family:column'
    #
    # 删除数据不是以行为单位的,而是列
    hbase(main):048:0> delete 'student','s001','grade'
    0 row(s) in 0.0030 seconds
    
    hbase(main):049:0> get 'student','s001'
    COLUMN                                         CELL                                              grade:math                                     timestamp=1648044130996, value=100               grade:music                                    timestamp=1648044154962, value=95                 info:name                                      timestamp=1648043400032, value=Jerry             1 row(s) in 0.0030 seconds
    
    hbase(main):050:0> delete 'student','s001','grade:math'
    0 row(s) in 0.0030 seconds
    
    hbase(main):051:0> get 'student','s001'
    COLUMN                                         CELL                                              grade:music                                    timestamp=1648044154962, value=95                 info:name                                      timestamp=1648043400032, value=Jerry             1 row(s) in 0.0070 seconds
    

    (8)deleteall 删除整行数据

    hbase(main):052:0> deleteall 'student','s001'
    0 row(s) in 0.0080 seconds
    
    hbase(main):053:0> get 'student','s001'
    COLUMN                                          CELL                                                                                                                                    
    0 row(s) in 0.0040 seconds
    

    (9)drop 删除表

    # 语法:drop 'tablename'
    #
    # 删除表前必须先禁用表,否则会报错:ERROR: Table student is enabled. Disable it first.
    hbase(main):024:0> disable 'student'
    0 row(s) in 2.2630 seconds
    
    hbase(main):025:0> drop 'student'
    0 row(s) in 1.2520 seconds
    


    2、Java API

    (1)CURD

    ① 创建表

        // 如果程序执行卡住不动,可能是客户端无法解析 hbase 主机名,检查 host 即可
        // 创建表
        @Test
        public void testCreateTable() throws Exception {
            // 配置ZooKeeper的地址
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            // 客户端
            HBaseAdmin admin = new HBaseAdmin(conf);
            
            //创建表的描述符
            HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("mystudent"));
            // 表中的列族
            htd.addFamily(new HColumnDescriptor("info"));
            htd.addFamily(new HColumnDescriptor("grade"));
            
            // 创建表
            admin.createTable(htd);
            
            admin.close();
        }
    

    ② 插入数据

        // 插入数据:  单条数据
        @Test
        public void testPut() throws Exception {
            // 配置ZooKeeper的地址
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            // 得到表的客户端
            HTable table = new HTable(conf, "mystudent");
            
            // 插入的数据:构造Put(参数:行键)
            Put put = new Put(Bytes.toBytes("s001"));
            // put.addColumn(family,        列族的名字
            //               qualifier,     列的名字
            //               value)         值
            put.addColumn(Bytes.toBytes("info"), 
                          Bytes.toBytes("name"), 
                          Bytes.toBytes("Tom"));
            table.put(put);
            
            // 插入数据:多条数据
            // table.put(List<Put>);
            table.close();  
        }
    

    ③ 查询单条数据

        // 查询数据Get
        @Test
        public void testGet() throws Exception {
            // 配置ZooKeeper的地址
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            // 得到表的客户端
            HTable table = new HTable(conf, "mystudent");
            
            // 构造Get对象
            Get get = new Get(Bytes.toBytes("s001"));
            
            // 查询
            Result r = table.get(get);
            // 取出数据
            String name = Bytes.toString(r.getValue(Bytes.toBytes("info"), Bytes.toBytes("name")));
            System.out.println(name);
            
            table.close();
        }
    

    ④ 查询多条数据

        // 查询数据
        @Test
        public void testScan() throws Exception {
            // 配置ZooKeeper的地址
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            // 得到表的客户端
            HTable table = new HTable(conf, "mystudent");
            
            // 定义一个扫描器
            Scan scan = new Scan();
            // scan.setFilter(filter) 过滤器
            
            // 通过扫描器查询数据
            // 在该集合中保存的是Result
            ResultScanner rs = table.getScanner(scan);
            for(Result r:rs) {
                //取出数据
                String name = Bytes.toString(r.getValue(Bytes.toBytes("info"), Bytes.toBytes("name")));
                System.out.println(name);           
            }
            
            table.close();
        }
    

    ⑤ 删除表

        // 删除表
        @Test
        public void testDrop() throws Exception {
            // 配置ZooKeeper的地址
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            // 客户端
            HBaseAdmin admin = new HBaseAdmin(conf);
            
            // 删除表
            admin.disableTable("mystudent");
            admin.deleteTable("mystudent");
            
            admin.close();
        }
    

    (2)MapReduce 处理 HBase

      首先分析需求,还是 WordCount 小程序的例子,首先创建数据库 word,列族为 contentcontent:info 为每一行文本数据;计算结果保存在 stat 数据库中,rowkey 为分词的每个单词,计数存储在 content:result 中。

    ① 数据初始化

      通过以下代码,往 word 表插入三条数据。

    public class WordCountDataInit {
    
        @Test
        public void createTable() throws IOException {
            //指定的配置信息: ZooKeeper
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            //创建一个HBase客户端: HBaseAdmin
            HBaseAdmin admin = new HBaseAdmin(conf);
            
            //创建一个表的描述符: 表名
            HTableDescriptor hd = new HTableDescriptor(TableName.valueOf("word"));
            
            //创建列族描述符
            HColumnDescriptor hcd1 = new HColumnDescriptor("content");
            
            //加入列族
            hd.addFamily(hcd1);
            
            //创建表
            admin.createTable(hd);
            
            //关闭客户端
            admin.close();
        }
        
        @Test
        public void insertData() throws IOException {
            //指定的配置信息: ZooKeeper
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            //客户端
            HTable table = new HTable(conf, "word");
            
            Put put1 = new Put(Bytes.toBytes("1"));
            put1.add(Bytes.toBytes("content"), Bytes.toBytes("info"), Bytes.toBytes("I love Beijing"));
            
            Put put2 = new Put(Bytes.toBytes("2"));
            put2.add(Bytes.toBytes("content"), Bytes.toBytes("info"), Bytes.toBytes("I love China"));
            
            Put put3 = new Put(Bytes.toBytes("3"));
            put3.add(Bytes.toBytes("content"), Bytes.toBytes("info"), Bytes.toBytes("Beijing is the capital of China"));
            
            List<Put> list = new ArrayList<Put>();
            list.add(put1);
            list.add(put2);
            list.add(put3);
            
            //插入数据
            table.put(list);
            table.close();  
        }
    }
    

    ② mapper

      要通过 MapReduce 程序操作 HBase,mapper 需要继承 org.apache.hadoop.hbase.mapreduce.TableMapper,map 的输入处理的就是一条 HBase 表记录,至于是哪个表在作业配置中指定。

    // 现在的输入就是HBase表中的一条记录
    //                                              k2单词    v2记一次数
    public class WordCountMapper extends TableMapper<Text, IntWritable> {
    
        @Override
        protected void map(ImmutableBytesWritable key1, Result value1, Context context)
                throws IOException, InterruptedException {
            /*
             * key1:代表的是行键rowkey
             * value1:该行记录
             * 数据 : I love Beijing
             */
            String data = Bytes.toString(value1.getValue(Bytes.toBytes("content"), Bytes.toBytes("info")));
            // 分词
            String[] words = data.split(" ");
            for (String word : words) {
                context.write(new Text(word), new IntWritable(1));
            }
        }
    }
    

    ③ reducer

      和 mapper 一样,reducer 同样要继承 org.apache.hadoop.hbase.mapreduce.TableReducer,reduce 的输出就是往 HBase 里插入一条记录,插入的表在作业配置中指定,插入的列族和列则直接在 reducer 中指定。

    //                                                  k3       v3           k4是输出记录的行键
    public class WordCountReducer extends TableReducer<Text, IntWritable, ImmutableBytesWritable> {
    
        @Override
        protected void reduce(Text key3, Iterable<IntWritable> value3, Context context)
                throws IOException, InterruptedException {
            // 计算总数
            int total = 0;
            for (IntWritable v : value3) {
                total += v.get();
            }
            
            // 输出的是表中的一条记录
            // 构造一个Put对象
            Put put = new Put(Bytes.toBytes(key3.toString()));
            put.addColumn(Bytes.toBytes("content"), Bytes.toBytes("result"), Bytes.toBytes(String.valueOf(total)));
            
            // 输出                单词作为rowkey
            context.write(new ImmutableBytesWritable(Bytes.toBytes(key3.toString())), put);
        }
    }
    

    ④ Job

    public class WordCountMain {
    
        public static void main(String[] args) throws Exception {
            // 指定的配置信息: ZooKeeper
            Configuration conf = new Configuration();
            conf.set("hbase.zookeeper.quorum", "192.168.190.111");
            
            // 创建任务,指定任务的入口
            Job job = Job.getInstance(conf);
            job.setJarByClass(WordCountMain.class);
            
            // 定义一个扫描器,指定读取的列
            Scan scan = new Scan();
            scan.addColumn(Bytes.toBytes("content"), Bytes.toBytes("info"));
            
            // 使用工具类指定任务的Map
            TableMapReduceUtil.initTableMapperJob(Bytes.toBytes("word"), // 指定读取HBase数据所在的表
                                                  scan,                // 扫描器
                                                  WordCountMapper.class,// 指定处理的Mapper Class
                                                  Text.class,           // k2
                                                  IntWritable.class,    // v2
                                                  job);
            
            // 使用工具类指定任务的reducer
            TableMapReduceUtil.initTableReducerJob("stat", 
                                                   WordCountReducer.class, 
                                                   job);
            
            // 执行任务
            job.waitForCompletion(true);
        }
    }
    

      提交到 hadoop 上执行

    hadoop jar hbase-mr.jar
    

      结果失败了,发现是没有提前创建好保存作业结果的 stat 表。

      创建好表重新执行,作业成功

      查看 stat 表,结果正常


    3、Web Console

      我们还可以通过 web console 访问 HBase,需要访问 master 节点的 16010 端口。

      网页控制台具备的功能有:

    • 1)查看集群节点信息(主从节点、备用节点)
    • 2)基本表信息(包括用户表、系统表)
    • 3)查看执行过的 HBase 任务
    • 4)查看日志,设置日志级别
    • 5)dump 信息
    • 6)Hbase 配置
    • ...

    相关文章

      网友评论

        本文标题:分布式列式数据库 —— HBase

        本文链接:https://www.haomeiwen.com/subject/wvrcjrtx.html