HBase 基础

HBase

1. HBase简介

1.1 HBase定义

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

1.2 HBase数据模型

逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。

但从HBase 的底层物理存储结构(K-V) 来看,HBase 更像是一个 multi-dimensionalmap

HBase 的设计理念依据 Google 的 BigTable 论文,论文中对于数据模型的首句介绍。BigTable 是一个稀疏的、分布式的、持久的多维排序 map。
之后对于映射的解释如下:
该映射由行键、列键和时间戳索引;映射中的每个值都是一个未解释的字节数组。

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

HBase 使用与 BigTable 非常相似的数据模型。用户将数据行存储在带标签的表中。数据行具有可排序的键和任意数量的列。该表存储稀疏,因此如果用户喜欢,同一表中的行可以具有疯狂变化的列。
最终理解 HBase 数据模型的关键在于稀疏、分布式、多维、排序的映射。其中映射 map指代非关系型数据库的 key-Value 结构。

1.2.1 HBase逻辑结构

在这里插入图片描述

1.2.2 HBase物理存储结构

在这里插入图片描述

1.2.3 数据模型
  1. Name Space

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

  1. Region

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

  1. Row

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

  1. Column

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

  1. Time Stamp

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

  1. Cell

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

1.3 HBase基本架构

在这里插入图片描述

2. HBase环境安装

我为各位提供了 env-hadoop-3.3.5-zookeeper-3.8.2.tar.gz 文件

这个压缩包内置了 Hadoop 和 Zookeeper 环境的一键部署指令,将这个压缩包放到 /root 下解压后,使用 launcher 指令可以实现一键部署并启动这个集群环境,具体执行命令如下,等待大概十分钟左右,脚本会自动启动 HDFS 和 Zookeeper 的集群环境,方便各位同学做后面的 HBase 的安装实验

[root@c7100 ~]# tar -zxvf env-hadoop-3.3.5-zookeeper-3.8.2.tar.gz
[root@c7100 ~]# /root/env/sh/launcher

需要注意的是:

  1. 本教程中给出的是完整的 HBase 的安装步骤,如果是使用我发的 env-hadoop-3.3.5-zookeeper-3.8.2.tar.gz 来进行一键部署 Hadoop 和 Zookeeper 的同学,使用 hstarthstop 也可以实现 hdfs 的手动启动和停止,使用 zk startzk stop 也可以实现 Zookeeper 的手动启动和停止
  2. 使用 launcher 指令的使用前提:应当确保三台 Linux 系统相互能免密登录
  3. 使用 launcher 指令来进行一键部署的时候,只能使用一次,再次使用可能结果不是预期

2.1 HBase 安装部署

2.1.1 HBase 本地按照
  1. hbase-2.4.17-bin.tar.gz 文件上传到 CentOS 并解压到 opt 目录
mkdir /opt/apache/
tar -zxvf hbase-2.4.17-bin.tar.gz -C /opt/apache/
  1. 使用 vim 修改 HBase 的配置文件 /opt/apache/hbase-2.4.17/conf/hbase-site.xml
</configuration>  
  <property>
    <name>hbase.rootdir</name>
    <value>file:///opt/apache/data/hbase/rootdir</value>>
    <description>HBase 根目录,这里需要在地址前增加 file:// 协议</description>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>false</value>
    <description>是否开启 HBase 分布式集群</description>
  </property>
  <property>
    <name>hbase.tmp.dir</name>
    <value>/opt/apache/data/hbase/tmp</value>
    <description>指定 HBase 临时目录</description>
  </property>
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
    <description>控制 HBase 是否检查流功能(hflush/hsync)</description>
  </property>
</configuration>

要注意,目录 /opt/apache/data/hbase 需要提前创建的

  1. 创建 HBase 的环境变量文件 /etc/profile.d/env-hbase.sh,内容如下:
HBASE_HOME=/opt/apache/hbase-2.4.17
PATH=$PATH:$HBASE_HOME/bin
export HBASE_HOME PATH

完成后,需要使用 source /etc/profile 使其立即生效

  1. 开放防火墙端口 16010
firewall-cmd --add-port=16010/tcp --permanent
firewall-cmd --reload
  1. 启动 HBase
start-hbase.sh
2.1.2 HBase 伪分布模式安装

强烈建议在学习 HBase 的初期采用伪分布模式来安装

  1. 启动 Zookeeper(前提是已经安装了 Zookeeper)
[root@c7100 ~]# zkServer.sh start
[root@c7101 ~]# zkServer.sh start
  1. 启动 Hadoop 的 HDFS(前提是已经安装了 Hadoop)
[root@c7100 ~]# start-dfs.sh
  1. HBase的解压
[root@c7100 ~]# cd /opt/apache
[root@c7100 apache]# tar -zxvf hbase-2.5.5-bin.tar.gz
  1. HBase的配置文件
  • ① 修改 hbase-env.sh 文件,内容如下,修改完成之后需要使用 source 命令使其生效!
# Tell HBase whether it should manage it's own instance of ZooKeeper or not.
# export HBASE_MANAGES_ZK=true
export HBASE_MANAGES_ZK=false
  • ② 修改配置文件 hbase-site.xml 文件,内容如下
<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://c7100.vm:9000/hbase</value>
    <description>The directory shared by region servers and into which HBase persists.</description>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
    <description>
      The mode the cluster will be in.
      Possible values are false for standalone mode and true for distributed mode. 
      If false, startup will run all HBase and ZooKeeper daemons together in the one JVM.
    </description>
  </property>
  <property>
    <name>hbase.tmp.dir</name>
    <value>/opt/apache/data/hbase/tmp/hbase-${user.name}</value>
    <description>
      Temporary directory on the local filesystem.
      Change this setting to point to a location more permanent than '/tmp',
      the usual resolve for java.io.tmpdir,
      as the '/tmp' directory is cleared on machine restart.
    </description>
  </property>
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>c7100.vm,c7101.vm,c7102.vm</value>
    <description>
      Comma separated list of servers in the ZooKeeper ensemble
      (This config. should have been named hbase.zookeeper.ensemble).
      For example, "host1.mydomain.com,host2.mydomain.com,host3.mydomain.com".
    </description>
  </property>
  <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/opt/apache/data/zookeeper</value>
    <description>
      Property from ZooKeeper's config zoo.cfg. The directory where the snapshot is stored.
    </description>
  </property>
  <property>
    <name>hbase.wal.provider</name>
    <value>filesystem</value>
    <description>write ahead log</description>
  </property>
</configuration>
  • ③解决 log4j 的 jar 包冲突
mv /opt/apache/hbase-2.4.17/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar /opt/apache/hbase-2.4.17/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar.bak
  1. 开放防火墙端口
firewall-cmd --zone=public --add-port=2181/tcp --permanent
firewall-cmd --zone=public --add-port=2888/tcp --permanent
firewall-cmd --zone=public --add-port=3888/tcp --permanent
firewall-cmd --zone=public --add-port=16000/tcp --permanent
firewall-cmd --zone=public --add-port=16010/tcp --permanent
firewall-cmd --zone=public --add-port=16020/tcp --permanent
firewall-cmd --zone=public --add-port=16030/tcp --permanent
firewall-cmd --reload
  1. HBase服务的启动
start-hbase.sh
  1. 查看HBase页面

使用浏览器打开 http://c7100:16010 即可

2.1.3 HBase 集群安装
  1. 启动 Zookeeper(前提是已经安装了 Zookeeper)
[root@c7100 ~]# zkServer.sh start
[root@c7101 ~]# zkServer.sh start
  1. 启动 Hadoop 的 HDFS(前提是已经安装了 Hadoop)
[root@c7100 ~]# start-dfs.sh
  1. HBase的解压
[root@c7100 ~]# cd /opt/apache
[root@c7100 apache]# tar -zxvf hbase-2.5.5-bin.tar.gz
  1. HBase的配置文件
  • ① 修改 hbase-env.sh 文件,内容如下,修改完成之后需要使用 source 命令使其生效!
# The java implementation to use.  Java 1.8+ required.
# export JAVA_HOME=/usr/java/jdk1.8.0/
# export JAVA_HOME=/opt/java-se-8u41-ri/

# The directory where pid files are stored. /tmp by default.
# export HBASE_PID_DIR=/var/hadoop/pids
export HBASE_PID_DIR=/opt/apache/data/hbase/pids

# Tell HBase whether it should manage it's own instance of ZooKeeper or not.
# export HBASE_MANAGES_ZK=true
export HBASE_MANAGES_ZK=false
  • ② 修改配置文件 hbase-site.xml 文件,内容如下
<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://c7100.vm:9000/hbase</value>
    <description>The directory shared by region servers and into which HBase persists.</description>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
    <description>
      The mode the cluster will be in.
      Possible values are false for standalone mode and true for distributed mode. 
      If false, startup will run all HBase and ZooKeeper daemons together in the one JVM.
    </description>
  </property>
  <!--
  <property>
    <name>hbase.master.port</name>
    <value>16000</value>
    <description>The port the HBase Master should bind to.</description>
  </property>
  <property>
    <name>hbase.master.info.port</name>
    <value>16010</value>
    <description>
      The port for the HBase Master web UI.
      Set to -1 if you do not want a UI instance run.
    </description>
  </property>
  -->
  <property>
    <name>hbase.tmp.dir</name>
    <!--<value>${java.io.tmpdir}/hbase-${user.name}</value>-->
    <value>/opt/apache/data/hbase/tmp/hbase-${user.name}</value>
    <description>
      Temporary directory on the local filesystem.
      Change this setting to point to a location more permanent than '/tmp',
      the usual resolve for java.io.tmpdir,
      as the '/tmp' directory is cleared on machine restart.
    </description>
  </property>
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>c7100.vm,c7101.vm,c7102.vm</value>
    <description>
      Comma separated list of servers in the ZooKeeper ensemble
      (This config. should have been named hbase.zookeeper.ensemble).
      For example, "host1.mydomain.com,host2.mydomain.com,host3.mydomain.com".
    </description>
  </property>
  <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/opt/apache/data/zookeeper</value>
    <description>
      Property from ZooKeeper's config zoo.cfg. The directory where the snapshot is stored.
    </description>
  </property>
  <property>
    <name>hbase.wal.provider</name>
    <value>filesystem</value>
    <description>write ahead log</description>
  </property>
</configuration>
  • ③ 修改 regionservers 文件
c7100.vm
c7101.vm
c7102.vm
  • ④ 解决 log4j 的 jar 包冲突
mv /opt/apache/hbase-2.4.17/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar /opt/apache/hbase-2.4.17/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar.bak
  1. HBase远程发送
xsync /opt/apache/hbase-2.5.5/
  1. 开放防火墙端口
firewall-cmd --zone=public --add-port=2181/tcp --permanent
firewall-cmd --zone=public --add-port=2888/tcp --permanent
firewall-cmd --zone=public --add-port=3888/tcp --permanent
firewall-cmd --zone=public --add-port=16000/tcp --permanent
firewall-cmd --zone=public --add-port=16010/tcp --permanent
firewall-cmd --zone=public --add-port=16020/tcp --permanent
firewall-cmd --zone=public --add-port=16030/tcp --permanent
firewall-cmd --reload
  1. HBase服务的启动
start-hbase.sh
  1. 查看HBase页面

使用浏览器打开 http://c7100:16010 即可

2.2 HBase Shell操作

  • help:是HBase 中最基础的命令,能为需要执行的命令提供帮助
hbase:003:0> help
HBase Shell, version 2.4.17, r7fd096f39b4284da9a71da3ce67c48d259ffa79a, Fri Mar 31 18:10:45 UTC 2023
Type 'help "COMMAND"', (e.g. 'help "get"' -- the quotes are necessary) for help on a specific command.
Commands are grouped. Type 'help "COMMAND_GROUP"', (e.g. 'help "general"') for help on a command group.

COMMAND GROUPS:
  Group name: general
  Commands: processlist, status, table_help, version, whoami

  Group name: ddl
  Commands: alter, alter_async, alter_status, clone_table_schema, create, describe, disable, disable_all, drop, drop_all, enable, enable_all, exists, get_table, is_disabled, is_enabled, list, list_regions, locate_region, show_filters

  Group name: namespace
  Commands: alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables

  Group name: dml
  Commands: append, count, delete, deleteall, get, get_counter, get_splits, incr, put, scan, truncate, truncate_preserve
  • 命名空间
# 创建新的命名空间
hbase:029:0>create_namespace 'hadoop'


# 列举所有的命名空间
hbase:030:0>list_namespace
NAMESPACE
default
hadoop
hbase
3row(s)

# 列举以特定字符开头的所有的命名空间,支持正则匹配
hbase:034:0>list_namespace 'h.*'
NAMESPACE
hadoop
hbase
2 row(s)

# 查看命名空间描述
hbase:037:0>describe_namespace 'hadoop'
DESCRIPTION
{
   NAME => 'hadoop'}
Quota is disabled

# 修改命名空间,为其增加一个附加属性 describe,其值为 'big data framework'
hbase:002:0> alter_namespace 'hadoop', {
   METHOD=>'set', 'describe'=>'big data framework'}

hbase:003:0> describe_namespace 'hadoop'
DESCRIPTION
{
   NAME => 'hadoop', describe => 'big data framework'}
Quota is disabled

# 修改命名空间,删除一个附加属性 describe
hbase:006:0> alter_namespace 'hadoop', {
   METHOD=>'unset', NAME=>'describe'}

hbase:007:0> describe_namespace 'hadoop'
DESCRIPTION
{
   NAME => 'hadoop'} 
Quota is disabled

# 删除一个的命名空间,该明明空间必须是空的
hbase:016:0>drop_namespace 'hadoop'
Took 0.1541 seconds

hbase:017:0>list_namespace
NAMESPACE
default
hbase
2row(s)
2.2.1 DDL操作
# 建表
# ------------------------------------------------------

# 在默认命名空间下创建表 t1
hbase:016:0> create 't1', {
   NAME => 'f1'}, {
   NAME => 'f2'}, {
   NAME => 'f3'}

# 上一行可以简写成如下形式
hbase:016:0> create 't2', 'f1', 'f2', 'f3'

# 创建表的时候为列族 f1 设置版本
hbase:016:0> create 't3', {
   NAME => 'f1', VERSIONS => 2}

# 在命名空间 nano 下创建表 stu
hbase:016:0> create 'nano:stu', 'basic', {
   NAME=>'advanced', VERSIONS=>'2'}

# 查看表信息
# ------------------------------------------------------

# 查看所有用户表
hbase:043:0> list
TABLE
t1 
t2 
t3
nano:stu
4 row(s)
=> ["t1", "t2", "t3", "nano:stu"]

# 查看某个表的信息
hbase:042:0> describe 't3'
Table t3 is ENABLED 
t3
COLUMN FAMILIES DESCRIPTION
{
   NAME => 'f1', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '2', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'} 


# 查看某个表是否存在
hbase:044:0> exists 't1'
Table t1 does exist
=> true

hbase:045:0> exists 't4'
Table t4 does not exist
=> false

# 禁用表
# ------------------------------------------------------
hbase:050:0> is_disabled 't3'
false
hbase:051:0> disable 't3'
hbase:052:0> is_disabled 't3'
true
hbase:053:0> enable 't3'
hbase:054:0> is_enabled 't3'
true

# 删除表
# ------------------------------------------------------
hbase:004:0> drop 't3'

ERROR: Table t3 is enabled. Disable it first.

For usage try 'help "drop"'

hbase:005:0> disable 't3'
hbase:006:0> drop 't3'
hbase:007:0> exists 't3'
Table t3 does not exist
=> false

# 修改表
# ------------------------------------------------------

# 修改列族 t1 的最大版本为 5
hbase:014:0> alter 't1', NAME => 'f1', VERSIONS => 5
Updating all regions with the new schema...
1/1 regions updated.
Done.

# 支持同时多个列族,每个列族使用映射语法并用逗号隔开,
hbase:015:0> alter 't1', {
   NAME => 'f2', IN_MEMORY => true}, {
   NAME => 'f3', VERSIONS => 5}
Updating all regions with the new schema...
1/1 regions updated.
Done.

# 删除一个列族
hbase:018:0> alter 't1', NAME => 'f1', METHOD => 'delete'
Updating all regions with the new schema...
1/1 regions updated.
Done.

# 删除一个列族的简写形式【需要注意的是:仅剩余最后一个列族时,不可删除!】
hbase:019:0> alter 't1', 'delete' => 'f2'
Updating all regions with the new schema...
1/1 regions updated.
Done.

2.2.2 DML 操作
# 写入数据
# ------------------------------------------------------
hbase:005:0> put 'nano:stu', '1001', 'basic:name', 'tina'
Took 0.1454 seconds
hbase:006:0> put 'nano:stu', '1001', 'basic', 'this is basic information!'
Took 0.0296 seconds
hbase:007:0> put 'nano:stu', '1001', 'basic:age', '19'
Took 0.0328 seconds 
hbase:008:0> put 'nano:stu', '1001', 'basic:name', 'coco'
Took 0.0097 seconds
hbase:009:0> put 'nano:stu', '1001', 'basic:name', 'yoyo'
Took 0.0160 seconds
hbase:015:0> put 'nano:stu', '1002', 'basic:name', 'Jenkins'
Took 0.0431 seconds
hbase:016:0> put 'nano:stu', '1002', 'basic:age', '20'
Took 0.0254 seconds
hbase:017:0> put 'nano:stu', '1003', 'basic:name', 'Lucas'
Took 0.1106 seconds

# 读取数据
# ------------------------------------------------------

# 获取行键为 1001 的数据
hbase:010:0> get 'nano:stu', '1001'
COLUMN         CELL
 basic:        timestamp=2023-09-02T21:51:35.249, value=this is basic information!
 basic:age     timestamp=2023-09-02T21:51:49.318, value=19
 basic:name    timestamp=2023-09-02T21:52:02.590, value=yoyo
1 row(s)
Took 0.0285 seconds
# 获取行键为 1001 的数据的 name 列的数据
hbase:011:0> get 'nano:stu', '1001', {
   COLUMN => 'basic:name'}
COLUMN         CELL
 basic:name    timestamp=2023-09-02T21:52:02.590, value=yoyo
1 row(s)
Took 0.0557 seconds

# 获取 nano:stu 表所有行的数据
hbase:017:0> scan 'nano:stu'
ROW       COLUMN+CELL
 1001     column=basic:, timestamp=2023-09-02T21:51:35.249, value=this is basic information!
 1001     column=basic:age, timestamp=2023-09-02T21:51:49.318, value=19
 1001     column=basic:name, timestamp=2023-09-02T21:52:02.590, value=yoyo
 1002     column=basic:age, timestamp=2023-09-02T21:55:51.626, value=20
 1002     column=basic:name, timestamp=2023-09-02T21:55:32.903, value=Jenkins
 1003     column=basic:name, timestamp=2023-09-02T21:55:57.235, value=Lucas
3 row(s)
Took 0.0727 seconds

# 获取 nano:stu 表所有行的数据,过滤所有 name 列
hbase:005:0> scan 'nano:stu', {
   COLUMNS => 'basic:name'}
ROW                       COLUMN+CELL
 1001                     column=basic:name, timestamp=2023-09-02T23:13:44.212, value=yoyo
 1002                     column=basic:name, timestamp=2023-09-02T23:13:44.251, value=Jenkins
 1003                     column=basic:name, timestamp=2023-09-02T23:13:44.336, value=Lucas
3 row(s)

# 获取 nano:stu 表所有行的数据,过滤所有 name 列
hbase:005:0> scan 'nano:stu', {
   COLUMNS => 'basic:name'}
ROW                       COLUMN+CELL
 1001                     column=basic:name, timestamp=2023-09-02T23:13:44.212, value=yoyo
 1002                     column=basic:name, timestamp=2023-09-02T23:13:44.251, value=Jenkins
 1003                     column=basic:name, timestamp=2023-09-02T23:13:44.336, value=Lucas
3 row(s)

# 获取 nano:stu 表所有行的数据,从 1001 开始,只查询两行
hbase:008:0> scan 'nano:stu', {
   LIMIT => 2, STARTROW => '1001'}
ROW                       COLUMN+CELL
 1001                     column=basic:, timestamp=2023-09-02T23:13:43.967, value=this is basic
                          information!
 1001                     column=basic:age, timestamp=2023-09-02T23:13:44.031, value=19
 1001                     column=basic:name, timestamp=2023-09-02T23:13:44.212, value=yoyo
 1002                     column=basic:age, timestamp=2023-09-02T23:13:44.307, value=20
 1002                     column=basic:name, timestamp=2023-09-02T23:13:44.251, value=Jenkins
2 row(s)

# 删除数据,删除 1003 的 name,因为 1003 只有 name 这一列,如果它被删除,则意味着这一整行被删除
# ------------------------------------------------------
hbase:012:0> delete 'nano:stu', '1003', 'basic:name'
Took 0.0884 seconds
hbase:013:0> scan 'nano:stu'
ROW                       COLUMN+CELL
 1001                     column=basic:, timestamp=2023-09-02T23:13:43.967, value=this is basic
                          information!
 1001                     column=basic:age, timestamp=2023-09-02T23:13:44.031, value=19
 1001                     column=basic:name, timestamp=2023-09-02T23:13:44.212, value=yoyo
 1002                     column=basic:age, timestamp=2023-09-02T23:13:44.307, value=20
 1002                     column=basic:name, timestamp=2023-09-02T23:13:44.251, value=Jenkins
2 row(s)

# 删除数据,删除 1002 的 name,因为 1002 不止 name 这一列,此操作仅仅 name 这一个单元格
hbase:014:0> delete 'nano:stu', '1002', 'basic:name'
Took 0.0114 seconds
hbase:015:0> scan 'nano:stu'
ROW                       COLUMN+CELL
 1001                     column=basic:, timestamp=2023-09-02T23:13:43.967, value=this is basic
                          information!
 1001                     column=basic:age, timestamp=2023-09-02T23:13:44.031, value=19
 1001                     column=basic:name, timestamp=2023-09-02T23:13:44.212, value=yoyo
 1002                     column=basic:age, timestamp=2023-09-02T23:13:44.307, value=20
2 row(s)

# 删除数据,删除 1001 的所有单元格的数据,意味着这一整行都将被删除
hbase:018:0> deleteall 'nano:stu', '1001'
Took 0.0248 seconds
hbase:019:0> scan 'nano:stu'
ROW                       COLUMN+CELL
 1002                     column=basic:age, timestamp=2023-09-02T23:13:44.307, value=20
1 row(s)
Took 0.0241 seconds

3. HBase 的 Java API

  1. 新建 Maven 项目,pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.tedu.hbase</groupId>
    <artifactId>hbase-learner</artifactId>
    <version>1.0</version>

    <properties>
        <maven.test.skip>true</maven.test.skip>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>2.4.17</version>
            <exclusions>
                <exclusion>
                    <groupId>org.glassfish</groupId>
                    <artifactId>javax.el</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>3.0.1-b06</version>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
        </repository>
        <repository>
            <id>apache release</id>
            <url>https://repository.apache.org/content/repositories/releases/</url>
        </repository>
    </repositories>

</project>
  1. 在 maven 项目的 resources 目录中增加 hbase-site.xml 文件,其内容如下:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>c7100.vm,c7101.vm,c7102.vm</value>
    <description>上一行的 value 的值需要跟服务器端 zookeeper 的集群的配置值保持一致</description>
  </property>
</configuration>
  1. 在 maven 项目的 resources 目录中增加 log4j.properties 文件,其内容如下:
log4j.rootLogger=INFO,Console

# Standard Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss:SSS}][%C-%M] %m%n

# MyBatis Console
log4j.logger.com.huofutp.uc.dao=DEBUG
log4j.logger.com.huofutp.crm.dao=DEBUG
log4j.logger.com.huofutp.log.dao=DEBUG
log4j.logger.com.huofutp.pis.dao=DEBUG
log4j.logger.com.huofutp.user.dao=DEBUG
log4j.logger.com.huofutp.common.dao=DEBUG
log4j.logger.com.huofutp.report.dao=DEBUG
log4j.logger.com.huofutp.logger.dao=DEBUG
log4j.logger.com.huofutp.workflow.dao=DEBUG
log4j.logger.com.huofutp.messenger.dao=DEBUG
log4j.logger.com.huofutp.bcs.dynamic.field.dao=DEBUG
  1. 创建单例的 HBase 的链接对象
public final class HBaseConnection {
   
	private static Connection connection = null;

	/**
	 * 获取线程安全的 HBase 连接对象
	 * @return HBase 连接对象
	 */
	public static Connection getConnection() {
   
		if(connection == null) {
   
			synchronized (HBaseConnection.class) {
   
				if(connection == null) {
   
					try {
   
						connection = ConnectionFactory.createConnection();
					} catch (IOException e) {
   
						e.printStackTrace();
					}
				}
			}
		}
		return connection;
	}
}
  1. 创建HBase的DDL操作类
public class HBaseDDL {
   
	/**
	 * 创建命名空间
	 *
	 * @param namespace 命名空间名称
	 */
	public static void createNamespace(String namespace) throws IOException {
   
		if (isNamespaceExists(namespace)) {
   
			System.out.println("namespace[" + namespace + "] already exists");
			return;
		}
		NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);
		try (Admin admin = getConnection().getAdmin()) {
   
			try {
   
				admin.createNamespace(builder.build());
			} catch (IOException e) {
   
				System.out.println("create namespace had error.");
			}
		}
		System.out.println("namespace[" + namespace + "] has been created");
	}

	/**
	 * 列出所有的命名空间
	 */
	public static void listNamespace() {
   
		try (Admin admin = getConnection().getAdmin()) {
   
			String[] strings = admin.listNamespaces();
			System.out.println("namespaces:[" + String.join("\t", strings) + "]");
		} catch (IOException e) {
   
			e.printStackTrace();
		}
	}

	/**
	 * 判断命名空间是否存在
	 */
	public static boolean isNamespaceExists(String namespace) {
   
		try (Admin admin = getConnection().getAdmin()) {
   
			String[] strings = admin.listNamespaces();
			return Arrays.asList(strings).contains(namespace);
		} catch (IOException e) {
   
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * 删除命名空间
	 *
	 * @param namespace 命名空间名称
	 */
	public static void deleteNamespace(String namespace) throws IOException {
   
		if (!isNamespaceExists(namespace)) {
   
			System.out.println("namespace[" + namespace + "] does not exist");
			return;
		}
		try (Admin admin = getConnection().getAdmin()) {
   
			admin.deleteNamespace(namespace);
		}
		boolean disappear = !isNamespaceExists(namespace);
		if (disappear) {
   
			System.out.println("namespace[" + namespace + "] has been deleted");
		}
	}

	/**
	 * 判断表格是否存在
	 */
	public static void listTable() throws IOException {
   
		listTable(null);
	}

	/**
	 * 判断表格是否存在
	 *
	 * @param namespace 命名空间名称
	 */
	public static void listTable(String namespace) throws IOException {
   
		try (Admin admin = getConnection().getAdmin()) {
   
			List<String> strings = new ArrayList<>();
			if (namespace != null) {
   
				TableName[] names = admin.listTableNamesByNamespace(namespace);
				for (TableName name : names) {
   
					strings.add(name.getNameWithNamespaceInclAsString());
				}
			} else {
   
				List<TableDescriptor> descriptors = admin.listTableDescriptors();
				for (TableDescriptor descriptor : descriptors) {
   
					strings.add(descriptor.getTableName().getNameWithNamespaceInclAsString());
				}
			}
			System.out.println("tables:[" + String.join("\t", strings) + "]");
		}
	}

	/**
	 * 判断表格是否存在
	 *
	 * @param namespace 命名空间名称
	 * @param tablename 表的名称
	 * @return 返回 true 表示表存在
	 */
	public static boolean isTableExists(String namespace, String tablename) throws IOException {
   
		boolean exists = false;
		try (Admin admin = getConnection().getAdmin()) {
   
			try {
   
				exists = admin.tableExists(TableName.valueOf(namespace, tablename));
			} catch (IOException e) {
   
				e.printStackTrace();
			}
		}
		return exists;
	}

	/**
	 * 创建表
	 *
	 * @param namespace      命名空间
	 * @param tablename      表名
	 * @param columnFamilies 列族
	 */
	public static void createTable(String namespace, String tablename, String... columnFamilies) throws IOException {
   
		if (isTableExists(namespace, tablename)) {
   
			System.out.println("table[" + tablename + "] already exists");
			return;
		}
		if (columnFamilies == null || columnFamilies.length < 1) {
   
			System.out.println("Table should have at least one column family");
			return;
		}
		try (Admin admin = getConnection().getAdmin()) {
   
			TableName tn = TableName.valueOf(namespace, tablename);
			if (admin.tableExists(tn)) {
   
				return;
			}
			TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tn);
			List<ColumnFamilyDescriptor> descriptors = new ArrayList<>();
			for (String family : columnFamilies) {
   
				ColumnFamilyDescriptorBuilder descriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(family));
				descriptors.add(descriptorBuilder.build());
			}
			builder.setColumnFamilies(descriptors);
			admin.createTable(builder.build());
		}
		System.out.println("table[" + namespace + ":" + tablename + "] has been created");
	}

	/**
	 * 获取列族的最大 version 值
	 *
	 * @param namespace    命名空间
	 * @param tablename    表名
	 * @param columnFamily 列族
	 * @return 返回最大 version 值
	 */
	public static int getTableColumnFamilyMaxVersion(String namespace, String tablename, String columnFamily) throws IOException {
   
		if (!isTableExists(namespace, tablename)) {
   
			System.out.println("table[" + tablename + "] does not exist");
			return 0;
		}
		try (Admin admin = getConnection().getAdmin()) {
   
			TableName tn = TableName.valueOf(namespace, tablename);
			if (!admin.tableExists(tn)) {
   
				System.out.println("table[" + tablename + "] does not exist!");
				return 0;
			}
			TableDescriptor descriptor = admin.getDescriptor(tn);
			// 获取原表的列族描述信息
			ColumnFamilyDescriptor family = descriptor.getColumnFamily(Bytes.toBytes(columnFamily));
			return family.getMaxVersions();
		} catch (IOException e) {
   
			e.printStackTrace();
		}
		return 0;
	}

	/**
	 * 修改表
	 *
	 * @param namespace 命名空间
	 * @param tablename 表名
	 * @param version   最大版本号
	 */
	public static void modifyTableColumnFamilyMaxVersion(String namespace, String tablename, String columnFamily, int version) throws IOException {
   
		if (!isTableExists(namespace, tablename)) {
   
			System.out.println("table[" + tablename + "] does not exist");
			return;
		}
		try (Admin admin = getConnection().getAdmin()) {
   
			TableName tn = TableName.valueOf(namespace, tablename);
			if (!admin.tableExists(tn)) {
   
				System.out.println("table[" + tablename + "] does not exist!");
				return;
			}
			TableDescriptor descriptor = admin.getDescriptor(tn);
			// 获取原表的列族描述信息
			ColumnFamilyDescriptor family = descriptor.getColumnFamily(Bytes.toBytes(columnFamily));
			{
   
				ColumnFamilyDescriptorBuilder builder = ColumnFamilyDescriptorBuilder.newBuilder(family);
				// 修改 version 后重新构建列族描述信息
				builder.setMaxVersions(version);
				family = builder.build();
			}
			TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(descriptor);
			builder.modifyColumnFamily(family);
			admin.modifyTable(builder.build());
		}
	}

	/**
	 * 删除表
	 *
	 * @param namespace 命名空间
	 * @param tablename 表名
	 */
	public static void deleteTable(String namespace, String tablename) throws IOException {
   
		if (!isTableExists(namespace, tablename)) {
   
			System.out.println("table[" + tablename + "] does not exist");
			return;
		}
		try (Admin admin = getConnection().getAdmin()) {
   
			TableName tn = TableName.valueOf(namespace, tablename);
			if (!admin.isTableDisabled(tn)) {
   
				admin.disableTable(tn);
			}
			admin.deleteTable(tn);
		} catch (IOException e) {
   
			e.printStackTrace();
		}
		boolean disappear = !isTableExists(namespace, tablename);
		if (disappear) {
   
			System.out.println("table[" + namespace + ":" + tablename + "] has been deleted");
		}
	}
}
  1. 创建 HBase 的DML 操作类
public class HBaseDML {
   
	private static final Connection connection = HBaseConnection.getConnection();

	/**
	 * 为表中特定的行的列赋值
	 *
	 * @param namespace 命名空间
	 * @param tablename 表名
	 * @param rowKey    行键
	 * @param column    列名,由列族和列标识符组成,例如 basic:name
	 * @param value     该列的值
	 */
	public static void put(String namespace, String tablename, String rowKey, String column, String value) throws IOException {
   
		if (namespace == null || tablename == null || column == null) {
   
			return;
		}
		String[] strings = column.split(":");
		if (strings.length < 1) {
   
			return;
		}
		String columnName = strings.length > 1 ? strings[1] : "";
		Table table = connection.getTable(TableName.valueOf(namespace, tablename));
		Put put = new Put(Bytes.toBytes(rowKey));
		put.addColumn(Bytes.toBytes(strings[0]), Bytes.toBytes(columnName), Bytes.toBytes(value));
		table.put(put);
		table.close();
	}

	/**
	 * 打印 Cell 并返回 Map 结果
	 *
	 * @param result 数据返回结果
	 * @return 返回 Map 集合
	 */
	private static List<Map<String, Object>> getListFromResult(Result result) {
   
		List<Map<String, Object>> maps = new ArrayList<>();
		assert result != null;
		Cell[] cells = result.rawCells();
		System.out.print(Bytes.toString(result.getRow()) + "[");
		for (Cell cell : cells) {
   
			Map<String, Object> map = new HashMap<>();
			long timestamp = cell.getTimestamp();
			String time = DateFormatUtils.format(timestamp, "yyyy-MM-dd HH:mm:ss");
			String row = Bytes.toString(CellUtil.cloneRow(cell));
			String family = Bytes.toString(CellUtil.cloneFamily(cell));
			String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell));
			String value = Bytes.toString(CellUtil.cloneValue(cell));
			map.put("key", row);
			map.put("timestamp", timestamp);
			map.put("time", time);
			map.put("column", family + ":" + qualifier);
			map.put("value", value);
			System.out.print("(" + family + ":" + qualifier + ") => " + value + "\t");
			maps.add(map);
		}
		System.out.println("]");
		return maps;
	}


	/**
	 * 扫描表的数据
	 *
	 * @param namespace 命名空间
	 * @param tablename 表名
	 * @param start     起始行的主键[包含]
	 * @param stop      结束行的主键[不包含]
	 */
	public static void scan(String namespace, String tablename, String start, String stop) throws IOException {
   
		scan(namespace, tablename, start, stop, null, null);
	}

	/**
	 * 扫描表的数据
	 *
	 * @param namespace 命名空间
	 * @param tablename 表名
	 * @param start     起始行的主键[包含]
	 * @param stop      结束行的主键[不包含]
	 * @param column    列名,由列族和列标识符组成,例如 basic:name
	 * @param value     结束行的主键[不包含]
	 */
	public static void scan(String namespace, String tablename, String start, String stop, String column, String value) throws IOException {
   
		Table table = connection.getTable(TableName.valueOf(namespace, tablename));
		Scan scan = new Scan();
		scan.withStartRow(Bytes.toBytes(start));
		scan.withStopRow(Bytes.toBytes(stop));
		// 过滤器
		if (column != null && column.contains(":") && value != null) {
   
			String[] cs = column.split(":");
			if (cs.length > 0) {
   
				String cn = cs.length > 1 ? cs[1] : "";
				Filter filter = new ColumnValueFilter(
						Bytes.toBytes(cs[0]),
						Bytes.toBytes(cn),
						// 会查找到所有等于 value 的数据
						// 需要注意的是,如果该列不存在,不会被找到
						CompareOperator.EQUAL,
						Bytes.toBytes(value)
				);
				// 1. scan 可以使用 FilterList 组装多个 Filter 后使用
				//    FilterList filters = new FilterList();
				// 2. SingleColumnValueFilter
				//    会查找到所有等于 value 的数据,如果该列不存在,也会被找到
				// 3. ColumnValueFilter
				//    会查找到所有等于 value 的数据,如果该列不存在,不会被找到
				scan.setFilter(filter);
			}
		}
		try (ResultScanner scanner = table.getScanner(scan)) {
   
			for (Result result : scanner) {
   
				List<Map<String, Object>> maps = getListFromResult(result);
				//System.out.println(maps);
			}
		} catch (IOException e) {
   
			e.printStackTrace();
		}
		table.close();
	}

	/**
	 * 获取某行的所有数据
	 *
	 * @param namespace 命名空间
	 * @param tablename 表名
	 * @param rowKey    行主键
	 */
	public static List<Map<String, Object>> get(String namespace, String tablename, String rowKey) throws IOException {
   
		return get(namespace, tablename, rowKey, null);
	}

	/**
	 * 获取某个单元格的数据
	 *
	 * @param namespace 命名空间
	 * @param tablename 表名
	 * @param rowKey    行主键
	 * @param column    列名,由列族和列标识符组成,例如 basic:name
	 * @return
	 */
	public static List<Map<String, Object>> get(String namespace, String tablename, String rowKey, String column) throws IOException {
   
		String[] cs = column != null && column.contains(":") ? column.split(":") : null;
		Table table = connection.getTable(TableName.valueOf(namespace, tablename));
		Get get = new Get(Bytes.toBytes(rowKey));
		if (cs != null && cs.length > 0) {
   
			String columnName = cs.length > 1 ? cs[1] : "";
			get.addColumn(Bytes.toBytes(cs[0]), Bytes.toBytes(columnName));
		}
		get.readAllVersions();
		Result result = null;
		try {
   
			result = table.get(get);
		} catch (IOException e) {
   
			e.printStackTrace();
		}
		assert result != null;
		List<Map<String, Object>> maps = getListFromResult(result);
		table.close();
		return maps;
	}
}
  1. 创建单元测试类
@RunWith(JUnit4.class)
public class JUnit4TestHBase {
   

	@After
	public void testClose() throws IOException {
   
		getConnection().close();
		System.out.println("单元测试结束!");
	}

	/**
	 * DDL 相关测试 —— 命名空间
	 */
	@Test
	public void testNamespace() throws IOException {
   
		HBaseDDL.listNamespace();
		HBaseDDL.createNamespace("tmp");
		HBaseDDL.listNamespace();
		HBaseDDL.createTable("tmp", "stu", "basic", "advanced");
		HBaseDDL.listTable("tmp");
	}

	/**
	 * DDL 相关测试 —— 修改表
	 */
	@Test
	public void testModifyTable() throws IOException {
   
		int version = HBaseDDL.getTableColumnFamilyMaxVersion("tmp", "stu", "basic");
		System.out.println("basic`s max version is:" + version);
		HBaseDDL.modifyTableColumnFamilyMaxVersion("tmp", "stu", "basic", version + 1);
		version = HBaseDDL.getTableColumnFamilyMaxVersion("tmp", "stu", "basic");
		System.out.println("basic`s max version is:" + version);
	}

	/**
	 * DML相关测试
	 */
	@Test
	public void testDML() throws IOException {
   
		HBaseDML.put("tmp", "stu", "1001", "basic:name", "tina");
		HBaseDML.put("tmp", "stu", "1001", "basic", "this is basic information!");
		HBaseDML.put("tmp", "stu", "1001", "basic:age", "19");
		//HBaseDML.put("tmp", "stu", "1002", "basic:name", "Jenkins");
		HBaseDML.put("tmp", "stu", "1002", "basic:age", "20");
		HBaseDML.put("tmp", "stu", "1003", "basic:name", "Lucas");
		HBaseDML.put("tmp", "stu", "1003", "basic:age", "19");

		System.out.println("==GET[1003]basic:name============");
		HBaseDML.get("tmp", "stu", "1003", "basic:name");
		//System.out.println(maps);
		System.out.println("==GET[1003]======================");
		HBaseDML.get("tmp", "stu", "1003");
		//System.out.println(maps);
		System.out.println("==SCAN[1001:1003]===========================");
		HBaseDML.scan("tmp", "stu", "1001", "1003");
		System.out.println("==SCAN(name=tina)================");
		HBaseDML.scan("tmp", "stu", "1001", "1003", "basic:name", "tina");
	}

	/**
	 * DDL 相关测试 —— 删除命名空间和表
	 */
	@Test
	public void testDeleteTable() throws IOException {
   
		HBaseDDL.listTable();
		HBaseDDL.deleteTable("tmp", "stu");
		HBaseDDL.listTable();

		HBaseDDL.listNamespace();
		HBaseDDL.deleteNamespace("tmp");
		HBaseDDL.listNamespace();
	}
}

相关推荐

  1. HBASE基础

    2024-01-13 23:00:06       41 阅读
  2. 【笔记】Hbase基础笔记

    2024-01-13 23:00:06       20 阅读
  3. hbase基础(三)

    2024-01-13 23:00:06       17 阅读
  4. HBase基础知识(六):HBase 对接 Hive

    2024-01-13 23:00:06       34 阅读
  5. hbase基础搭建

    2024-01-13 23:00:06       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-13 23:00:06       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-13 23:00:06       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-13 23:00:06       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-13 23:00:06       20 阅读

热门阅读

  1. 头像的渲染及表单的验证

    2024-01-13 23:00:06       37 阅读
  2. Open3D 从点云构建八叉树(13)

    2024-01-13 23:00:06       38 阅读
  3. 【UML】第20篇 UML系列完结篇

    2024-01-13 23:00:06       40 阅读
  4. Vue3 和Vue2的区别,以及钩子函数的使用

    2024-01-13 23:00:06       36 阅读
  5. vue3基础:单文件组件介绍

    2024-01-13 23:00:06       42 阅读
  6. pi i2c 读取24c256

    2024-01-13 23:00:06       40 阅读
  7. C++(20):list通过erase, erase_if删除元素

    2024-01-13 23:00:06       31 阅读
  8. 【Linux】网络版计算器代码

    2024-01-13 23:00:06       41 阅读
  9. Python 面向对象之多态

    2024-01-13 23:00:06       36 阅读