Skip to content

项目6 HBase分布式数据库实战教程 ​

项目场景 ​

本教程将带你从零开始搭建HBase分布式数据库集群,并实现一个电影网站用户影评分析系统。通过这个项目,你将掌握HBase的安装配置、Shell命令操作以及Java API编程。


第一部分:环境准备与集群搭建 ​

1.1 服务器规划 ​

集群架构:

  • master节点:192.168.128.130(HBase Master)
  • slave1节点:192.168.128.131(ZooKeeper + RegionServer)
  • slave2节点:192.168.128.132(ZooKeeper + RegionServer)
  • slave3节点:192.168.128.133(ZooKeeper + RegionServer)

1.2 部署ZooKeeper集群 ​

步骤1:下载并解压ZooKeeper ​

bash
# 在slave1节点操作
cd /opt/software
wget https://downloads.apache.org/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7.0-bin.tar.gz
tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz -C /opt/module/
cd /opt/module
mv apache-zookeeper-3.7.0-bin zookeeper

步骤2:配置zoo.cfg ​

bash
cd /opt/module/zookeeper/conf
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg

配置内容:

properties
# 数据目录
dataDir=/opt/module/zookeeper/zkData
# 客户端连接端口
clientPort=2181
# 集群配置
server.1=slave1:2888:3888
server.2=slave2:2888:3888
server.3=slave3:2888:3888

步骤3:创建myid文件 ​

bash
# 在slave1上
mkdir -p /opt/module/zookeeper/zkData
echo "1" > /opt/module/zookeeper/zkData/myid

# 在slave2上
echo "2" > /opt/module/zookeeper/zkData/myid

# 在slave3上
echo "3" > /opt/module/zookeeper/zkData/myid

步骤4:配置环境变量 ​

bash
vi /etc/profile

# 添加以下内容
export ZOOKEEPER_HOME=/opt/module/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin

# 刷新环境变量
source /etc/profile

步骤5:分发配置文件 ​

bash
# 在slave1上执行
scp -r /opt/module/zookeeper slave2:/opt/module/
scp -r /opt/module/zookeeper slave3:/opt/module/
scp /etc/profile slave2:/etc/profile
scp /etc/profile slave3:/etc/profile

步骤6:启动ZooKeeper集群 ​

bash
# 在slave1、slave2、slave3上分别执行
zkServer.sh start

# 查看状态
zkServer.sh status

正常输出:

  • 一个节点显示:Mode: leader
  • 两个节点显示:Mode: follower

1.3 部署HBase集群 ​

步骤1:下载并解压HBase ​

bash
# 在master节点操作
cd /opt/software
wget https://downloads.apache.org/hbase/2.4.9/hbase-2.4.9-bin.tar.gz
tar -zxvf hbase-2.4.9-bin.tar.gz -C /opt/module/
cd /opt/module
mv hbase-2.4.9 hbase

步骤2:配置hbase-env.sh ​

bash
cd /opt/module/hbase/conf
vi hbase-env.sh

添加配置:

bash
export JAVA_HOME=/opt/module/jdk1.8.0_212
export HBASE_MANAGES_ZK=false  # 使用外部ZooKeeper

步骤3:配置hbase-site.xml ​

bash
vi hbase-site.xml

配置内容:

xml
<configuration>
    <!-- HBase数据存储目录 -->
    <property>
        <name>hbase.rootdir</name>
        <value>hdfs://master:9000/hbase</value>
    </property>
    
    <!-- 集群模式 -->
    <property>
        <name>hbase.cluster.distributed</name>
        <value>true</value>
    </property>
    
    <!-- ZooKeeper集群地址 -->
    <property>
        <name>hbase.zookeeper.quorum</name>
        <value>slave1,slave2,slave3</value>
    </property>
    
    <!-- ZooKeeper数据目录 -->
    <property>
        <name>hbase.zookeeper.property.dataDir</name>
        <value>/opt/module/zookeeper/zkData</value>
    </property>
</configuration>

步骤4:配置regionservers ​

bash
vi regionservers

内容:

slave1
slave2
slave3

步骤5:配置环境变量 ​

bash
vi /etc/profile

# 添加
export HBASE_HOME=/opt/module/hbase
export PATH=$PATH:$HBASE_HOME/bin

source /etc/profile

步骤6:分发HBase配置 ​

bash
scp -r /opt/module/hbase slave1:/opt/module/
scp -r /opt/module/hbase slave2:/opt/module/
scp -r /opt/module/hbase slave3:/opt/module/

步骤7:启动HBase ​

bash
# 1. 启动Hadoop(在master上)
start-all.sh

# 2. 启动ZooKeeper(在slave1、slave2、slave3上)
zkServer.sh start

# 3. 启动HBase(在master上)
start-hbase.sh

步骤8:验证安装 ​

查看进程:

bash
# master节点应有
jps
# HMaster

# slave节点应有
jps
# HRegionServer

Web界面访问:


第二部分:HBase Shell命令实战 ​

2.1 进入HBase Shell ​

bash
hbase shell

2.2 表操作命令 ​

创建表 ​

bash
# 创建student表,包含info列族
create 'student', 'info'

# 创建包含多个列族的表
create 'user', 'basic', 'address', 'contact'

查看表信息 ​

bash
# 列出所有表
list

# 查看表结构
describe 'student'

# 检查表是否存在
exists 'student'

修改表结构 ​

bash
# 添加列族
alter 'student', NAME => 'relationship'

# 删除列族
alter 'student', 'delete' => 'relationship'

# 修改列族属性
alter 'student', {NAME => 'info', VERSIONS => 5}

删除表 ​

bash
# 禁用表
disable 'student'

# 删除表
drop 'student'

# 检查是否删除成功
exists 'student'

2.3 数据操作命令 ​

插入数据 ​

bash
# 向student表插入数据
put 'student', 'row1', 'info:name', 'zhangsan'
put 'student', 'row1', 'info:age', '20'
put 'student', 'row1', 'info:gender', 'male'

put 'student', 'row2', 'info:name', 'lisi'
put 'student', 'row2', 'info:age', '22'

查询数据 ​

bash
# 查询指定行
get 'student', 'row1'

# 查询指定列
get 'student', 'row1', 'info:name'

# 扫描全表
scan 'student'

# 条件扫描
scan 'student', {STARTROW => 'row1', STOPROW => 'row2'}

# 统计行数
count 'student'

删除数据 ​

bash
# 删除指定单元格
delete 'student', 'row1', 'info:age'

# 删除整行
deleteall 'student', 'row1'

# 清空表
truncate 'student'

第三部分:Java API编程实战 ​

3.1 创建Maven项目 ​

pom.xml配置 ​

xml
<dependencies>
    <!-- HBase客户端 -->
    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-client</artifactId>
        <version>2.4.9</version>
    </dependency>
    
    <!-- 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

3.2 基础工具类 ​

java
package com.movie.hbase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

public class HBaseUtils {
    
    private static Configuration conf;
    private static Connection connection;
    private static Admin admin;
    
    // 初始化配置
    static {
        conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "slave1,slave2,slave3");
        conf.set("hbase.zookeeper.property.clientPort", "2181");
        
        try {
            connection = ConnectionFactory.createConnection(conf);
            admin = connection.getAdmin();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    // 创建表
    public static void createTable(String tableName, String... columnFamilies) 
            throws IOException {
        TableName table = TableName.valueOf(tableName);
        
        if (admin.tableExists(table)) {
            System.out.println("表已存在:" + tableName);
            return;
        }
        
        TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(table);
        for (String cf : columnFamilies) {
            ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder
                    .newBuilder(Bytes.toBytes(cf))
                    .build();
            builder.setColumnFamily(cfd);
        }
        
        admin.createTable(builder.build());
        System.out.println("创建表成功:" + tableName);
    }
    
    // 插入数据
    public static void putData(String tableName, String rowKey, 
                              String cf, String column, String value) 
            throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        
        Put put = new Put(Bytes.toBytes(rowKey));
        put.addColumn(Bytes.toBytes(cf), 
                     Bytes.toBytes(column), 
                     Bytes.toBytes(value));
        
        table.put(put);
        table.close();
    }
    
    // 查询数据
    public static void getData(String tableName, String rowKey) 
            throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        
        Get get = new Get(Bytes.toBytes(rowKey));
        Result result = table.get(get);
        
        for (Cell cell : result.rawCells()) {
            System.out.println("行键:" + Bytes.toString(CellUtil.cloneRow(cell)));
            System.out.println("列族:" + Bytes.toString(CellUtil.cloneFamily(cell)));
            System.out.println("列名:" + Bytes.toString(CellUtil.cloneQualifier(cell)));
            System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
            System.out.println("------------------------");
        }
        
        table.close();
    }
    
    // 扫描表
    public static void scanTable(String tableName) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        
        Scan scan = new Scan();
        ResultScanner scanner = table.getScanner(scan);
        
        for (Result result : scanner) {
            for (Cell cell : result.rawCells()) {
                System.out.println("行键:" + Bytes.toString(CellUtil.cloneRow(cell)));
                System.out.println("列:" + Bytes.toString(CellUtil.cloneFamily(cell)) 
                        + ":" + Bytes.toString(CellUtil.cloneQualifier(cell)));
                System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
            }
            System.out.println("============================");
        }
        
        scanner.close();
        table.close();
    }
    
    // 删除表
    public static void dropTable(String tableName) throws IOException {
        TableName table = TableName.valueOf(tableName);
        
        if (!admin.tableExists(table)) {
            System.out.println("表不存在:" + tableName);
            return;
        }
        
        admin.disableTable(table);
        admin.deleteTable(table);
        System.out.println("删除表成功:" + tableName);
    }
    
    // 关闭连接
    public static void close() {
        try {
            if (admin != null) admin.close();
            if (connection != null) connection.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.3 项目实战:电影影评分析系统 ​

任务1:存储评分次数最多的10部电影 ​

表设计:

  • 表名:film_RateNum
  • RowKey:Top1 ~ Top10
  • 列族:scoringtimes
  • 列:MovieID、Movietype、RateNum

实现代码:

java
package com.movie.hbase;

import java.io.IOException;

public class Task1_TopRatedMovies {
    
    public static void main(String[] args) throws IOException {
        String tableName = "film_RateNum";
        
        // 1. 创建表
        HBaseUtils.createTable(tableName, "scoringtimes");
        
        // 2. 插入数据(模拟评分次数最多的10部电影)
        insertTopMovies(tableName);
        
        // 3. 查询数据
        System.out.println("======= 评分次数最多的10部电影 =======");
        HBaseUtils.scanTable(tableName);
        
        HBaseUtils.close();
    }
    
    private static void insertTopMovies(String tableName) throws IOException {
        // 电影数据示例
        String[][] movies = {
            {"Top1", "2858", "Drama|Romance", "3428"},
            {"Top2", "260", "Action|Crime|Thriller", "2991"},
            {"Top3", "1196", "Drama|War", "2990"},
            {"Top4", "1210", "Drama|War", "2883"},
            {"Top5", "480", "Adventure|Drama|Sci-Fi", "2672"},
            {"Top6", "2028", "Drama|Mystery|Thriller", "2653"},
            {"Top7", "589", "Comedy|Drama|Romance", "2649"},
            {"Top8", "2571", "Drama|Thriller", "2649"},
            {"Top9", "1270", "Action|Drama|War", "2583"},
            {"Top10", "593", "Drama|Thriller", "2578"}
        };
        
        for (String[] movie : movies) {
            HBaseUtils.putData(tableName, movie[0], "scoringtimes", 
                             "MovieID", movie[1]);
            HBaseUtils.putData(tableName, movie[0], "scoringtimes", 
                             "Movietype", movie[2]);
            HBaseUtils.putData(tableName, movie[0], "scoringtimes", 
                             "RateNum", movie[3]);
        }
        
        System.out.println("插入数据完成!");
    }
}

任务2:存储不同性别用户评分最高的10部电影 ​

表设计:

  • 表名:film_GenderRatings
  • RowKey:F_Top1 ~ F_Top10(女性)、M_Top1 ~ M_Top10(男性)
  • 列族:info
  • 列:MovieID、Rating

实现代码:

java
package com.movie.hbase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Task2_GenderRatings {
    
    public static void main(String[] args) throws IOException {
        String tableName = "film_GenderRatings";
        String hdfsPath = "/join/MoviesRatesTop10GroupByGender/part-r-00000";
        
        // 1. 创建表
        HBaseUtils.createTable(tableName, "info");
        
        // 2. 从HDFS读取数据并插入HBase
        readFromHDFSAndInsert(tableName, hdfsPath);
        
        // 3. 查询数据
        System.out.println("======= 不同性别评分最高的电影 =======");
        HBaseUtils.scanTable(tableName);
        
        HBaseUtils.close();
    }
    
    private static void readFromHDFSAndInsert(String tableName, String hdfsPath) 
            throws IOException {
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://master:9000");
        
        FileSystem fs = FileSystem.get(conf);
        Path path = new Path(hdfsPath);
        
        FSDataInputStream in = fs.open(path);
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        
        String line;
        int femaleCount = 0, maleCount = 0;
        
        while ((line = br.readLine()) != null) {
            String[] fields = line.split("\t");
            if (fields.length >= 3) {
                String gender = fields[0];  // F或M
                String movieId = fields[1];
                String rating = fields[2];
                
                String rowKey;
                if ("F".equals(gender)) {
                    femaleCount++;
                    rowKey = "F_Top" + femaleCount;
                } else {
                    maleCount++;
                    rowKey = "M_Top" + maleCount;
                }
                
                HBaseUtils.putData(tableName, rowKey, "info", 
                                 "MovieID", movieId);
                HBaseUtils.putData(tableName, rowKey, "info", 
                                 "Rating", rating);
            }
        }
        
        br.close();
        fs.close();
        System.out.println("从HDFS读取并插入数据完成!");
    }
}

任务3:存储电影ID为2858各年龄段用户的平均评分 ​

表设计:

  • 表名:film_AgeRatings
  • RowKey:Age=1、Age=18、Age=25等
  • 列族:info
  • 列:avgRating、MovieID

实现代码:

java
package com.movie.hbase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Task3_AgeRatings {
    
    public static void main(String[] args) throws IOException {
        String tableName = "film_AgeRatings";
        String hdfsPath = "/join/MoviesAvgScoreGroupByAge/part-r-00000";
        
        // 1. 创建表
        HBaseUtils.createTable(tableName, "info");
        
        // 2. 从HDFS读取数据
        readAndInsertAgeRatings(tableName, hdfsPath);
        
        // 3. 查询数据
        System.out.println("======= 电影2858各年龄段平均评分 =======");
        HBaseUtils.scanTable(tableName);
        
        HBaseUtils.close();
    }
    
    private static void readAndInsertAgeRatings(String tableName, String hdfsPath) 
            throws IOException {
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://master:9000");
        
        FileSystem fs = FileSystem.get(conf);
        FSDataInputStream in = fs.open(new Path(hdfsPath));
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        
        String line;
        while ((line = br.readLine()) != null) {
            String[] fields = line.split("\t");
            if (fields.length >= 3) {
                String age = fields[0];
                String movieId = fields[1];
                String avgRating = fields[2];
                
                String rowKey = "Age=" + age;
                
                HBaseUtils.putData(tableName, rowKey, "info", 
                                 "MovieID", movieId);
                HBaseUtils.putData(tableName, rowKey, "info", 
                                 "avgRating", avgRating);
            }
        }
        
        br.close();
        fs.close();
        System.out.println("年龄段评分数据插入完成!");
    }
}

任务4:存储各类型电影评分最高的5部 ​

表设计:

  • 表名:film_TypeRatings
  • RowKey:Action_Top1、Drama_Top1等
  • 列族:info
  • 列:MovieID、Rating

实现代码:

java
package com.movie.hbase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

public class Task4_TypeRatings {
    
    public static void main(String[] args) throws IOException {
        String tableName = "film_TypeRatings";
        String hdfsPath = "/join/outputTop5/part-r-00000";
        
        // 1. 创建表
        HBaseUtils.createTable(tableName, "info");
        
        // 2. 读取并插入数据
        readAndInsertTypeRatings(tableName, hdfsPath);
        
        // 3. 查询数据
        System.out.println("======= 各类型电影评分TOP5 =======");
        HBaseUtils.scanTable(tableName);
        
        HBaseUtils.close();
    }
    
    private static void readAndInsertTypeRatings(String tableName, String hdfsPath) 
            throws IOException {
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://master:9000");
        
        FileSystem fs = FileSystem.get(conf);
        FSDataInputStream in = fs.open(new Path(hdfsPath));
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        
        Map<String, Integer> typeCountMap = new HashMap<>();
        String line;
        
        while ((line = br.readLine()) != null) {
            String[] fields = line.split("\t");
            if (fields.length >= 3) {
                String type = fields[0];
                String movieId = fields[1];
                String rating = fields[2];
                
                // 统计每个类型的计数
                int count = typeCountMap.getOrDefault(type, 0) + 1;
                typeCountMap.put(type, count);
                
                String rowKey = type + "_Top" + count;
                
                HBaseUtils.putData(tableName, rowKey, "info", 
                                 "MovieID", movieId);
                HBaseUtils.putData(tableName, rowKey, "info", 
                                 "Rating", rating);
            }
        }
        
        br.close();
        fs.close();
        System.out.println("电影类型评分数据插入完成!");
    }
}

第四部分:常见问题与解决方案 ​

4.1 集群启动问题 ​

问题1:HBase无法启动

bash
# 检查Hadoop是否启动
jps  # 应看到NameNode和DataNode

# 检查ZooKeeper是否启动
zkServer.sh status

# 查看HBase日志
tail -f $HBASE_HOME/logs/hbase-*-master-*.log

问题2:RegionServer启动失败

bash
# 检查时钟同步
ntpdate -u ntp.aliyun.com

# 检查防火墙
systemctl stop firewalld

4.2 HBase Shell常见错误 ​

错误1:表已存在

bash
# 解决方法
disable 'table_name'
drop 'table_name'
create 'table_name', 'cf'

错误2:连接超时

bash
# 检查ZooKeeper连接
hbase zkcli
ls /hbase

4.3 Java API常见问题 ​

问题1:找不到表

java
// 确保表名正确
TableName tableName = TableName.valueOf("film_RateNum");
if (admin.tableExists(tableName)) {
    System.out.println("表存在");
}

问题2:连接失败

java
// 检查配置
conf.set("hbase.zookeeper.quorum", "slave1,slave2,slave3");
conf.set("hbase.zookeeper.property.clientPort", "2181");

// 确保网络通畅
// ping slave1

第五部分:性能优化建议 ​

5.1 RowKey设计原则 ​

java
// 1. 避免热点问题 - 使用散列前缀
String rowKey = MD5Hash.getMD5AsHex(originalKey.getBytes()).substring(0, 4) 
                + "_" + originalKey;

// 2. 定长设计
String rowKey = String.format("Top%02d", number);  // Top01, Top02...

// 3. 反转时间戳(查询最新数据)
long reverseTimestamp = Long.MAX_VALUE - System.currentTimeMillis();
String rowKey = "user_" + userId + "_" + reverseTimestamp;

5.2 批量操作 ​

java
// 批量插入
public static void batchPut(String tableName, List<Put> puts) 
        throws IOException {
    Table table = connection.getTable(TableName.valueOf(tableName));
    table.put(puts);
    table.close();
}

// 批量查询
public static void batchGet(String tableName, List<Get> gets) 
        throws IOException {
    Table table = connection.getTable(TableName.valueOf(tableName));
    Result[] results = table.get(gets);
    table.close();
}

5.3 配置优化 ​

xml
<!-- hbase-site.xml -->
<!-- 增加RegionServer的handler数量 -->
<property>
    <name>hbase.regionserver.handler.count</name>
    <value>100</value>
</property>

<!-- 调整MemStore大小 -->
<property>
    <name>hbase.regionserver.global.memstore.size</name>
    <value>0.4</value>
</property>

总结 ​

通过本教程,你已经掌握了:

  1. ✅ 环境搭建:ZooKeeper集群 + HBase集群的完整部署
  2. ✅ Shell操作:创建表、插入数据、查询数据、删除数据
  3. ✅ Java编程:使用Java API进行表设计和数据操作
  4. ✅ 项目实战:完成4个电影影评分析任务
  5. ✅ 问题排查:常见错误的解决方案

下一步建议:

  • 深入学习HBase的Region分裂机制
  • 研究HBase与MapReduce的集成
  • 探索HBase的二级索引方案(Phoenix)
  • 实践HBase的数据备份与恢复

基于 Vite 强力驱动 | 纯静态轻量托管