项目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
# HRegionServerWeb界面访问:
- HBase Master: http://192.168.128.130:16010
- RegionServer: http://192.168.128.131:16030
第二部分:HBase Shell命令实战
2.1 进入HBase Shell
bash
hbase shell2.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 firewalld4.2 HBase Shell常见错误
错误1:表已存在
bash
# 解决方法
disable 'table_name'
drop 'table_name'
create 'table_name', 'cf'错误2:连接超时
bash
# 检查ZooKeeper连接
hbase zkcli
ls /hbase4.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>总结
通过本教程,你已经掌握了:
- ✅ 环境搭建:ZooKeeper集群 + HBase集群的完整部署
- ✅ Shell操作:创建表、插入数据、查询数据、删除数据
- ✅ Java编程:使用Java API进行表设计和数据操作
- ✅ 项目实战:完成4个电影影评分析任务
- ✅ 问题排查:常见错误的解决方案
下一步建议:
- 深入学习HBase的Region分裂机制
- 研究HBase与MapReduce的集成
- 探索HBase的二级索引方案(Phoenix)
- 实践HBase的数据备份与恢复