项目:lean/Hbase_demo
1.安装docker
2.安装Hbase
bash
docker pull harisekhon/hbase
3.配置docker-compose.yml文件
bash
version: '3.8'
services:
hbase:
image: harisekhon/hbase:latest
container_name: hbase-standalone
hostname: hbase
ports:
- "2181:2181" # Zookeeper
- "16000:16000" # HBase Master
- "16010:16010" # HBase Master Web UI
- "16020:16020" # HBase RegionServer
- "16030:16030" # HBase RegionServer Web UI
environment:
- HBASE_CONF_hbase_rootdir=file:///hbase-data
- HBASE_CONF_hbase_zookeeper_property_dataDir=/zookeeper-data
volumes:
- hbase-data:/hbase-data
- zookeeper-data:/zookeeper-data
networks:
- hbase-network
healthcheck:
test: ["CMD", "hbase", "shell", "-n", "-e", "status"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
volumes:
hbase-data:
driver: local
zookeeper-data:
driver: local
networks:
hbase-network:
driver: bridge
含义如下
1. - "2181:2181" # Zookeeper
- 含义 :将容器的 2181 端口映射到你电脑的 2181 端口。
- 作用 :这是 ZooKeeper 的服务端口。
- HBase 强依赖 ZooKeeper 来管理集群状态。
- 你的 Java 代码、HBase Shell 或者其他客户端,都需要通过这个端口连接到 HBase 集群。
- 注意:这就是你刚才报错的原因!因为你电脑上已经有一个程序(你之前启动的独立 ZK 容器)占用了 2181,所以这扇"窗户"打不开了。
2. - "16000:16000" # HBase Master
- 含义 :将容器的 16000 端口映射到你电脑的 16000 端口。
- 作用 :这是 HBase Master 的 RPC 通信端口 。
- 这是 HBase 的"大脑"。RegionServer(干活的节点)会连这个端口汇报工作。
- 客户端(如 Java 代码)也会连这个端口来获取元数据(比如某张表存在哪个 RegionServer 上)。
- 通常不需要在浏览器访问,主要是给程序内部通信用的。
3. - "16010:16010" # HBase Master Web UI
- 含义 :将容器的 16010 端口映射到你电脑的 16010 端口。
- 作用 :这是 HBase Master 的网页管理界面 。
- 最常用! 你可以在浏览器输入
http://localhost:16010访问它。 - 在这里你可以看到:集群是否健康、有多少个 RegionServer、有哪些表、每个表的读写负载情况等。
- 它是可视化的监控面板。
- 最常用! 你可以在浏览器输入
4. - "16020:16020" # HBase RegionServer
- 含义 :将容器的 16020 端口映射到你电脑的 16020 端口。
- 作用 :这是 RegionServer 的 RPC 通信端口 。
- RegionServer 是 HBase 中真正存储数据和处理读写请求的"工人"。
- 当你的 Java 代码知道数据在哪个 RegionServer 后,会直接连这个端口进行数据的增删改查(Put/Get/Delete)。
- 这是数据流量最大的端口。
5. - "16030:16030" # HBase RegionServer Web UI
- 含义 :将容器的 16030 端口映射到你电脑的 16030 端口。
- 作用 :这是 RegionServer 的网页管理界面 。
- 可以在浏览器输入
http://localhost:16030访问。 - 这里主要看单个节点的状态,比如这个节点存了哪些 Region(数据分片)、内存使用情况、具体的日志报错等。
- 通常用于排查某个具体节点的问题。
- 可以在浏览器输入

注意 :docker运行的Hbase还要修改hosts文件,添加127.0.0.1 hbase
4.GUI web界面
- "2181:2181" # Zookeeper
- "16000:16000" # HBase Master HBase Master 的 RPC 通信端口。
- "16010:16010" # HBase Master Web UI HBase Master 的网页管理界面。
- "16020:16020" # HBase RegionServer RegionServer 的 RPC 通信端口。
- "16030:16030" # HBase RegionServer Web UI RegionServer 的网页管理界面。
Master: hbase
HBase Region Server: hbase
5.Hbase测试项目
注意 :这里我用的3.3.6版本的hadoop,但是还要下载其他的加入到环境变量里面去
下载h~.dll和w~.exe,然后加入到变量就好了,bin路径

依赖
bash
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hbase-log-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>HBase测试项目</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<!-- 推荐使用稳定的 2.4.x 版本,兼容性最好 -->
<hbase.version>2.6.4</hbase.version>
<!-- 显式指定 Hadoop 版本,避免依赖冲突 -->
<hadoop.version>3.3.6</hadoop.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 强制锁定 Hadoop 所有核心组件为 3.3.6 -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.3.6</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-auth</artifactId>
<version>3.3.6</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.hadoop</groupId>-->
<!-- <artifactId>hadoop-hdfs</artifactId>-->
<!-- <version>3.3.6</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.hadoop</groupId>-->
<!-- <artifactId>hadoop-mapreduce-client-core</artifactId>-->
<!-- <version>3.3.6</version>-->
<!-- </dependency>-->
<!-- 锁定 HBase 版本 -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.4.18</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- HBase Client -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>${hbase.version}</version>
<!-- 排除旧版日志和可能冲突的组件 -->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<!-- 排除 jetty,避免与 Spring Boot 内置 Tomcat 冲突 -->
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- HBase Common (显式引入以确保类完整) -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>${hbase.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
【关键】显式引入 Hadoop Common
解决 "HADOOP_HOME and hadoop.home.dir are unset" 的关键依赖之一
必须与 HBase 兼容,3.3.6 是稳定选择
-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Jackson (Spring Boot 已管理版本,通常不需要单独写版本号) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置类
bash
package com.yyz.hbase_demo.config;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.annotation.PreDestroy;
import java.io.IOException;
/**
* HBase配置类
*/
@org.springframework.context.annotation.Configuration
public class HBaseConfig {
@Value("${hbase.config.zk-quorum}")
private String zkQuorum;
@Value("${hbase.config.client-port}")
private String clientPort;
@Value("${hbase.config.zookeeper-znode-parent}")
private String znodeParent;
private Connection connection;
@Bean
public Connection hbaseConnection() throws IOException {
try {
Configuration config = HBaseConfiguration.create();
config.set("hbase.zookeeper.quorum", zkQuorum);
config.set("hbase.zookeeper.property.clientPort", clientPort);
config.set("zookeeper.znode.parent", znodeParent);
config.set("hbase.client.write.buffer", "0");
connection = ConnectionFactory.createConnection(config);
System.out.println("HBase连接已创建");
return connection;
} catch (Exception e) {
e.printStackTrace();
// 重要:如果连接失败,不要让它卡住主线程,可以返回 null 或者抛出一个明确的运行时异常
// 但为了调试,暂时让它抛出,方便你知道错了
throw new RuntimeException("HBase 连接失败,请检查 HBase 服务及 hosts 配置", e);
}
}
@PreDestroy
public void closeConnection() throws IOException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}
控制器
bash
package com.yyz.hbase_demo.controller;
import com.yyz.hbase_demo.entity.LogEntry;
import com.yyz.hbase_demo.service.LogService;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
/**
* 日志控制器
*/
@RestController
@RequestMapping("/api/logs")
public class LogController {
private final LogService logService;
public LogController(LogService logService) {
this.logService = logService;
}
@GetMapping
public String getAllLogs() {
return "Hello World!";
}
@PostMapping
public String addLog(@RequestBody LogEntry log) {
try {
logService.saveLog(log);
return "Success: Log saved for user " + log.getUserId();
} catch (IOException e) {
e.printStackTrace();
return "Error: " + e.getMessage();
}
}
@GetMapping("/user/{userId}")
public List<LogEntry> getUserLogs(@PathVariable String userId) {
try {
return logService.getUserLogs(userId);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@GetMapping("/user/{userId}/action/{action}")
public List<LogEntry> getUserLogsByAction(@PathVariable String userId, @PathVariable String action) {
try {
return logService.getUserLogsByAction(userId, action);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
实体
bash
package com.yyz.hbase_demo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 日志实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LogEntry {
private String userId;
private String action; // 例如:CLICK, VIEW, ERROR
private String pageUrl;
private long timestamp;
private String details; // 额外信息
}
数据层
bash
package com.yyz.hbase_demo.repository;
import com.yyz.hbase_demo.entity.LogEntry;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* HBase数据访问层
*/
@Repository
public class LogRepository {
private final Connection connection;
private static final String TABLE_NAME_STR = "user_logs";
private static final byte[] TABLE_NAME = Bytes.toBytes(TABLE_NAME_STR);
private static final byte[] CF = Bytes.toBytes("cf");
private static final byte[] COL_ACTION = Bytes.toBytes("action");
private static final byte[] COL_URL = Bytes.toBytes("url");
private static final byte[] COL_DETAILS = Bytes.toBytes("details");
private static final byte[] COL_TS = Bytes.toBytes("ts");
public LogRepository(Connection connection) {
this.connection = connection;
}
@PostConstruct
public void initTable() throws IOException {
Admin admin = connection.getAdmin();
// 检查表是否存在,不存在则创建
if (!admin.tableExists(TableName.valueOf(TABLE_NAME))) {
System.out.println(">>> Table '" + TABLE_NAME_STR + "' does not exist. Creating...");
// 1. 创建表描述符建造者
TableDescriptorBuilder tableBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(TABLE_NAME));
// 2. 【关键修改】创建列族描述符
// ColumnFamilyDescriptorBuilder.of() 返回的是一个 ModifyableColumnFamilyDescriptor (实现了 ColumnFamilyDescriptor)
// 所以变量类型必须是 ColumnFamilyDescriptor,不能是 Builder
ColumnFamilyDescriptor cfDescriptor = ColumnFamilyDescriptorBuilder.of(CF);
// 3. 设置列族属性 (需要强转为可修改的实现类才能设置setMaxVersions)
// 注意:of() 返回的对象通常就是 ModifyableColumnFamilyDescriptor
if (cfDescriptor instanceof ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor) {
((ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor) cfDescriptor).setMaxVersions(5);
}
// 4. 将列族描述符加入表建造者
// 直接传入 cfDescriptor,因为它已经是最终对象了,不需要再 .build()
tableBuilder.setColumnFamily(cfDescriptor);
// 5. 构建表描述符并创建表
admin.createTable(tableBuilder.build());
System.out.println(">>> Table '" + TABLE_NAME_STR + "' created successfully.");
} else {
System.out.println(">>> Table '" + TABLE_NAME_STR + "' already exists.");
}
admin.close();
}
public void save(LogEntry log) throws IOException {
Table table = connection.getTable(TableName.valueOf(TABLE_NAME));
try {
String rowKeyStr = log.getUserId() + "_" + log.getTimestamp();
Put put = new Put(Bytes.toBytes(rowKeyStr));
put.addColumn(CF, COL_ACTION, Bytes.toBytes(log.getAction()));
put.addColumn(CF, COL_URL, Bytes.toBytes(log.getPageUrl()));
put.addColumn(CF, COL_DETAILS, Bytes.toBytes(log.getDetails()));
put.addColumn(CF, COL_TS, Bytes.toBytes(log.getTimestamp()));
table.put(put);
} finally {
table.close();
}
}
public List<LogEntry> findByUserId(String userId) throws IOException {
List<LogEntry> results = new ArrayList<>();
Table table = connection.getTable(TableName.valueOf(TABLE_NAME));
try {
Scan scan = new Scan();
byte[] startRow = Bytes.toBytes(userId + "_");
byte[] stopRow = Bytes.toBytes(userId + "_\uffff");
scan.withStartRow(startRow);
scan.withStopRow(stopRow);
// scan.setReversed(true);
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
results.add(parseResult(result));
}
scanner.close();
} finally {
table.close();
}
return results;
}
public List<LogEntry> findByUserIdAndAction(String userId, String action) throws IOException {
List<LogEntry> results = new ArrayList<>();
Table table = connection.getTable(TableName.valueOf(TABLE_NAME));
try {
Scan scan = new Scan();
byte[] startRow = Bytes.toBytes(userId + "_");
byte[] stopRow = Bytes.toBytes(userId + "_\uffff");
scan.withStartRow(startRow);
scan.withStopRow(stopRow);
SingleColumnValueFilter filter = new SingleColumnValueFilter(
CF,
COL_ACTION,
CompareFilter.CompareOp.EQUAL,
Bytes.toBytes(action)
);
filter.setFilterIfMissing(true);
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
results.add(parseResult(result));
}
scanner.close();
} finally {
table.close();
}
return results;
}
private LogEntry parseResult(Result result) {
String rowKey = Bytes.toString(result.getRow());
String[] parts = rowKey.split("_");
String userId = parts[0];
long ts = Long.parseLong(parts[1]);
String action = Bytes.toString(result.getValue(CF, COL_ACTION));
String url = Bytes.toString(result.getValue(CF, COL_URL));
String details = Bytes.toString(result.getValue(CF, COL_DETAILS));
return new LogEntry(userId, action, url, ts, details);
}
}
方法层
bash
package com.yyz.hbase_demo.service;
import com.yyz.hbase_demo.entity.LogEntry;
import com.yyz.hbase_demo.repository.LogRepository;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
/**
* 日志业务逻辑层
*/
@Service
public class LogService {
private final LogRepository logRepository;
public LogService(LogRepository logRepository) {
this.logRepository = logRepository;
}
public void saveLog(LogEntry log) throws IOException {
if (log.getTimestamp() == 0) {
log.setTimestamp(System.currentTimeMillis());
}
logRepository.save(log);
}
public List<LogEntry> getUserLogs(String userId) throws IOException {
return logRepository.findByUserId(userId);
}
public List<LogEntry> getUserLogsByAction(String userId, String action) throws IOException {
return logRepository.findByUserIdAndAction(userId, action);
}
}