Hadoop学习教程,从入门到精通, HBase 分布式数据库 — 完整知识点与案例代码(8)

HBase 分布式数据库 --- 完整知识点与案例代码


一、HBase 概述

1.1 HBase 的技术特点

HBase(Hadoop Database)是一个分布式的、面向列的开源数据库,基于 Google Bigtable 的设计思想构建。

核心技术特点:

特点 说明
海量存储 可存储数十亿行 × 数百万列的数据
面向列 按列族存储,同一列族的数据连续存放
稀疏性 空白单元格不占用存储空间
多版本 每个单元格可保存多个版本的数据,用时间戳区分
强一致性 同一行的读写操作是原子性的
高可靠性 基于 HDFS 实现数据自动备份
线性扩展 可通过增加 RegionServer 节点水平扩展

1.2 HBase 与传统关系数据库的区别

复制代码
┌──────────────┬──────────────────────┬──────────────────────┐
│   比较项      │   传统关系数据库(RDBMS)│   HBase              │
├──────────────┼──────────────────────┼──────────────────────┤
│ 数据类型      │ 丰富的数据类型         │ 只有字节数组(byte[])  │
│ 数据操作      │ SQL(增删改查+JOIN)   │ 只有简单的Get/Put/Scan/Delete │
│ 存储模式      │ 基于行存储             │ 基于列族存储           │
│ 数据索引      │ 支持多级索引           │ 只有行键(RowKey)索引   │
│ 事务支持      │ 完整的ACID事务         │ 仅行级别的原子性       │
│ 扩展方式      │ 纵向扩展(升级硬件)     │ 横向扩展(增加节点)     │
│ 数据量级别    │ 百万~千万级            │ 十亿~百亿级            │
│ 稀疏数据      │ 用NULL填充,浪费空间   │ 空白不存储,节省空间   │
└──────────────┴──────────────────────┴──────────────────────┘

1.3 HBase 与 Hadoop 中其他组件的关系

复制代码
                    ┌─────────┐
                    │  HBase   │  ← 提供实时读写能力
                    └────┬────┘
                         │
           ┌─────────────┼─────────────┐
           ▼             ▼             ▼
     ┌──────────┐  ┌──────────┐  ┌──────────┐
     │   HDFS   │  │MapReduce │  │  ZooKeeper│
     │ (底层存储)│  │ (批量计算)│  │ (协调服务) │
     └──────────┘  └──────────┘  └──────────┘
  • HDFS:HBase 的底层存储系统,所有数据最终存放在 HDFS 上
  • MapReduce:HBase 可作为 MapReduce 的数据源和数据目标
  • ZooKeeper:负责 HBase 的 Master 选举、RegionServer 监控、元数据管理

二、HBase 系统架构和数据访问流程

2.1 HBase 系统架构

复制代码
┌──────────────────────────────────────────────────────────┐
│                        Client                             │
│              (HBase Shell / Java API / REST)              │
└────────────────────────┬─────────────────────────────────┘
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
   ┌────────────┐ ┌────────────┐ ┌────────────┐
   │  ZooKeeper │ │ ZooKeeper  │ │ ZooKeeper  │  ← 集群协调
   │  Server 1  │ │ Server 2   │ │ Server 3   │
   └────────────┘ └────────────┘ └────────────┘
          │
          ▼
   ┌────────────┐
   │ HBase Master│  ← 管理Region分配、DDL操作
   └────────────┘
          │
   ┌──────┼──────┬──────────────┐
   ▼      ▼      ▼              ▼
┌──────┐┌──────┐┌──────┐  ┌──────────┐
│Region││Region││Region│  │HLog(WAL) │
│Server││Server││Server│  │预写日志   │
│  1   ││  2   ││  3   │  └──────────┘
└──┬───┘└──┬───┘└──┬───┘
   │       │       │
   ▼       ▼       ▼
┌──────────────────────────┐
│         HDFS             │  ← 底层分布式文件存储
│  (DataNode 1, 2, 3...)   │
└──────────────────────────┘

各组件功能:

组件 功能
Master 负责 Region 的分配、DDL 操作(创建/删除表)、负载均衡
RegionServer 负责处理数据的读写请求,管理多个 Region
Region 表中数据按行键范围划分的子表,分布式存储的基本单位
ZooKeeper 存储元数据表位置、Master 地址、RegionServer 状态
HLog (WAL) 预写日志,保证数据写入的可靠性,防止内存数据丢失
MemStore 内存中的写缓存,数据先写入 MemStore,满后刷写到 HFile
StoreFile (HFile) 磁盘上的存储文件,实际存储在 HDFS 上

2.2 HBase 数据访问流程

写数据流程:

复制代码
Client → ZooKeeper(获取Meta表位置) → Meta表(定位RegionServer)
       → RegionServer → WAL(预写日志) → MemStore(内存缓存)
       → MemStore满时 → 刷写到StoreFile(HFile)

读数据流程:

复制代码
Client → ZooKeeper(获取Meta表位置) → Meta表(定位RegionServer)
       → RegionServer → BlockCache(读缓存,命中则返回)
       → 未命中 → MemStore → StoreFile(HFile)
       → 合并结果返回Client

三、HBase 数据表

3.1 HBase 数据表逻辑视图

HBase 表由 行键(RowKey)、列族(Column Family)、列限定符(Qualifier)、时间戳(Timestamp)、值(Value) 五维组成。

复制代码
RowKey    │ Column Family: info        │ Column Family: score
          │ name    age     gender     │ chinese  math    english
──────────┼────────────────────────────┼──────────────────────────
row_001   │ "Tom"  "20"   "M"         │ "89"    "95"    "88"
row_002   │ "Jerry" "21"               │ "76"            "92"
row_003   │                   "F"      │ "90"    "85"

关键概念:

  • RowKey:行的唯一标识,按字典序排列,是 HBase 中唯一的索引
  • Column Family:列族,必须在建表时预先定义
  • Column Qualifier:列限定符,可以动态添加,无需提前定义
  • Timestamp:每个单元格可存储多个版本,用时间戳区分
  • Value:实际存储的数据,以字�组形式存储

3.2 HBase 数据表物理视图

在物理存储上,HBase 按 列族 分开存储:

复制代码
StoreFile 1 (info列族):
  row_001 → info:name = "Tom"
  row_001 → info:age  = "20"
  row_002 → info:name = "Jerry"
  row_002 → info:age  = "21"

StoreFile 2 (score列族):
  row_001 → score:chinese = "89"
  row_001 → score:math    = "95"
  row_002 → score:english = "92"

每个列族对应一个 Store,Store 包含一个 MemStore 和多个 StoreFile。


3.3 HBase 数据表面向列的存储

复制代码
面向行存储(RDBMS):
┌─────┬───────┬─────┬────────┬─────────┬───────┬─────────┐
│ ID  │ Name  │ Age │ Gender │ Chinese │ Math  │ English │
│ 001 │ Tom   │ 20  │ M      │ 89      │ 95    │ 88      │
│ 002 │ Jerry │ 21 │        │ 76      │       │ 92      │
└─────┴───────┴─────┴────────┴─────────┴───────┴─────────┘

面向列存储(HBase):
info列族:                          score列族:
┌───────┬──────┬──────┐            ┌─────────┬──────┬─────────┐
│ RowKey│ name │ age  │            │ RowKey  │chinese│english │
│ 001   │ Tom  │ 20   │            │ 001     │ 89   │ 88     │
│ 002   │ Jerry│ 21   │            │ 002     │ 76   │ 92     │
└───────┴──────┴──────┘            └─────────┴──────┴─────────┘

优势:

  • 列式存储中,同列数据类型相同,压缩率更高
  • 查询某几列时无需读取整行数据,I/O 更少

3.4 HBase 数据表的查询方式

HBase 提供四种基本查询操作:

操作 方法 说明
Get get(rowkey) 根据行键获取单行数据
Scan scan() 扫描表中多行数据,可设置起止行键和过滤器
Put put(row, cf, qualifier, value) 写入/更新数据
Delete delete(row, cf, qualifier) 删除数据

四、HBase 表结构设计

RowKey 设计原则

复制代码
设计原则:
1. 长度原则:建议 10~100 字节,过长浪费存储
2. 唯一性原则:RowKey 必须唯一
3. 散列原则:避免热点问题(数据集中在一个 Region)
4. 业务原则:将最常用的查询条件放在 RowKey 中

避免热点的常见方法:

java 复制代码
// 方法1:反转RowKey(将手机号后4位放前面)
// 原始: 13812345678 → 反转: 87654321831

// 方法2:加盐(在RowKey前加随机前缀)
// 原始: row001 → 加盐: a_row001, b_row001, c_row001

// 方法3:哈希(对RowKey取哈希值)
// 原始: user_001 → MD5哈希: 5d41402abc4b2...

五、HBase 的安装与配置

5.1 下载安装文件

bash 复制代码
# 第一步:从Apache官网下载HBase安装包
wget https://archive.apache.org/dist/hbase/2.4.17/hbase-2.4.17-bin.tar.gz

# 第二步:解压到指定目录
tar -zxvf hbase-2.4.17-bin.tar.gz -C /usr/local/

# 第三步:重命名目录,方便后续操作
mv /usr/local/hbase-2.4.17 /usr/local/hbase

5.2 配置环境变量

bash 复制代码
# 编辑系统环境变量配置文件
sudo vi /etc/profile

# 在文件末尾添加以下内容:
# 设置HBase的安装根目录
export HBASE_HOME=/usr/local/hbase

# 将HBase的bin目录添加到系统PATH中,使hbase命令全局可用
export PATH=$HBASE_HOME/bin:$PATH

# 使配置立即生效
source /etc/profile

5.3 添加用户权限

bash 复制代码
# 确保当前用户对HBase目录有读写权限
sudo chown -R hadoop:hadoop /usr/local/hbase

# 验证权限
ls -ld /usr/local/hbase
# 输出类似: drwxr-xr-x 7 hadoop hadoop 4096 ...

5.4 查看HBase版本信息

bash 复制代码
# 查看HBase版本
hbase version

# 输出示例:
# HBase 2.4.17
# Source code repository ...
# ...

5.5 单机模式配置

单机模式下 HBase 不依赖 HDFS 和 ZooKeeper,使用本地文件系统。

xml 复制代码
<!-- 编辑文件:$HBASE_HOME/conf/hbase-site.xml -->
<configuration>
    <!-- 指定HBase数据存储在本地文件系统的路径 -->
    <property>
        <name>hbase.rootdir</name>
        <value>file:///usr/local/hbase/data</value>
    </property>

    <!-- 指定HBase的临时文件目录 -->
    <property>
        <name>hbase.tmp.dir</name>
        <value>/usr/local/hbase/tmp</value>
    </property>
</configuration>
bash 复制代码
# 启动HBase
start-hbase.sh

# 验证是否启动成功
# 使用jps命令查看Java进程,应看到HMaster进程
jps
# 输出应包含:HMaster

# 进入HBase Shell
hbase shell

5.6 伪分布式模式配置

伪分布式模式下 HBase 使用 HDFS 存储数据,但所有进程运行在一台机器上。

xml 复制代码
<!-- 编辑文件:$HBASE_HOME/conf/hbase-site.xml -->
<configuration>
    <!-- 启用分布式模式(设为true表示运行在分布式环境) -->
    <property>
        <name>hbase.cluster.distributed</name>
        <value>true</value>
    </property>

    <!-- 指定HBase数据存储在HDFS上的路径 -->
    <property>
        <name>hbase.rootdir</name>
        <value>hdfs://localhost:9000/hbase</value>
    </property>

    <!-- 指定ZooKeeper的数据目录 -->
    <property>
        <name>hbase.zookeeper.property.dataDir</name>
        <value>/usr/local/zookeeper/data</value>
    </property>

    <!-- 指定ZooKeeper的端口号 -->
    <property>
        <name>hbase.zookeeper.property.clientPort</name>
        <value>2181</value>
    </property>
</configuration>
bash 复制代码
# 确保HDFS已启动
start-dfs.sh

# 确保ZooKeeper已启动(HBase自带ZooKeeper,也可使用独立的)
# 如果使用独立ZooKeeper:
# zkServer.sh start

# 启动HBase
start-hbase.sh

# 验证各进程
jps
# 应看到:HMaster、HRegionServer、HQuorumPeer(自带ZK)等进程

六、HBase 的 Shell 操作

6.1 基本操作

bash 复制代码
# 进入HBase Shell交互环境
hbase shell

# 查看所有可用命令帮助
help

# 查看集群状态
status
# 输出示例:1 active master, 0 backup masters, 1 servers, 0 dead, 3.0000 average load

# 查看当前所有表
list
# 输出示例:TABLE → student, teacher ...

# 退出HBase Shell
exit

6.2 创建表

bash 复制代码
# 语法:create '表名', '列族1', '列族2', ...

# 创建student表,包含info和score两个列族
create 'student', 'info', 'score'

# 创建表时指定列族属性(版本数、压缩方式等)
# versions => 3 表示该列族最多保存3个版本的数据
# compression => 'SNAPPY' 表示使用Snappy压缩算法
create 'employee', {NAME => 'basic_info', VERSIONS => 3}, {NAME => 'work_info', BLOOMFILTER => 'ROW'}

# 查看表的描述信息
describe 'student'
# 输出示例:
# Table student is ENABLED
# student
# COLUMN FAMILIES DESCRIPTION
# {NAME => 'info', VERSIONS => '1', ...}
# {NAME => 'score', VERSIONS => '1', ...}

# 验证表是否存在
exists 'student'
# 输出示例:Table student does exist

6.3 插入与更新表中的数据

bash 复制代码
# 语法:put '表名', '行键', '列族:列限定符', '值'

# ---- 插入第一条记录 ----
# 插入学生Tom的基本信息
put 'student', 'row_001', 'info:name', 'Tom'
put 'student', 'row_001', 'info:age', '20'
put 'student', 'row_001', 'info:gender', 'Male'

# 插入学生Tom的成绩信息
put 'student', 'row_001', 'score:chinese', '89'
put 'student', 'row_001', 'score:math', '95'
put 'student', 'row_001', 'score:english', '88'

# ---- 插入第二条记录 ----
put 'student', 'row_002', 'info:name', 'Jerry'
put 'student', 'row_002', 'info:age', '21'
put 'student', 'row_002', 'info:gender', 'Male'
put 'student', 'row_002', 'score:chinese', '76'
put 'student', 'row_002', 'score:math', '82'
put 'student', 'row_002', 'score:english', '92'

# ---- 插入第三条记录 ----
put 'student', 'row_003', 'info:name', 'Alice'
put 'student', 'row_003', 'info:age', '20'
put 'student', 'row_003', 'info:gender', 'Female'
put 'student', 'row_003', 'score:chinese', '90'
put 'student', 'row_003', 'score:math', '85'

# ---- 更新操作 ----
# 更新操作本质上也是put操作,新值会覆盖旧值(或作为新版本保留)
# 将Tom的数学成绩从95修改为98
put 'student', 'row_001', 'score:math', '98'
# 此时若列族配置了versions>1,则两条记录都会保留
# 最新版本的数据在查询时默认返回

6.4 查看表中的数据

bash 复制代码
# ==== 方式1:get命令 ------ 获取单行数据 ====
# 语法:get '表名', '行键'

# 获取row_001的所有列族所有数据
get 'student', 'row_001'
# 输出示例:
# COLUMN                   CELL
#  info:age                timestamp=..., value=20
#  info:gender             timestamp=..., value=Male
#  info:name               timestamp=..., value=Tom
#  score:chinese           timestamp=..., value=89
#  score:english           timestamp=..., value=88
#  score:math              timestamp=..., value=98

# 获取row_001的info列族的所有列
get 'student', 'row_001', 'info'

# 获取row_001的info列族的name列
get 'student', 'row_001', 'info:name'

# 获取row_001的多个指定列
get 'student', 'row_001', {COLUMN => ['info:name', 'score:math']}

# 获取row_001的最近2个版本的数据(需要列族配置VERSIONS>1)
get 'student', 'row_001', {COLUMN => 'score:math', VERSIONS => 2}

# ==== 方式2:scan命令 ------ 扫描多行数据 ====
# 语法:scan '表名', {可选参数}

# 扫描整张表的所有数据
scan 'student'

# 扫描指定列族
scan 'student', {COLUMNS => 'info'}

# 扫描指定列
scan 'student', {COLUMNS => ['info:name', 'score:chinese']}

# 限制返回的行数(LIMIT)
scan 'student', {LIMIT => 2}

# 设置扫描的起始行键(STARTROW)和结束行键(STOPROW)
# 注意:STARTROW是包含的,STOPROW是不包含的
scan 'student', {STARTROW => 'row_001', STOPROW => 'row_003'}

# 反向扫描(REVERSED => true)
scan 'student', {REVERSED => true}

# 设置返回的版本数
scan 'student', {VERSIONS => 3}

# 只显示行键(RAW => true + LIMIT)
scan 'student', {RAW => true, LIMIT => 3}

6.5 删除表中的数据

bash 复制代码
# ==== 删除指定单元格 ====
# 语法:delete '表名', '行键', '列族:列限定符'
# 注意:删除前必须先禁用表 或 直接delete(2.x版本支持直接删除单元格)

# 删除row_002的英语成绩
delete 'student', 'row_002', 'score:english'

# ==== 删除指定版本的数据 ====
# delete指定时间戳版本
delete 'student', 'row_001', 'score:math', 1634567890123

# ==== 删除整行数据 ====
# 语法:deleteall '表名', '行键'
# 删除row_003的所有数据(所有列族、所有列)
deleteall 'student', 'row_003'

# 删除row_001的某一列的所有版本
deleteall 'student', 'row_001', 'info:age'

# 删除时指定时间戳
deleteall 'student', 'row_001', 'info:gender', {TIMESTAMP => 1634567890123}

# ==== 清空整张表 ====
# 语法:truncate '表名'
# truncate会先禁用表、删除表、再重新创建表
truncate 'student'

6.6 表的启用/禁用

bash 复制代码
# HBase中,修改表结构、删除表之前,必须先禁用表

# 查看表的状态(ENABLED 或 DISABLED)
is_enabled 'student'
is_disabled 'student'

# 禁用表
disable 'student'

# 启用表
enable 'student'

# 禁用所有表(慎用!)
# disable_all '.*'

# 启用所有表
# enable_all '.*'

6.7 修改表结构

bash 复制代码
# ==== 修改列族属性 ====
# 语法:alter '表名', {NAME => '列族名', 属性 => 值}

# 将info列族的最大版本数改为5
alter 'student', {NAME => 'info', VERSIONS => 5}

# 为info列族设置数据存活时间(TTL),单位为秒
# 31536000秒 = 1年,超过时间的数据会被自动清理
alter 'student', {NAME => 'info', TTL => 31536000}

# ==== 新增列族 ====
# 为student表新增一个address列族
alter 'student', {NAME => 'address'}

# 同时新增多个列族
alter 'student', {NAME => 'contact', VERSIONS => 2}, {NAME => 'remark'}

# ==== 删除列族 ====
# 删除student表的address列族
alter 'student', {NAME => 'address', METHOD => 'delete'}

# 另一种删除列族的写法
alter 'student', 'delete' => 'address'

6.8 删除HBase表

bash 复制代码
# HBase中删除表必须先禁用再删除

# 第一步:禁用表
disable 'student'

# 第二步:删除表
drop 'student'

# 验证表已删除
exists 'student'
# 输出:Table student does not exist

# 删除所有匹配正则表达式的表(慎用!)
# drop_all 'temp_.*'

七、HBase 的 Java API 操作

7.1 HBase 数据库管理 API(Admin 管理类)

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.HColumnDescriptor;

import java.io.IOException;

/**
 * HBaseAdminDemo - 演示HBase Admin管理API
 * 功能:创建表、删除表、修改表结构、查看表信息等DDL操作
 */
public class HBaseAdminDemo {

    // 定义HBase连接和Admin对象(类级别,便于各方法复用)
    private static Connection connection = null;   // HBase连接对象
    private static Admin admin = null;             // HBase管理对象

    /**
     * 获取HBase连接和Admin实例
     * 在所有操作之前必须先调用此方法建立连接
     */
    public static void init() throws IOException {
        // 第一步:创建HBase配置对象
        // HBaseConfiguration.create() 会自动加载 hbase-site.xml 配置文件
        Configuration conf = HBaseConfiguration.create();

        // 第二步:设置ZooKeeper的连接地址
        // HBase通过ZooKeeper来协调集群,客户端需要知道ZK的地址才能连接HBase
        conf.set("hbase.zookeeper.quorum", "localhost");

        // 第三步:设置ZooKeeper的端口号(默认2181)
        conf.set("hbase.zookeeper.property.clientPort", "2181");

        // 第四步:通过配置信息创建HBase连接(重量级操作,应复用)
        connection = ConnectionFactory.createConnection(conf);

        // 第五步:从连接中获取Admin对象,用于执行DDL操作
        admin = connection.getAdmin();
    }

    /**
     * 关闭HBase连接和Admin实例
     * 在所有操作完成后必须调用此方法释放资源
     */
    public static void close() throws IOException {
        // 先关闭Admin对象
        if (admin != null) {
            admin.close();
        }
        // 再关闭连接对象
        if (connection != null) {
            connection.close();
        }
    }

    /**
     * 创建表
     * @param tableName 表名
     * @param columnFamilies 列族名称数组(可传入多个列族)
     */
    public static void createTable(String tableName, String[] columnFamilies) throws IOException {
        // 第一步:将String类型的表名转换为TableName对象
        TableName table = TableName.valueOf(tableName);

        // 第二步:判断表是否已存在,避免重复创建
        if (admin.tableExists(table)) {
            System.out.println("表 " + tableName + " 已存在!");  // 提示表已存在
            return;  // 直接返回,不重复创建
        }

        // 第三步:创建表描述器对象(HBase 1.x/2.x API)
        HTableDescriptor tableDescriptor = new HTableDescriptor(table);

        // 第四步:遍历列族数组,为表添加列族描述
        for (String cf : columnFamilies) {
            // 创建列族描述器,指定列族名称
            HColumnDescriptor columnDescriptor = new HColumnDescriptor(cf);
            // 设置列族最大版本数为5(该列族最多保存5个版本的数据)
            columnDescriptor.setMaxVersions(5);
            // 将列族描述器添加到表描述器中
            tableDescriptor.addFamily(columnDescriptor);
        }

        // 第五步:调用Admin的createTable方法执行建表操作
        admin.createTable(tableDescriptor);

        // 第六步:验证表是否创建成功
        if (admin.tableExists(table)) {
            System.out.println("表 " + tableName + " 创建成功!");
        }
    }

    /**
     * 删除表
     * @param tableName 要删除的表名
     */
    public static void deleteTable(String tableName) throws IOException {
        // 将表名字符串转换为TableName对象
        TableName table = TableName.valueOf(tableName);

        // 第一步:检查表是否存在
        if (!admin.tableExists(table)) {
            System.out.println("表 " + tableName + " 不存在,无法删除!");
            return;
        }

        // 第二步:删除表之前必须先禁用表(HBase强制要求)
        // 如果表处于ENABLED状态,直接调用deleteTable会抛出异常
        if (admin.isTableEnabled(table)) {
            admin.disableTable(table);  // 禁用表
            System.out.println("表 " + tableName + " 已禁用");
        }

        // 第三步:执行删除操作
        admin.deleteTable(table);
        System.out.println("表 " + tableName + " 已成功删除!");
    }

    /**
     * 修改表结构 ------ 为表添加新的列族
     * @param tableName  表名
     * @param newFamily  要新增的列族名称
     */
    public static void addColumnFamily(String tableName, String newFamily) throws IOException {
        TableName table = TableName.valueOf(tableName);

        // 检查表是否存在
        if (!admin.tableExists(table)) {
            System.out.println("表 " + tableName + " 不存在!");
            return;
        }

        // 创建列族描述器
        HColumnDescriptor newColumn = new HColumnDescriptor(newFamily);

        // 使用TableDescriptorBuilder构建修改后的表描述器
        // HBase 2.x 推荐使用TableDescriptorBuilder
        admin.modifyColumnFamily(table, newColumn);

        // 或者使用以下方式修改表结构(新增列族):
        // admin.addColumnFamily(table, newColumn);
        // 注意:HBase 2.x API 中 modifyColumnFamily 用于修改已有列族属性
        // 新增列族应使用 admin.addColumnFamily()

        admin.addColumnFamily(table, newColumn);
        System.out.println("列族 " + newFamily + " 添加成功!");
    }

    /**
     * 修改表结构 ------ 删除表中的指定列族
     * @param tableName  表名
     * @param familyName 要删除的列族名称
     */
    public static void deleteColumnFamily(String tableName, String familyName) throws IOException {
        TableName table = TableName.valueOf(tableName);

        // 检查表是否存在
        if (!admin.tableExists(table)) {
            System.out.println("表 " + tableName + " 不存在!");
            return;
        }

        // 删除指定列族(需要将列族名转换为byte[])
        admin.deleteColumnFamily(table, Bytes.toBytes(familyName));
        System.out.println("列族 " + familyName + " 删除成功!");
    }

    /**
     * 列出所有表名
     */
    public static void listTables() throws IOException {
        // 获取所有表的描述器数组
        HTableDescriptor[] tables = admin.listTables();
        System.out.println("===== HBase中的所有表 =====");
        // 遍历并打印每张表的名称
        for (HTableDescriptor table : tables) {
            System.out.println("表名:" + table.getNameAsString());
        }
    }

    /**
     * 查看表的详细描述信息
     * @param tableName 表名
     */
    public static void describeTable(String tableName) throws IOException {
        TableName table = TableName.valueOf(tableName);

        // 检查表是否存在
        if (!admin.tableExists(table)) {
            System.out.println("表 " + tableName + " 不存在!");
            return;
        }

        // 获取表的描述器
        HTableDescriptor tableDescriptor = admin.getTableDescriptor(table);
        System.out.println("表名:" + tableDescriptor.getNameAsString());

        // 获取表中所有列族的描述器
        HColumnDescriptor[] columnFamilies = tableDescriptor.getColumnFamilies();
        // 遍历并打印每个列族的详细信息
        for (HColumnDescriptor cf : columnFamilies) {
            System.out.println("  列族名:" + cf.getNameAsString());       // 列族名称
            System.out.println("  最大版本数:" + cf.getMaxVersions());    // 最大版本数
            System.out.println("  压缩方式:" + cf.getCompressionType()); // 压缩算法
            System.out.println("  TTL:" + cf.getTimeToLive());           // 数据存活时间
            System.out.println("  ---");
        }
    }

    /**
     * 主方法 ------ 演示完整的Admin操作流程
     */
    public static void main(String[] args) {
        try {
            // 1. 初始化连接
            init();

            // 2. 创建名为"students"的表,包含info和score两个列族
            createTable("students", new String[]{"info", "score"});

            // 3. 列出所有表
            listTables();

            // 4. 查看students表的详细信息
            describeTable("students");

            // 5. 为students表添加一个新列族address
            addColumnFamily("students", "address");

            // 6. 再次查看表结构,确认address列族已添加
            describeTable("students");

            // 7. 删除address列族
            deleteColumnFamily("students", "address");

            // 8. 删除students表
            // deleteTable("students");

            // 9. 关闭连接
            close();
        } catch (IOException e) {
            // 捕获并打印IO异常
            e.printStackTrace();
        }
    }
}

7.2 HBase 数据库表 API(Connection 和 Table 接口)

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;

import java.io.IOException;

/**
 * HBaseTableDemo - 演示HBase的Connection和Table接口
 * 功能:获取连接、获取表对象、获取Region信息等
 */
public class HBaseTableDemo {

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

        // ========== 第一步:创建HBase配置 ==========
        // HBaseConfiguration.create() 方法会自动读取classpath下的hbase-site.xml
        Configuration conf = HBaseConfiguration.create();

        // 手动设置ZooKeeper集群地址(如果hbase-site.xml中未配置)
        // 多个ZK地址用逗号分隔,如"zk1:2181,zk2:2181,zk3:2181"
        conf.set("hbase.zookeeper.quorum", "localhost");

        // 设置ZK客户端端口
        conf.set("hbase.zookeeper.property.clientPort", "2181");

        // ========== 第二步:创建Connection连接 ==========
        // ConnectionFactory.createConnection() 创建一个与HBase集群的连接
        // 注意:Connection是重量级对象,整个应用应只创建一个,后续复用
        Connection connection = ConnectionFactory.createConnection(conf);
        System.out.println("HBase连接创建成功!");
        System.out.println("连接状态:" + (connection.isClosed() ? "已关闭" : "正常"));

        // ========== 第三步:获取Table对象 ==========
        // 通过Connection.getTable()获取指定表的操作句柄
        // Table是轻量级对象,用于执行数据的增删改查操作
        TableName tableName = TableName.valueOf("students");
        Table table = connection.getTable(tableName);
        System.out.println("获取表对象成功:" + table.getName());

        // ========== 第四步:获取表名信息 ==========
        System.out.println("表的限定名:" + table.getName().getNameAsString());
        // getNameAsString() 返回完整的表名字符串

        // ========== 第五步:获取Region信息 ==========
        // 通过Connection获取RegionLocator,查看表在集群中的Region分布
        RegionLocator regionLocator = connection.getRegionLocator(tableName);

        // 获取表的所有Region信息
        // 每个HRegionInfo包含Region的起始行键、结束行键、RegionServer地址等
        for (HRegionLocation location : regionLocator.getAllRegionLocations()) {
            System.out.println("Region名称:" + location.getRegion().getEncodedName());
            System.out.println("RegionServer地址:" + location.getHostname());
            System.out.println("Region端口:" + location.getPort());
            System.out.println("起始行键:" + Bytes.toString(location.getRegion().getStartKey()));
            System.out.println("结束行键:" + Bytes.toString(location.getRegion().getEndKey()));
            System.out.println("---");
        }

        // ========== 第六步:获取Admin对象并查看集群信息 ==========
        Admin admin = connection.getAdmin();
        // 获取集群ID
        System.out.println("集群ID:" + admin.getClusterId());
        // 获取HBase Master地址
        System.out.println("Master地址:" + admin.getMaster().getServerName());
        // 获取正在运行的RegionServer列表
        ServerName[] servers = admin.getRegionServers().toArray(new ServerName[0]);
        for (ServerName server : servers) {
            System.out.println("RegionServer:" + server.getHostname() + ":" + server.getPort());
        }

        // ========== 第七步:释放资源 ==========
        // 按照创建的反顺序关闭,先关闭Table,再关闭Connection
        regionLocator.close();  // 关闭Region定位器
        table.close();          // 关闭表对象
        admin.close();          // 关闭Admin
        connection.close();     // 关闭连接
        System.out.println("所有资源已释放!");
    }
}

7.3 HBase 数据库表行列 API(CRUD 操作)

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * HBaseCRUDDemo - 演示HBase的完整CRUD操作
 * 包括:单条插入、批量插入、单条查询、扫描查询、过滤器查询、更新、删除等
 */
public class HBaseCRUDDemo {

    private static Connection connection = null;  // HBase连接对象
    private static Table table = null;            // 表操作对象

    /**
     * 初始化连接
     */
    public static void init() throws IOException {
        // 创建HBase配置
        Configuration conf = HBaseConfiguration.create();
        // 设置ZooKeeper地址
        conf.set("hbase.zookeeper.quorum", "localhost");
        conf.set("hbase.zookeeper.property.clientPort", "2181");
        // 创建连接
        connection = ConnectionFactory.createConnection(conf);
        // 获取表对象(操作students表)
        table = connection.getTable(TableName.valueOf("students"));
    }

    /**
     * 关闭连接,释放资源
     */
    public static void close() throws IOException {
        if (table != null) table.close();
        if (connection != null) connection.close();
    }

    // ==================== 插入数据 ====================

    /**
     * 插入单条数据(Put操作)
     */
    public static void putData() throws IOException {
        System.out.println("===== 插入单条数据 =====");

        // 第一步:创建Put对象,参数为行键(byte[]类型)
        // Bytes.toBytes() 将字符串转换为字节数组
        Put put = new Put(Bytes.toBytes("row_001"));

        // 第二步:向Put对象中添加列数据
        // addColumn(列族, 列限定符, 值)
        put.addColumn(
            Bytes.toBytes("info"),       // 列族名
            Bytes.toBytes("name"),       // 列限定符
            Bytes.toBytes("Tom")         // 值
        );

        // 继续添加其他列
        put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("20"));
        put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("gender"), Bytes.toBytes("Male"));
        put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("chinese"), Bytes.toBytes("89"));
        put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("math"), Bytes.toBytes("95"));
        put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("english"), Bytes.toBytes("88"));

        // 第三步:执行Put操作,将数据写入HBase
        table.put(put);
        System.out.println("row_001 数据插入成功!");
    }

    /**
     * 批量插入数据(Batch Put操作)
     */
    public static void putBatchData() throws IOException {
        System.out.println("===== 批量插入数据 =====");

        // 创建一个Put列表,用于存放多条Put操作
        List<Put> putList = new ArrayList<>();

        // ---- 构造row_002的数据 ----
        Put put2 = new Put(Bytes.toBytes("row_002"));
        put2.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("Jerry"));
        put2.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("21"));
        put2.addColumn(Bytes.toBytes("info"), Bytes.toBytes("gender"), Bytes.toBytes("Male"));
        put2.addColumn(Bytes.toBytes("score"), Bytes.toBytes("chinese"), Bytes.toBytes("76"));
        put2.addColumn(Bytes.toBytes("score"), Bytes.toBytes("math"), Bytes.toBytes("82"));
        put2.addColumn(Bytes.toBytes("score"), Bytes.toBytes("english"), Bytes.toBytes("92"));
        putList.add(put2);  // 将put2加入列表

        // ---- 构造row_003的数据 ----
        Put put3 = new Put(Bytes.toBytes("row_003"));
        put3.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("Alice"));
        put3.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("20"));
        put3.addColumn(Bytes.toBytes("info"), Bytes.toBytes("gender"), Bytes.toBytes("Female"));
        put3.addColumn(Bytes.toBytes("score"), Bytes.toBytes("chinese"), Bytes.toBytes("90"));
        put3.addColumn(Bytes.toBytes("score"), Bytes.toBytes("math"), Bytes.toBytes("85"));
        put3.addColumn(Bytes.toBytes("score"), Bytes.toBytes("english"), Bytes.toBytes("91"));
        putList.add(put3);  // 将put3加入列表

        // ---- 构造row_004的数据 ----
        Put put4 = new Put(Bytes.toBytes("row_004"));
        put4.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("Bob"));
        put4.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("22"));
        put4.addColumn(Bytes.toBytes("score"), Bytes.toBytes("chinese"), Bytes.toBytes("88"));
        put4.addColumn(Bytes.toBytes("score"), Bytes.toBytes("math"), Bytes.toBytes("90"));
        putList.add(put4);

        // 批量写入:一次调用提交所有Put操作,效率远高于逐条写入
        table.put(putList);
        System.out.println("批量插入 " + putList.size() + " 条数据成功!");
    }

    // ==================== 查询数据 ====================

    /**
     * Get查询 ------ 根据行键获取单行数据
     */
    public static void getData() throws IOException {
        System.out.println("===== Get查询单行数据 =====");

        // 创建Get对象,指定要查询的行键
        Get get = new Get(Bytes.toBytes("row_001"));

        // 可选:只获取指定列族的指定列(减少网络传输,提高查询效率)
        // 如果不指定,则获取该行的所有列
        get.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"));
        get.addColumn(Bytes.toBytes("score"), Bytes.toBytes("math"));

        // 可选:设置返回的最大版本数
        get.setMaxVersions(3);

        // 执行查询,返回Result对象
        Result result = table.get(get);

        // 检查查询结果是否为空
        if (result.isEmpty()) {
            System.out.println("未找到数据!");
            return;
        }

        // 遍历Result中的所有单元格(Cell)
        for (Cell cell : result.rawCells()) {
            // CellUtil工具类用于从Cell中提取各部分信息
            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));       // 值
            long timestamp = cell.getTimestamp();                            // 时间戳

            // 格式化输出每个单元格的信息
            System.out.printf("行键:%s | 列族:%s | 列:%s | 值:%s | 时间戳:%d%n",
                    row, family, qualifier, value, timestamp);
        }
    }

    /**
     * 查询指定行键的所有列数据
     */
    public static void getFullRow() throws IOException {
        System.out.println("===== Get查询整行数据 =====");

        Get get = new Get(Bytes.toBytes("row_002"));
        // 不指定列,获取该行所有列族的所有列
        Result result = table.get(get);

        for (Cell cell : result.rawCells()) {
            System.out.printf("%s:%s = %s%n",
                    Bytes.toString(CellUtil.cloneFamily(cell)),
                    Bytes.toString(CellUtil.cloneQualifier(cell)),
                    Bytes.toString(CellUtil.cloneValue(cell)));
        }
    }

    /**
     * Scan扫描查询 ------ 获取多行数据
     */
    public static void scanData() throws IOException {
        System.out.println("===== Scan扫描查询 =====");

        // 创建Scan对象(不指定参数则扫描全表)
        Scan scan = new Scan();

        // 可选:设置扫描的起止行键范围
        // 包含startRow,不包含stopRow(左闭右开区间)
        scan.withStartRow(Bytes.toBytes("row_001"));
        scan.withStopRow(Bytes.toBytes("row_005"));

        // 可选:只扫描指定列族
        scan.addFamily(Bytes.toBytes("info"));

        // 可选:设置缓存的行数(提高扫描性能,默认100)
        scan.setCaching(100);

        // 可选:设置每次RPC返回的最大结果数
        scan.setBatch(10);

        // 执行扫描,返回ResultScanner(结果迭代器)
        ResultScanner scanner = table.getScanner(scan);

        // 遍历每一行结果
        for (Result result : scanner) {
            // 打印当前行的行键
            System.out.println("行键:" + Bytes.toString(result.getRow()));

            // 遍历该行的所有单元格
            for (Cell cell : result.rawCells()) {
                System.out.printf("  %s:%s = %s%n",
                        Bytes.toString(CellUtil.cloneFamily(cell)),       // 列族
                        Bytes.toString(CellUtil.cloneQualifier(cell)),    // 列限定符
                        Bytes.toString(CellUtil.cloneValue(cell)));       // 值
            }
        }

        // 必须关闭ResultScanner,释放服务端资源
        scanner.close();
    }

    /**
     * 带过滤器的Scan查询 ------ 条件过滤
     */
    public static void scanWithFilter() throws IOException {
        System.out.println("===== 带过滤器的Scan查询 =====");

        Scan scan = new Scan();

        // ========== 过滤器1:行键前缀过滤器 ==========
        // 只返回行键以"row_00"开头的行
        // PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("row_00"));

        // ========== 过滤器2:列值过滤器 ==========
        // 只返回info:age的值等于"20"的行
        SingleColumnValueFilter ageFilter = new SingleColumnValueFilter(
                Bytes.toBytes("info"),         // 列族
                Bytes.toBytes("age"),          // 列限定符
                CompareOperator.EQUAL,         // 比较运算符:等于
                Bytes.toBytes("20")            // 比较的值
        );
        // 如果某行没有info:age列,是否过滤掉该行(true=过滤掉)
        ageFilter.setFilterIfMissing(true);

        // ========== 过滤器3:列名过滤器 ==========
        // 只返回info:name和info:age这两列的数据
        ColumnPrefixFilter nameFilter = new ColumnPrefixFilter(Bytes.toBytes("name"));

        // ========== 过滤器4:值比较过滤器 ==========
        // 只返回score:math的值大于等于90的行
        SingleColumnValueFilter mathFilter = new SingleColumnValueFilter(
                Bytes.toBytes("score"),        // 列族
                Bytes.toBytes("math"),         // 列限定符
                CompareOperator.GREATER_OR_EQUAL,  // 比较运算符:大于等于
                Bytes.toBytes("90")            // 比较的值
        );
        mathFilter.setFilterIfMissing(true);

        // ========== 组合过滤器:AND组合 ==========
        // 同时满足 age=20 AND math>=90
        FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
        // MUST_PASS_ALL = AND(所有条件都满足)
        // MUST_PASS_ONE = OR(任一条件满足即可)
        filterList.addFilter(ageFilter);
        filterList.addFilter(mathFilter);

        // 将过滤器列表设置到Scan中
        scan.setFilter(filterList);

        // 执行带过滤器的扫描
        ResultScanner scanner = table.getScanner(scan);
        for (Result result : scanner) {
            System.out.println("匹配行键:" + Bytes.toString(result.getRow()));
            for (Cell cell : result.rawCells()) {
                System.out.printf("  %s:%s = %s%n",
                        Bytes.toString(CellUtil.cloneFamily(cell)),
                        Bytes.toString(CellUtil.cloneQualifier(cell)),
                        Bytes.toString(CellUtil.cloneValue(cell)));
            }
        }
        scanner.close();
    }

    // ==================== 更新数据 ====================

    /**
     * 更新数据(本质上就是重新Put,新值覆盖旧值)
     */
    public static void updateData() throws IOException {
        System.out.println("===== 更新数据 =====");

        // HBase中没有专门的update方法
        // 更新操作就是重新执行Put操作,相同行键+列族+列限定符的新值会覆盖旧值
        Put put = new Put(Bytes.toBytes("row_001"));

        // 将Tom的数学成绩从95更新为98
        put.addColumn(
                Bytes.toBytes("score"),        // 列族
                Bytes.toBytes("math"),         // 列限定符
                Bytes.toBytes("98")            // 新值
        );

        // 将Tom的年龄从20更新为21
        put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("21"));

        // 执行Put操作
        table.put(put);
        System.out.println("row_001 数据更新成功!");

        // 验证更新结果
        getFullRow();
    }

    // ==================== 删除数据 ====================

    /**
     * 删除指定单元格数据(Delete操作)
     */
    public static void deleteData() throws IOException {
        System.out.println("===== 删除数据 =====");

        // ---- 删除单个单元格 ----
        // 创建Delete对象,指定要删除的行键
        Delete delete = new Delete(Bytes.toBytes("row_004"));

        // 删除指定列(精确到列限定符级别)
        // 参数:列族, 列限定符
        delete.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"));
        delete.addColumn(Bytes.toBytes("score"), Bytes.toBytes("chinese"));

        // 执行删除操作
        table.delete(delete);
        System.out.println("row_004 的 info:age 和 score:chinese 已删除");

        // ---- 删除整行 ----
        Delete deleteAll = new Delete(Bytes.toBytes("row_004"));
        // 不指定列,直接删除该行所有数据
        table.delete(deleteAll);
        System.out.println("row_004 整行已删除");

        // ---- 批量删除 ----
        List<Delete> deleteList = new ArrayList<>();
        Delete del1 = new Delete(Bytes.toBytes("row_003"));
        deleteList.add(del1);
        // 批量删除多行
        table.delete(deleteList);
        System.out.println("row_003 已批量删除");
    }

    /**
     * 检查行/列是否存在(使用checkAndPut实现乐观锁)
     */
    public static void checkAndPut() throws IOException {
        System.out.println("===== checkAndPut 乐观锁操作 =====");

        // 场景:只有当 row_001 的 info:name 等于 "Tom" 时,才更新 info:age 为 "22"
        // 这实现了类似CAS(Compare-And-Swap)的原子操作
        Put put = new Put(Bytes.toBytes("row_001"));
        put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("22"));

        // checkAndPut(行键, 列族, 列限定符, 期望值, 要执行的Put操作)
        boolean success = table.checkAndPut(
                Bytes.toBytes("row_001"),       // 要检查的行键
                Bytes.toBytes("info"),           // 检查的列族
                Bytes.toBytes("name"),           // 检查的列限定符
                Bytes.toBytes("Tom"),            // 期望值(只有当前值等于此值才执行put)
                put                              // 条件满足时执行的Put操作
        );

        if (success) {
            System.out.println("条件满足,数据更新成功!");
        } else {
            System.out.println("条件不满足,数据未更新!");
        }
    }

    /**
     * 主方法 ------ 按顺序执行所有CRUD操作
     */
    public static void main(String[] args) {
        try {
            init();               // 初始化连接

            putData();            // 1. 插入单条数据
            putBatchData();       // 2. 批量插入数据
            getData();            // 3. Get查询单行
            getFullRow();         // 4. Get查询整行
            scanData();           // 5. Scan扫描查询
            scanWithFilter();     // 6. 带过滤器的查询
            updateData();         // 7. 更新数据
            checkAndPut();        // 8. 乐观锁操作
            deleteData();         // 9. 删除数据

            close();              // 关闭连接
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

八、HBase 案例实战

8.1 在 IDEA 中创建工程并添加 JAR 包

Maven pom.xml 依赖配置:

xml 复制代码
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>hbase-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 统一管理HBase版本号 -->
        <hbase.version>2.4.17</hbase.version>
        <!-- Hadoop版本需与HBase兼容 -->
        <hadoop.version>3.3.4</hadoop.version>
    </properties>

    <dependencies>
        <!-- HBase客户端依赖(包含HBase Client API) -->
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>${hbase.version}</version>
        </dependency>

        <!-- HBase服务端依赖(包含Admin、RegionServer等API) -->
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>${hbase.version}</version>
        </dependency>

        <!-- Hadoop Common依赖(提供HDFS相关类和Configuration) -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>${hadoop.version}</version>
        </dependency>

        <!-- Hadoop HDFS客户端依赖 -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs-client</artifactId>
            <version>${hadoop.version}</version>
        </dependency>
    </dependencies>
</project>

8.2 完整案例:学生信息管理系统

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * StudentHBaseManager - 学生信息管理系统
 * 功能:
 *   1. 创建学生信息表
 *   2. 批量导入学生数据
 *   3. 查询单个学生信息
 *   4. 查询所有学生信息
 *   5. 按条件过滤查询
 *   6. 更新学生信息
 *   7. 删除学生信息
 *   8. 统计学生数量
 *   9. 删除表
 */
public class StudentHBaseManager {

    // ---- 常量定义 ----
    private static final String TABLE_NAME = "student_info";        // 表名
    private static final String CF_INFO = "info";                   // 基本信息列族
    private static final String CF_SCORE = "score";                 // 成绩信息列族
    private static final String CF_ADDRESS = "address";             // 地址信息列族

    private static Connection connection;   // 连接对象
    private static Admin admin;             // 管理对象

    /**
     * 初始化HBase连接
     */
    public static void init() throws IOException {
        Configuration conf = HBaseConfiguration.create();               // 创建配置
        conf.set("hbase.zookeeper.quorum", "localhost");                // ZK地址
        conf.set("hbase.zookeeper.property.clientPort", "2181");       // ZK端口
        connection = ConnectionFactory.createConnection(conf);          // 建立连接
        admin = connection.getAdmin();                                  // 获取Admin
        System.out.println("=== HBase连接初始化成功 ===");
    }

    /**
     * 关闭连接,释放所有资源
     */
    public static void destroy() throws IOException {
        if (admin != null) admin.close();
        if (connection != null) connection.close();
        System.out.println("=== HBase连接已关闭 ===");
    }

    /**
     * 功能1:创建学生信息表
     * 创建包含三个列族的表:info(基本信息)、score(成绩)、address(地址)
     */
    public static void createStudentTable() throws IOException {
        TableName tableName = TableName.valueOf(TABLE_NAME);

        // 检查表是否已存在
        if (admin.tableExists(tableName)) {
            System.out.println("表 " + TABLE_NAME + " 已存在,跳过创建");
            return;
        }

        // 创建表描述器
        HTableDescriptor tableDesc = new HTableDescriptor(tableName);

        // 创建info列族,设置最大版本数为3
        HColumnDescriptor infoCF = new HColumnDescriptor(CF_INFO);
        infoCF.setMaxVersions(3);               // 最多保存3个版本
        infoCF.setMinVersions(1);               // 最少保留1个版本

        // 创建score列族,设置压缩和布隆过滤器
        HColumnDescriptor scoreCF = new HColumnDescriptor(CF_SCORE);
        scoreCF.setMaxVersions(1);                      // 只保留最新版本
        scoreCF.setBloomFilterType(BloomType.ROW);       // 布隆过滤器(按行键)

        // 创建address列族
        HColumnDescriptor addressCF = new HColumnDescriptor(CF_ADDRESS);
        addressCF.setMaxVersions(1);

        // 将三个列族添加到表描述器
        tableDesc.addFamily(infoCF);
        tableDesc.addFamily(scoreCF);
        tableDesc.addFamily(addressCF);

        // 执行建表
        admin.createTable(tableDesc);
        System.out.println("表 " + TABLE_NAME + " 创建成功!");
    }

    /**
     * 功能2:批量导入学生数据
     * 模拟批量插入10个学生的信息
     */
    public static void batchImportStudents() throws IOException {
        Table table = connection.getTable(TableName.valueOf(TABLE_NAME));

        // 创建学生数据数组(二维数组,每行一个学生)
        // 格式:{学号, 姓名, 年龄, 性别, 语文, 数学, 英语, 省份, 城市}
        String[][] students = {
            {"2021001", "张三", "20", "男", "88", "92", "85", "北京市", "海淀区"},
            {"2021002", "李四", "21", "女", "90", "88", "92", "上海市", "浦东区"},
            {"2021003", "王五", "20", "男", "76", "95", "80", "广东省", "深圳市"},
            {"2021004", "赵六", "22", "女", "92", "78", "90", "浙江省", "杭州市"},
            {"2021005", "孙七", "21", "男", "85", "90", "88", "江苏省", "南京市"},
            {"2021006", "周八", "20", "女", "91", "86", "93", "湖北省", "武汉市"},
            {"2021007", "吴九", "22", "男", "78", "91", "82", "四川省", "成都市"},
            {"2021008", "郑十", "21", "女", "87", "94", "89", "湖南省", "长沙市"},
            {"2021009", "陈一", "20", "男", "93", "82", "91", "福建省", "福州市"},
            {"2021010", "林二", "22", "女", "80", "88", "86", "山东省", "青岛市"}
        };

        // 创建Put列表用于批量插入
        List<Put> puts = new ArrayList<>();

        // 遍历学生数据,构造Put对象
        for (String[] s : students) {
            // 行键设计:使用学号作为RowKey
            Put put = new Put(Bytes.toBytes(s[0]));  // s[0]=学号

            // 写入info列族
            put.addColumn(Bytes.toBytes(CF_INFO), Bytes.toBytes("name"), Bytes.toBytes(s[1]));
            put.addColumn(Bytes.toBytes(CF_INFO), Bytes.toBytes("age"), Bytes.toBytes(s[2]));
            put.addColumn(Bytes.toBytes(CF_INFO), Bytes.toBytes("gender"), Bytes.toBytes(s[3]));

            // 写入score列族
            put.addColumn(Bytes.toBytes(CF_SCORE), Bytes.toBytes("chinese"), Bytes.toBytes(s[4]));
            put.addColumn(Bytes.toBytes(CF_SCORE), Bytes.toBytes("math"), Bytes.toBytes(s[5]));
            put.addColumn(Bytes.toBytes(CF_SCORE), Bytes.toBytes("english"), Bytes.toBytes(s[6]));

            // 写入address列族
            put.addColumn(Bytes.toBytes(CF_ADDRESS), Bytes.toBytes("province"), Bytes.toBytes(s[7]));
            put.addColumn(Bytes.toBytes(CF_ADDRESS), Bytes.toBytes("city"), Bytes.toBytes(s[8]));

            puts.add(put);  // 加入批量列表
        }

        // 执行批量插入
        table.put(puts);
        System.out.println("成功批量导入 " + students.length + " 条学生数据!");

        table.close();
    }

    /**
     * 功能3:根据学号查询单个学生信息
     * @param studentId 学号(RowKey)
     */
    public static void getStudentById(String studentId) throws IOException {
        Table table = connection.getTable(TableName.valueOf(TABLE_NAME));

        // 创建Get对象
        Get get = new Get(Bytes.toBytes(studentId));

        // 执行查询
        Result result = table.get(get);

        if (result.isEmpty()) {
            System.out.println("未找到学号为 " + studentId + " 的学生");
            table.close();
            return;
        }

        // 打印学生信息
        System.out.println("====== 学生信息 ======");
        System.out.println("学号:" + studentId);

        // 遍历所有单元格
        for (Cell cell : result.rawCells()) {
            String family = Bytes.toString(CellUtil.cloneFamily(cell));       // 列族
            String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell)); // 列名
            String value = Bytes.toString(CellUtil.cloneValue(cell));         // 值
            System.out.println("  " + family + ":" + qualifier + " = " + value);
        }

        table.close();
    }

    /**
     * 功能4:查询所有学生信息
     */
    public static void getAllStudents() throws IOException {
        Table table = connection.getTable(TableName.valueOf(TABLE_NAME));

        // 创建全表扫描
        Scan scan = new Scan();
        // 设置缓存行数,提高扫描效率
        scan.setCaching(50);

        ResultScanner scanner = table.getScanner(scan);
        int count = 0;  // 计数器

        System.out.println("====== 所有学生信息 ======");
        System.out.printf("%-12s %-8s %-4s %-4s %-8s %-8s %-8s%n",
                "学号", "姓名", "年龄", "性别", "语文", "数学", "英语");
        System.out.println("---------------------------------------------------------------");

        for (Result result : scanner) {
            // 获取行键(学号)
            String id = Bytes.toString(result.getRow());

            // 获取各列的值,如果某列不存在则显示"-"
            String name = getColumnValue(result, CF_INFO, "name");
            String age = getColumnValue(result, CF_INFO, "age");
            String gender = getColumnValue(result, CF_INFO, "gender");
            String chinese = getColumnValue(result, CF_SCORE, "chinese");
            String math = getColumnValue(result, CF_SCORE, "math");
            String english = getColumnValue(result, CF_SCORE, "english");

            // 格式化输出
            System.out.printf("%-12s %-8s %-4s %-4s %-8s %-8s %-8s%n",
                    id, name, age, gender, chinese, math, english);
            count++;
        }

        System.out.println("---------------------------------------------------------------");
        System.out.println("共 " + count + " 名学生");

        scanner.close();
        table.close();
    }

    /**
     * 功能5:按条件过滤查询
     * 查询数学成绩大于等于90分的学生
     */
    public static void queryByCondition() throws IOException {
        Table table = connection.getTable(TableName.valueOf(TABLE_NAME));

        Scan scan = new Scan();

        // 创建过滤器:数学成绩 >= 90
        SingleColumnValueFilter mathFilter = new SingleColumnValueFilter(
                Bytes.toBytes(CF_SCORE),             // 列族
                Bytes.toBytes("math"),                // 列限定符
                CompareOperator.GREATER_OR_EQUAL,     // 比较操作:>=
                Bytes.toBytes("90")                   // 比较值
        );
        mathFilter.setFilterIfMissing(true);  // 没有math列的行不返回

        // 创建过滤器:性别 = 男
        SingleColumnValueFilter genderFilter = new SingleColumnValueFilter(
                Bytes.toBytes(CF_INFO),
                Bytes.toBytes("gender"),
                CompareOperator.EQUAL,
                Bytes.toBytes("男")
        );
        genderFilter.setFilterIfMissing(true);

        // 组合过滤器:同时满足(AND关系)
        FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL);
        filters.addFilter(mathFilter);
        filters.addFilter(genderFilter);

        scan.setFilter(filters);

        System.out.println("====== 数学>=90且性别=男的学生 ======");
        ResultScanner scanner = table.getScanner(scan);
        for (Result result : scanner) {
            String id = Bytes.toString(result.getRow());
            String name = getColumnValue(result, CF_INFO, "name");
            String math = getColumnValue(result, CF_SCORE, "math");
            System.out.println("  学号:" + id + " | 姓名:" + name + " | 数学:" + math);
        }

        scanner.close();
        table.close();
    }

    /**
     * 功能6:更新学生信息
     * @param studentId 学号
     * @param family    列族
     * @param qualifier 列限定符
     * @param newValue  新值
     */
    public static void updateStudent(String studentId, String family, String qualifier, String newValue) throws IOException {
        Table table = connection.getTable(TableName.valueOf(TABLE_NAME));

        // 先查询原值
        Get get = new Get(Bytes.toBytes(studentId));
        Result result = table.get(get);
        if (result.isEmpty()) {
            System.out.println("学号 " + studentId + " 不存在,无法更新!");
            table.close();
            return;
        }

        // 获取原值
        byte[] oldValue = result.getValue(Bytes.toBytes(family), Bytes.toBytes(qualifier));
        String oldVal = oldValue != null ? Bytes.toString(oldValue) : "无";

        // 执行更新(Put覆盖)
        Put put = new Put(Bytes.toBytes(studentId));
        put.addColumn(Bytes.toBytes(family), Bytes.toBytes(qualifier), Bytes.toBytes(newValue));
        table.put(put);

        System.out.println("更新成功:" + studentId + " 的 " + family + ":" + qualifier
                + " 从 \"" + oldVal + "\" 更新为 \"" + newValue + "\"");

        table.close();
    }

    /**
     * 功能7:删除学生信息
     * @param studentId 学号
     */
    public static void deleteStudent(String studentId) throws IOException {
        Table table = connection.getTable(TableName.valueOf(TABLE_NAME));

        // 先检查是否存在
        Get get = new Get(Bytes.toBytes(studentId));
        Result result = table.get(get);
        if (result.isEmpty()) {
            System.out.println("学号 " + studentId + " 不存在!");
            table.close();
            return;
        }

        // 删除整行
        Delete delete = new Delete(Bytes.toBytes(studentId));
        table.delete(delete);

        System.out.println("学号 " + studentId + " 的信息已删除!");
        table.close();
    }

    /**
     * 功能8:统计学生总数(使用ResultScanner遍历计数)
     */
    public static long countStudents() throws IOException {
        Table table = connection.getTable(TableName.valueOf(TABLE_NAME));

        Scan scan = new Scan();
        // 只扫描info列族的name列(减少数据传输量,只需要确认行存在即可)
        scan.addColumn(Bytes.toBytes(CF_INFO), Bytes.toBytes("name"));
        scan.setCaching(500);  // 设置较大的缓存行数

        ResultScanner scanner = table.getScanner(scan);
        long count = 0;

        // 遍历每一行,逐行计数
        for (Result result : scanner) {
            count++;  // 每有一个Result就代表一行数据
        }

        scanner.close();
        table.close();

        System.out.println("学生总数:" + count);
        return count;
    }

    /**
     * 功能9:删除学生信息表
     */
    public static void dropStudentTable() throws IOException {
        TableName tableName = TableName.valueOf(TABLE_NAME);

        if (!admin.tableExists(tableName)) {
            System.out.println("表 " + TABLE_NAME + " 不存在!");
            return;
        }

        // 先禁用再删除
        admin.disableTable(tableName);
        admin.deleteTable(tableName);
        System.out.println("表 " + TABLE_NAME + " 已成功删除!");
    }

    /**
     * 工具方法:从Result中获取指定列族和列限定符的值
     * @param result    查询结果
     * @param family    列族名
     * @param qualifier 列限定符名
     * @return 列值的字符串形式,如果不存在返回"-"
     */
    private static String getColumnValue(Result result, String family, String qualifier) {
        // 从Result中获取指定列的字节数组值
        byte[] value = result.getValue(Bytes.toBytes(family), Bytes.toBytes(qualifier));
        // 如果值不为null,转换为字符串返回;否则返回"-"
        return value != null ? Bytes.toString(value) : "-";
    }

    /**
     * 主方法 ------ 完整流程演示
     */
    public static void main(String[] args) {
        try {
            // 初始化连接
            init();

            // 1. 创建表
            createStudentTable();

            // 2. 批量导入数据
            batchImportStudents();

            // 3. 查询单个学生
            getStudentById("2021001");

            // 4. 查询所有学生
            getAllStudents();

            // 5. 按条件查询
            queryByCondition();

            // 6. 更新学生信息
            updateStudent("2021001", CF_SCORE, "math", "98");

            // 7. 验证更新
            getStudentById("2021001");

            // 8. 删除学生
            deleteStudent("2021010");

            // 9. 统计学生总数
            countStudents();

            // 10. 查看所有学生(确认删除后)
            getAllStudents();

            // 11. 删除表(可选,注释掉以保留数据)
            // dropStudentTable();

            // 关闭连接
            destroy();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

九、利用 Python 操作 HBase

9.1 HappyBase 的安装

bash 复制代码
# 使用pip安装HappyBase(Python的HBase客户端库)
# HappyBase基于Thrift协议与HBase通信
pip install happybase

# 如果使用Python3,可能需要指定pip3
pip3 install happybase

# 验证安装
python3 -c "import happybase; print(happybase.__version__)"

前置条件:启动HBase Thrift Server

bash 复制代码
# HappyBase通过Thrift协议连接HBase,因此需要先启动Thrift服务
# 在HBase的Master节点上执行:
hbase-daemon.sh start thrift

# 验证Thrift Server是否启动(默认监听9090端口)
netstat -tlnp | grep 9090

9.2 Connection 类

python 复制代码
"""
Connection类 - 演示HappyBase的连接管理和数据库/表管理操作
Connection是HappyBase的入口类,用于连接HBase集群并管理表
"""
import happybase

# ==================== 创建连接 ====================
# 创建与HBase Thrift Server的连接
# host: HBase Thrift Server的主机地址
# port: Thrift Server的端口号(默认9090)
# autoconnect: 是否自动建立连接(True=立即连接,False=延迟连接)
# protocol: 协议版本,通常使用'binary'
connection = happybase.Connection(
    host='localhost',           # HBase所在主机的IP或主机名
    port=9090,                  # Thrift Server的端口号
    autoconnect=True,           # 设置为True,创建时自动连接
    protocol='binary'           # 使用binary协议(推荐)
)
print("成功连接到HBase集群!")

# ==================== 查看集群信息 ====================
# 获取HBase集群的版本信息
# 返回字节字符串,需要解码
print("HBase集群版本:", connection.client.getVersion())

# ==================== 管理表 ====================

# ---- 查看所有表 ----
# tables() 方法返回所有表名的列表(表名为bytes类型)
tables = connection.tables()
print("当前所有表:", [t.decode('utf-8') for t in tables])

# ---- 创建表 ----
# create_table() 方法创建新表
# 参数1:表名(字符串类型)
# 参数2:字典,键=列族名,值=列族属性字典
# 创建students表,包含info和score两个列族
connection.create_table(
    'students',                          # 表名
    {                                    # 列族配置字典
        'info': {                        # info列族
            'max_versions': 5,           # 最大版本数为5
            'bloom_filter_type': 'ROW',  # 布隆过滤器类型
            'compression': 'snappy'      # 压缩方式
        },
        'score': {                       # score列族
            'max_versions': 3,           # 最大版本数为3
            'compression': 'snappy'      # 压缩方式
        }
    }
)
print("表 students 创建成功!")

# ---- 查看表是否存在 ----
# 通过尝试获取表对象来判断表是否存在
try:
    table = connection.table('students')
    print("表 students 存在")
except Exception as e:
    print("表 students 不存在")

# ---- 获取表对象 ----
# table() 方法获取指定表的操作句柄
students_table = connection.table('students')
print("获取表对象:", students_table.name.decode('utf-8'))

# ---- 禁用并删除表 ----
# 注意:HappyBase的create_table在表已存在时会抛出异常
# 删除表需要先通过HBase Shell或Admin API禁用表
# connection.delete_table('表名', disable=True)  # disable=True会自动禁用表

# ==================== 关闭连接 ====================
# 使用完毕后必须关闭连接
connection.close()
print("连接已关闭!")

9.3 Table 类

python 复制代码
"""
Table类 - 演示HappyBase的Table类操作
Table类提供了对HBase表的CRUD操作
"""
import happybase

# 建立连接
connection = happybase.Connection(host='localhost', port=9090, autoconnect=True)

# 获取表对象
table = connection.table('students')

# ==================== 插入数据 ====================
# ---- 插入单条数据 ----
# put() 方法插入或更新一条数据
# 参数:行键(bytes), {列族:列限定符: 值}
table.put(
    b'row_001',                    # 行键(必须为bytes类型)
    {                              # 数据字典
        b'info:name': b'Tom',      # 列族:列限定符 = 值
        b'info:age': b'20',        # info列族的age列
        b'info:gender': b'Male',   # info列族的gender列
        b'score:chinese': b'89',   # score列族的chinese列
        b'score:math': b'95',      # score列族的math列
        b'score:english': b'88'    # score列族的english列
    }
)
print("row_001 插入成功!")

# ---- 批量插入数据 ----
# 使用batch()上下文管理器进行批量写入,效率更高
# batch()会在with块结束时自动提交所有写入操作
with table.batch() as batch:
    # 批量插入row_002
    batch.put(b'row_002', {
        b'info:name': b'Jerry',
        b'info:age': b'21',
        b'info:gender': b'Male',
        b'score:chinese': b'76',
        b'score:math': b'82',
        b'score:english': b'92'
    })
    # 批量插入row_003
    batch.put(b'row_003', {
        b'info:name': b'Alice',
        b'info:age': b'20',
        b'info:gender': b'Female',
        b'score:chinese': b'90',
        b'score:math': b'85',
        b'score:english': b'91'
    })
    # 批量插入row_004
    batch.put(b'row_004', {
        b'info:name': b'Bob',
        b'info:age': b'22',
        b'info:gender': b'Male',
        b'score:chinese': b'88',
        b'score:math': b'90',
        b'score:english': b'87'
    })
print("批量插入3条数据成功!")

# ==================== 查询数据 ====================

# ---- 查询单行数据 ----
# row() 方法根据行键获取一行数据
# 返回字典,键为列族:列限定符(bytes),值为单元格值(bytes)
row = table.row(b'row_001')        # 获取row_001的所有列
print("\nrow_001 的所有数据:")
for key, value in row.items():
    # 将bytes类型的键和值解码为字符串
    print(f"  {key.decode('utf-8')} = {value.decode('utf-8')}")

# ---- 查询单行指定列 ----
# columns参数指定要查询的列,减少网络传输
row_filtered = table.row(
    b'row_001',                     # 行键
    columns=[b'info:name', b'score:math']  # 指定查询的列
)
print("\nrow_001 的指定列:")
for key, value in row_filtered.items():
    print(f"  {key.decode('utf-8')} = {value.decode('utf-8')}")

# ---- 查询多行数据(指定多个行键)----
# rows() 方法根据多个行键批量查询
# 参数:行键列表
# 返回:列表,每个元素为(行键字典)的元组
rows = table.rows([b'row_001', b'row_002', b'row_003'])
print("\n多行查询结果:")
for row_key, row_data in rows:
    name = row_data.get(b'info:name', b'N/A').decode('utf-8')
    print(f"  行键:{row_key.decode('utf-8')} | 姓名:{name}")

# ---- 扫描全表 ----
# scan() 方法扫描表中数据
# 返回一个生成器(迭代器),逐行返回数据
print("\n扫描全表所有数据:")
for key, data in table.scan():
    name = data.get(b'info:name', b'N/A').decode('utf-8')
    age = data.get(b'info:age', b'N/A').decode('utf-8')
    print(f"  行键:{key.decode('utf-8')} | 姓名:{name} | 年龄:{age}")

# ---- 扫描指定范围 ----
# row_start和row_end参数设置扫描的起止行键(左闭右开)
print("\n扫描 row_001 到 row_003(不含row_003):")
for key, data in table.scan(row_start=b'row_001', row_stop=b'row_003'):
    name = data.get(b'info:name', b'N/A').decode('utf-8')
    print(f"  行键:{key.decode('utf-8')} | 姓名:{name}")

# ---- 扫描指定列 ----
# columns参数限制扫描返回的列
print("\n扫描所有行的info:name和score:math:")
for key, data in table.scan(columns=[b'info:name', b'score:math']):
    name = data.get(b'info:name', b'N/A').decode('utf-8')
    math = data.get(b'score:math', b'N/A').decode('utf-8')
    print(f"  行键:{key.decode('utf-8')} | 姓名:{name} | 数学:{math}")

# ---- 限制扫描行数 ----
# limit参数限制返回的最大行数
print("\n扫描前2行数据:")
for key, data in table.scan(limit=2):
    name = data.get(b'info:name', b'N/A').decode('utf-8')
    print(f"  行键:{key.decode('utf-8')} | 姓名:{name}")

# ==================== 更新数据 ====================
# HBase中更新和插入操作相同,使用put()覆盖旧值
# 将row_001的数学成绩从95更新为98
table.put(b'row_001', {b'score:math': b'98'})
print("\nrow_001 的 math 更新为 98")

# 验证更新结果
row = table.row(b'row_001', columns=[b'info:name', b'score:math'])
print(f"更新后验证:{row[b'info:name'].decode('utf-8')} 数学={row[b'score:math'].decode('utf-8')}")

# ==================== 删除数据 ====================

# ---- 删除指定列 ----
# delete() 方法删除数据
# 参数:行键, 要删除的列列表
table.delete(b'row_004', columns=[b'info:age', b'score:chinese'])
print("\nrow_004 的 info:age 和 score:chinese 已删除")

# ---- 删除整行 ----
# 不指定columns参数则删除整行
table.delete(b'row_004')
print("row_004 整行已删除")

# ---- 批量删除 ----
with table.batch() as batch:
    # 使用batch的delete方法批量删除
    batch.delete(b'row_003', columns=[b'score:english'])
print("批量删除完成")

# ==================== 获取行数(近似值)====================
# regions() 方法返回表的Region信息
# 注意:HappyBase没有直接的count方法
# 实际计数需要扫描全表
count = 0
for _ in table.scan():
    count += 1
print(f"\n表中共有 {count} 行数据")

# ==================== 关闭连接 ====================
connection.close()
print("操作完成,连接已关闭!")

十、HBase MapReduce 数据转移案例

10.1 HBase 不同表间数据转移

student_info 表中的数据经过 MapReduce 处理后写入 student_backup 表。

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.mapreduce.TableReducer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;

import java.io.IOException;

/**
 * HBaseTableTransfer - HBase表间数据转移
 * 功能:从student_info表读取数据,经过Map处理后,写入student_backup表
 *
 * 工作流程:
 *   Mapper:  从源表读取数据,提取行键和列值
 *   Reducer: 将处理后的数据写入目标表
 */
public class HBaseTableTransfer {

    /**
     * ========== Mapper类 ==========
     * TableMapper<KEYOUT, VALUEOUT> 是HBase提供的专用Mapper
     * 输入:ImmutableBytesWritable(行键), Result(一行数据)
     */
    public static class StudentMapper extends TableMapper<Text, Text> {

        /**
         * map方法:每读取一行数据调用一次
         * @param key     行键(ImmutableBytesWritable类型)
         * @param value   该行的所有数据(Result类型)
         * @param context 上下文对象,用于输出中间结果
         */
        @Override
        protected void map(ImmutableBytesWritable key, Result value, Context context)
                throws IOException, InterruptedException {

            // 1. 提取行键并转为字符串
            String rowKey = Bytes.toString(key.get());

            // 2. 遍历该行的所有单元格(Cell)
            for (Cell cell : value.rawCells()) {
                // 3. 提取列族名
                String family = Bytes.toString(CellUtil.cloneFamily(cell));
                // 4. 提取列限定符名
                String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell));
                // 5. 提取值
                String cellValue = Bytes.toString(CellUtil.cloneValue(cell));

                // 6. 将列族、列限定符、值拼接为一个字符串,用"|"分隔
                String outputValue = family + "|" + qualifier + "|" + cellValue;

                // 7. 输出中间结果:key=行键, value="列族|列限定符|值"
                // Text是Hadoop的字符串包装类
                context.write(new Text(rowKey), new Text(outputValue));
            }
        }
    }

    /**
     * ========== Reducer类 ==========
     * TableReducer<KEYIN, VALUEIN, KEYOUT> 是HBase提供的专用Reducer
     * 将Mapper的输出写入HBase目标表
     */
    public static class StudentReducer extends TableReducer<Text, Text, NullWritable> {

        /**
         * reduce方法:每个行键调用一次
         * @param key     行键(来自Mapper的输出键)
         * @param values  该行键对应的所有列值(来自Mapper的输出值)
         * @param context 上下文对象,用于写入目标HBase表
         */
        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {

            // 1. 创建Put对象,行键来自Mapper的key
            Put put = new Put(Bytes.toBytes(key.toString()));

            // 2. 遍历该行键下的所有列值
            for (Text val : values) {
                // 3. 将value按"|"分割,还原为列族、列限定符、值
                String[] parts = val.toString().split("\\|");

                // 4. 检查数据格式是否正确(应为3部分)
                if (parts.length == 3) {
                    String family = parts[0];      // 列族
                    String qualifier = parts[1];   // 列限定符
                    String cellValue = parts[2];   // 值

                    // 5. 向Put对象中添加列数据
                    put.addColumn(
                        Bytes.toBytes(family),       // 列族
                        Bytes.toBytes(qualifier),    // 列限定符
                        Bytes.toBytes(cellValue)     // 值
                    );
                }
            }

            // 6. 将Put对象写入目标表
            // NullWritable.get() 表示输出键为空(因为TableReducer会自动写入HBase)
            context.write(NullWritable.get(), put);
        }
    }

    /**
     * 主方法:配置并运行MapReduce作业
     */
    public static void main(String[] args) throws Exception {
        // 1. 创建HBase配置
        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "localhost");
        conf.set("hbase.zookeeper.property.clientPort", "2181");

        // 2. 创建MapReduce作业,作业名为"hbase-table-transfer"
        Job job = Job.getInstance(conf, "hbase-table-transfer");

        // 3. 设置作业的主类(包含main方法的类)
        job.setJarByClass(HBaseTableTransfer.class);

        // 4. 配置Mapper阶段
        // TableMapReduceUtil.initTableMapperJob() 是HBase提供的工具方法
        // 参数1:源表名(从哪里读数据)
        // 参数2:Scan对象(null表示扫描全表)
        // 参数3:Mapper类
        // 参数4:Mapper输出的Key类型
        // 参数5:Mapper输出的Value类型
        // 参数6:Job对象
        TableMapReduceUtil.initTableMapperJob(
            "student_info",                   // 源表:student_info
            new Scan(),                       // 扫描全表
            StudentMapper.class,              // 使用自定义的Mapper
            Text.class,                       // 输出Key类型:Text(行键)
            Text.class,                       // 输出Value类型:Text(列数据)
            job                               // 作业对象
        );

        // 5. 配置Reducer阶段
        // TableMapReduceUtil.initTableReducerJob() 配置将结果写入目标表
        // 参数1:目标表名(写入哪里)
        // 参数2:Reducer类
        // 参数3:Job对象
        TableMapReduceUtil.initTableReducerJob(
            "student_backup",                 // 目标表:student_backup
            StudentReducer.class,             // 使用自定义的Reducer
            job                               // 作业对象
        );

        // 6. 提交作业并等待完成
        // waitForCompletion(true) 表示等待作业完成,并输出进度信息
        boolean success = job.waitForCompletion(true);

        // 7. 根据作业结果退出
        // 成功返回0,失败返回1
        System.exit(success ? 0 : 1);
    }
}

10.2 HDFS 数据转移至 HBase

将 HDFS 上的 CSV 文件数据导入到 HBase 表中。

HDFS 上的 CSV 数据示例(student.csv):

复制代码
2021001,张三,20,男,88,92,85
2021002,李四,21,女,90,88,92
2021003,王五,20,男,76,95,80
java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.mapreduce.TableReducer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import java.io.IOException;

/**
 * HdfsToHBase - 将HDFS上的CSV文件数据导入HBase表
 *
 * 工作流程:
 *   Mapper:  读取HDFS上的CSV文件,按逗号分割,提取各字段
 *   Reducer: 将解析后的数据组装成HBase的Put对象,写入目标表
 */
public class HdfsToHBase {

    /**
     * ========== Mapper类 ==========
     * 读取HDFS文件,每行数据按逗号分割
     * 输入:<行偏移量(LongWritable), 每行文本(Text)>
     * 输出:<行键(Text), 各字段值(Text)>
     */
    public static class CsvMapper extends Mapper<LongWritable, Text, Text, Text> {

        /**
         * map方法:处理CSV文件的每一行
         * @param key     行偏移量(本案例中不使用)
         * @param value   CSV文件的一行文本,如 "2021001,张三,20,男,88,92,85"
         * @param context 上下文
         */
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {

            // 1. 读取一行CSV数据
            String line = value.toString().trim();

            // 2. 跳过空行
            if (line.isEmpty()) {
                return;
            }

            // 3. 按逗号分割各字段
            // CSV格式:学号,姓名,年龄,性别,语文,数学,英语
            String[] fields = line.split(",");

            // 4. 检查字段数量是否正确(应为7个字段)
            if (fields.length != 7) {
                // 字段数不正确,跳过该行(或记录日志)
                return;
            }

            // 5. 提取各字段
            String studentId = fields[0].trim();    // 学号(用作RowKey)
            String name = fields[1].trim();          // 姓名
            String age = fields[2].trim();           // 年龄
            String gender = fields[3].trim();        // 性别
            String chinese = fields[4].trim();       // 语文成绩
            String math = fields[5].trim();          // 数学成绩
            String english = fields[6].trim();       // 英语成绩

            // 6. 构造输出值:将所有字段拼接为一个字符串,用"|"分隔
            String outputValue = name + "|" + age + "|" + gender + "|"
                    + chinese + "|" + math + "|" + english;

            // 7. 输出:key=学号(RowKey), value=拼接后的字段字符串
            context.write(new Text(studentId), new Text(outputValue));
        }
    }

    /**
     * ========== Reducer类 ==========
     * 接收Mapper的输出,构造Put对象写入HBase目标表
     */
    public static class HBaseReducer extends TableReducer<Text, Text, NullWritable> {

        /**
         * reduce方法:每个行键(学号)调用一次
         */
        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {

            // 1. 创建Put对象,使用学号作为行键
            Put put = new Put(Bytes.toBytes(key.toString()));

            // 2. 遍历values(正常情况下每个学号只有一个value)
            for (Text val : values) {
                // 3. 按"|"分割还原字段
                String[] fields = val.toString().split("\\|");

                // 4. 检查字段数量
                if (fields.length == 6) {
                    // 5. 将各字段写入对应的列
                    // 写入info列族
                    put.addColumn(
                        Bytes.toBytes("info"),           // 列族
                        Bytes.toBytes("name"),           // 列限定符
                        Bytes.toBytes(fields[0])         // 姓名
                    );
                    put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes(fields[1]));
                    put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("gender"), Bytes.toBytes(fields[2]));

                    // 写入score列族
                    put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("chinese"), Bytes.toBytes(fields[3]));
                    put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("math"), Bytes.toBytes(fields[4]));
                    put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("english"), Bytes.toBytes(fields[5]));
                }
            }

            // 6. 将Put写入HBase目标表
            context.write(NullWritable.get(), put);
        }
    }

    /**
     * 主方法:配置并运行MapReduce作业
     */
    public static void main(String[] args) throws Exception {
        // 1. 创建HBase配置
        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "localhost");
        conf.set("hbase.zookeeper.property.clientPort", "2181");

        // 2. 创建Job实例
        Job job = Job.getInstance(conf, "hdfs-to-hbase");
        job.setJarByClass(HdfsToHBase.class);

        // 3. 设置Mapper
        job.setMapperClass(CsvMapper.class);       // 使用自定义的CSV解析Mapper
        job.setMapOutputKeyClass(Text.class);       // Mapper输出Key类型
        job.setMapOutputValueClass(Text.class);     // Mapper输出Value类型

        // 4. 设置输入路径(HDFS上的CSV文件路径)
        // 第一个命令行参数为输入路径
        FileInputFormat.addInputPath(job, new Path(args[0]));
        // 示例:hdfs://localhost:9000/input/student.csv

        // 5. 配置Reducer并设置目标表
        // 使用TableMapReduceUtil.initTableReducerJob()将Reducer输出写入HBase
        TableMapReduceUtil.initTableReducerJob(
            "student_from_hdfs",              // 目标HBase表名
            HBaseReducer.class,               // 使用自定义的Reducer
            job                               // 作业对象
        );

        // 6. 提交作业
        boolean success = job.waitForCompletion(true);
        System.exit(success ? 0 : 1);
    }
}

十一、HBase 数据备份与恢复

11.1 使用 Export/Import 工具进行备份和恢复

备份(Export):

bash 复制代码
# 使用HBase自带的Export工具将表数据导出到HDFS
# Export工具本质上是一个MapReduce作业

# 导出students表到HDFS的/hbase-backup/students目录
hbase org.apache.hadoop.hbase.mapreduce.Export \
    students \
    hdfs://localhost:9000/hbase-backup/students

# 导出时指定版本数(导出最近3个版本的数据)
hbase org.apache.hadoop.hbase.mapreduce.Export \
    students \
    hdfs://localhost:9000/hbase-backup/students \
    3

# 导出时指定起止时间戳(只导出指定时间范围内的数据)
# 时间戳需要查看数据中的实际时间戳值
hbase org.apache.hadoop.hbase.mapreduce.Export \
    students \
    hdfs://localhost:9000/hbase-backup/students \
    1 \
    1634567890000 \    # 起始时间戳
    1634667890000       # 结束时间戳

恢复(Import):

bash 复制代码
# 使用HBase自带的Import工具从HDFS导入数据到表
# 注意:目标表必须事先存在,且表结构(列族)需与导出时一致

# 先创建目标表
hbase shell << EOF
create 'students_restored', 'info', 'score'
exit
EOF

# 从HDFS导入数据到students_restored表
hbase org.apache.hadoop.hbase.mapreduce.Import \
    students_restored \
    hdfs://localhost:9000/hbase-backup/students

11.2 使用 Snapshot 进行备份和恢复(推荐方式)

bash 复制代码
# ========== 创建快照 ==========
# 快照是表的元数据指针,不会复制实际数据,创建速度快,不占用额外存储空间

# 在HBase Shell中创建快照
hbase shell << 'EOF'
# 语法:snapshot '表名', '快照名'
snapshot 'students', 'students_snapshot_20240101'

# 查看所有快照
list_snapshots

# 输出示例:
# students_snapshot_20240101   students   ...
EOF

# ========== 从快照恢复 ==========
# 恢复操作会将表恢复到快照创建时的状态
hbase shell << 'EOF'
# 第一步:禁用表(恢复前必须禁用)
disable 'students'

# 第二步:从快照恢复
# 语法:restore_snapshot '快照名'
restore_snapshot 'students_snapshot_20240101'

# 第三步:重新启用表
enable 'students'

# 验证恢复结果
scan 'students', {LIMIT => 3}
EOF

# ========== 从快照克隆新表 ==========
# 克隆操作创建一个新表,数据与快照时完全一致
hbase shell << 'EOF'
# 语法:clone_snapshot '快照名', '新表名'
clone_snapshot 'students_snapshot_20240101', 'students_clone'

# 验证克隆表
scan 'students_clone', {LIMIT => 3}
EOF

# ========== 删除快照 ==========
hbase shell << 'EOF'
# 语法:delete_snapshot '快照名'
delete_snapshot 'students_snapshot_20240101'

# 删除所有快照(慎用!)
# delete_all_snapshot 'students'
EOF

11.3 编程实现数据备份与恢复

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FSDataOutputStream;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * HBaseBackupRestore - 编程实现HBase数据备份与恢复
 *
 * 方法1:基于Scan+Put的全表复制(适用于小表)
 * 方法2:调用Snapshot API(适用于大表)
 */
public class HBaseBackupRestore {

    private static Connection connection;
    private static Admin admin;

    /**
     * 初始化连接
     */
    public static void init() throws IOException {
        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "localhost");
        conf.set("hbase.zookeeper.property.clientPort", "2181");
        connection = ConnectionFactory.createConnection(conf);
        admin = connection.getAdmin();
    }

    /**
     * 关闭连接
     */
    public static void close() throws IOException {
        if (admin != null) admin.close();
        if (connection != null) connection.close();
    }

    /**
     * 方法1:基于Scan和Put的表数据复制
     * 将源表的所有数据复制到目标表
     *
     * @param srcTableName  源表名
     * @param destTableName 目标表名
     * @param batchSize     每批次写入的行数(控制内存使用)
     */
    public static void copyTable(String srcTableName, String destTableName, int batchSize) throws IOException {
        System.out.println("===== 开始表数据复制 =====");
        System.out.println("源表:" + srcTableName + " → 目标表:" + destTableName);

        // 第一步:获取源表和目标表的Table对象
        Table srcTable = connection.getTable(TableName.valueOf(srcTableName));
        Table destTable = connection.getTable(TableName.valueOf(destTableName));

        // 第二步:创建全表扫描
        Scan scan = new Scan();
        scan.setCaching(batchSize);          // 设置每次RPC缓存的行数
        scan.setBatch(100);                  // 设置每次返回的最大列数

        // 第三步:执行扫描
        ResultScanner scanner = srcTable.getScanner(scan);
        List<Put> puts = new ArrayList<>();  // 批量写入列表
        long totalRows = 0;                  // 总行数计数器
        long totalCells = 0;                 // 总单元格计数器

        // 第四步:遍历源表的每一行
        for (Result result : scanner) {
            // 获取当前行的行键
            byte[] rowKey = result.getRow();

            // 创建Put对象(目标表的行键与源表相同)
            Put put = new Put(rowKey);

            // 遍历该行的所有单元格
            for (Cell cell : result.rawCells()) {
                // 将源表的单元格直接复制到目标表的Put对象中
                // CellUtil.cloneFamily/Qualifier/Value 提取Cell的各部分
                put.addColumn(
                    CellUtil.cloneFamily(cell),       // 列族(与源相同)
                    CellUtil.cloneQualifier(cell),    // 列限定符(与源相同)
                    cell.getTimestamp(),              // 时间戳(保留原始时间戳)
                    CellUtil.cloneValue(cell)         // 值(与源相同)
                );
                totalCells++;  // 单元格计数+1
            }

            puts.add(put);  // 将Put加入批量列表
            totalRows++;     // 行计数+1

            // 当积累了足够的Put操作时,执行批量写入
            if (puts.size() >= batchSize) {
                destTable.put(puts);       // 批量写入目标表
                System.out.println("  已复制 " + totalRows + " 行...");
                puts.clear();              // 清空列表,释放内存
            }
        }

        // 第五步:写入剩余的Put(最后一不满batchSize的部分)
        if (!puts.isEmpty()) {
            destTable.put(puts);
        }

        // 第六步:关闭资源
        scanner.close();
        srcTable.close();
        destTable.close();

        System.out.println("数据复制完成!");
        System.out.println("总计复制:" + totalRows + " 行," + totalCells + " 个单元格");
    }

    /**
     * 方法2:基于Snapshot的备份
     * 创建表的快照(不复制数据,仅记录元数据)
     *
     * @param tableName    表名
     * @param snapshotName 快照名称
     */
    public static void createSnapshot(String tableName, String snapshotName) throws IOException {
        TableName table = TableName.valueOf(tableName);

        // 检查表是否存在
        if (!admin.tableExists(table)) {
            System.out.println("表 " + tableName + " 不存在!");
            return;
        }

        // 检查快照是否已存在
        if (admin.listSnapshots(".*" + snapshotName + ".*").size() > 0) {
            System.out.println("快照 " + snapshotName + " 已存在!");
            return;
        }

        // 创建快照
        // snapshot操作非常快,因为不复制数据
        admin.snapshot(snapshotName, table);
        System.out.println("快照 " + snapshotName + " 创建成功!");
    }

    /**
     * 方法2续:从快照恢复表
     *
     * @param snapshotName 快照名称
     * @param tableName    要恢复到的表名(可以是原表名或新表名)
     */
    public static void restoreFromSnapshot(String snapshotName, String tableName) throws IOException {
        TableName table = TableName.valueOf(tableName);

        // 如果目标表已存在,需要先禁用
        if (admin.tableExists(table)) {
            if (admin.isTableEnabled(table)) {
                admin.disableTable(table);  // 禁用表
                System.out.println("表 " + tableName + " 已禁用");
            }
        }

        // 从快照恢复到指定表
        admin.restoreSnapshot(snapshotName);
        System.out.println("从快照 " + snapshotName + " 恢复成功!");

        // 如果表被禁用了,重新启用
        if (admin.tableExists(table) && admin.isTableDisabled(table)) {
            admin.enableTable(table);
            System.out.println("表 " + tableName + " 已重新启用");
        }
    }

    /**
     * 方法2续:从快照克隆新表
     *
     * @param snapshotName 快照名称
     * @param newTableName 新表名称
     */
    public static void cloneFromSnapshot(String snapshotName, String newTableName) throws IOException {
        TableName newTable = TableName.valueOf(newTableName);

        // 检查新表是否已存在
        if (admin.tableExists(newTable)) {
            System.out.println("表 " + newTableName + " 已存在,无法克隆!");
            return;
        }

        // 从快照克隆一个新表
        // 克隆操作不复制数据,只是创建新表引用相同的数据文件
        admin.cloneSnapshot(snapshotName, newTable);
        System.out.println("从快照 " + snapshotName + " 克隆新表 " + newTableName + " 成功!");
    }

    /**
     * 方法3:将表数据导出到HDFS文件(简化版,作为参考)
     * 实际生产中建议使用HBase自带的Export工具
     *
     * @param tableName 表名
     * @param hdfsPath  HDFS目标路径
     */
    public static void exportToHdfs(String tableName, String hdfsPath) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        Scan scan = new Scan();

        // 获取HDFS文件系统
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://localhost:9000");
        FileSystem fs = FileSystem.get(conf);

        // 创建HDFS输出文件
        FSDataOutputStream out = fs.create(new Path(hdfsPath));
        ResultScanner scanner = table.getScanner(scan);
        long count = 0;

        for (Result result : scanner) {
            String rowKey = Bytes.toString(result.getRow());

            for (Cell cell : result.rawCells()) {
                // 格式:行键\t列族:列限定符\t时间戳\t值
                String line = rowKey + "\t"
                        + Bytes.toString(CellUtil.cloneFamily(cell))
                        + ":" + Bytes.toString(CellUtil.cloneQualifier(cell))
                        + "\t" + cell.getTimestamp()
                        + "\t" + Bytes.toString(CellUtil.cloneValue(cell))
                        + "\n";

                out.writeBytes(line);  // 写入HDFS文件
                count++;
            }
        }

        // 关闭资源
        out.close();
        scanner.close();
        table.close();
        fs.close();

        System.out.println("导出完成!共导出 " + count + " 条记录到 " + hdfsPath);
    }

    /**
     * 主方法:演示备份与恢复的完整流程
     */
    public static void main(String[] args) {
        try {
            init();

            // ===== 方式1:表间数据复制 =====
            // 将students表的数据完整复制到students_backup表
            // 前提:students_backup表已创建且列族与students表一致
            copyTable("students", "students_backup", 100);

            // ===== 方式2:Snapshot快照备份与恢复 =====
            // 创建快照
            createSnapshot("students", "students_snap_20240101");

            // 查看所有快照
            System.out.println("所有快照:");
            admin.listSnapshots().forEach(snap ->
                System.out.println("  " + snap.getName() + " | 表:" + snap.getTable())
            );

            // 从快照克隆新表
            cloneFromSnapshot("students_snap_20240101", "students_clone");

            // 从快照恢复原表(恢复到快照时的状态)
            restoreFromSnapshot("students_snap_20240101", "students");

            // ===== 方式3:导出到HDFS =====
            exportToHdfs("students", "hdfs://localhost:9000/hbase-backup/students_export.txt");

            close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

知识点总结速查表

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                    HBase 核心知识点总结                               │
├────────────────────────┬────────────────────────────────────────────┤
│ 数据模型五要素           │ RowKey + Column Family + Qualifier         │
│                        │ + Timestamp + Value                        │
├────────────────────────┼────────────────────────────────────────────┤
│ Shell基本操作            │ create / put / get / scan / delete         │
│                        │ deleteall / disable / drop / alter          │
├────────────────────────┼────────────────────────────────────────────┤
│ Java Admin API          │ createTable / deleteTable / listTables     │
│ (DDL操作)               │ disableTable / enableTable                 │
├────────────────────────┼────────────────────────────────────────────┤
│ Java Table API          │ put / get / scan / delete                   │
│ (DML操作)               │ checkAndPut / checkAndDelete               │
├────────────────────────┼────────────────────────────────────────────┤
│ Python操作              │ happybase.Connection / .table()             │
│                        │ table.put() / .row() / .scan() / .delete()  │
├────────────────────────┼────────────────────────────────────────────┤
│ MapReduce集成           │ TableMapper / TableReducer                  │
│                        │ TableMapReduceUtil.initTableMapperJob()     │
├────────────────────────┼────────────────────────────────────────────┤
│ 数据备份方式             │ 1. Export/Import(MapReduce方式)            │
│                        │ 2. Snapshot快照(推荐,速度快)               │
│                        │ 3. CopyTable(Scan+Put方式)                │
├────────────────────────┼────────────────────────────────────────────┤
│ RowKey设计              │ 唯一性 + 散列性 + 长度合理 + 业务相关        │
├────────────────────────┼────────────────────────────────────────────┤
│ 过滤器                  │ SingleColumnValueFilter / PrefixFilter      │
│                        │ ColumnPrefixFilter / FilterList             │
└────────────────────────┴────────────────────────────────────────────┘
相关推荐
吴声子夜歌1 小时前
SQL经典实例——处理数字
java·数据库·sql
NineData1 小时前
日常巡检 Oracle 时,ChatDBA 怎么把会话、SQL 和等待事件一起看
数据库·sql·oracle·ninedata·故障排查·chatdba·实例巡检
王小王-1231 小时前
基于 Hadoop 的心脏病分析可视化与风险预测系统
大数据·hadoop·分布式·心脏病预测系统·疾病预测·冠心病风险预测
海天一色y1 小时前
深入理解 RAG 技术:从语义张量到向量数据库,Milvus 与 FAISS 全面对比
数据库·milvus·faiss
爱吃羊的老虎1 小时前
【数据库】模块二:SQL 语句、高级特性与优化
数据库·oracle
Rain5091 小时前
2.4. PostgreSQL 数据库连接与实战指南
前端·数据库·人工智能·后端·postgresql·数据分析
爱喝水的鱼丶1 小时前
SAP-ABAP:SAP表与视图权限管控方案:表维护权限、视图访问权限配置实操
运维·数据库·性能优化·sap·abap·权限·表和视图
tomcoding1 小时前
深入解析Oracle数据块的内部结构
数据库·oracle
TPBoreas7 小时前
springboot3.5比2.x做了哪儿些提升
数据仓库·hive·hadoop