1、HBase Java编程
1.1 需求与数据集
自来水公司存储大量数据,数据集格式示例如下
用户id | 姓名 | 用户地址 | 性别 | 缴费时间 | 表示数(本次) | 表示数(上次) | 用量(立方) | 合计金额 | 查表日期 | 最迟缴费日期 |
4944191 | 登卫红 | 贵州省铜仁市德江县7单元267室 | 男 | 2020-05-10 | 308.1 | 283.1 | 25 | 150 | 2020-04-25 | 2020-06-09 |
用HBase存储,希望用java程序访问
1.2 准备
(1)创建IDEA Maven项目
groupid:cn.btks, artifactId: habase_op

(2)导入包依赖
<repositories><!-- 代码库 -->
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- Hbase的java客户端-->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.1.0</version>
</dependency>
<!--操作文件的-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- 做单元测试的 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
</configuration>
</plugin>
</plugins>
</build>
(3)复制HBase的Hadoop配置文件,可以从hbase服务器中下载
所在目录及文件
/export/server/hbase-2.1.0/conf/hbase-site.xml
/export/server/hadoop-3.1.4/etc/hadoop/core-site.xml
还有一个日志配置文件
log4j.peopertis
拷贝到resource中
(4)创建包和类

1.3 创建Hbase链接和admin管理对象
/**
* 1. 使用HbaseConfiguration.create()创建Hbase配置
* 2. 使用ConnectionFactory.createConnection()创建Hbase连接
* 3. 要创建表,需要基于Hbase连接获取admin管理对象
* 4. 使用admin.close、connection.close关闭连接
*/
public class TableAdminTest {
private Connection connection;
private Admin admin;
@BeforeTest
public void beforeTest() throws IOException {
//从项目目录加载配置
Configuration configuration = HBaseConfiguration.create();
//使用连接的工厂创建链接
connection = ConnectionFactory.createConnection(configuration);
//创建表,需要和HMaster打交道,需要有一个admin对象
admin = connection.getAdmin();
}
@AfterTest
public void afterTest() throws IOException {
//关闭
admin.close();
connection.close();
}
}
使用Java代码创建表
@Test
public void createTableTest() throws IOException {
//1. 判断表是否存在
//a) 存在,则退出
TableName tableName = TableName.valueOf("WATER_BILL");
if (admin.tableExists(tableName)) {
System.out.println("表已存在");
return;
}
//构建表
//2. 使用TableDescriptorBuilder.newBuilder构建表描述构建器,描述列簇、属性
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tableName);
//3. 使用ColumnFamilyDescriptorBuilder.newBuilder构建列蔟描述构建器
//使用Hbase下的工具类,将常见类型转换为byte数组或将数字转换常见类型
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("C1"));
//4. 构建列蔟描述,构建表描述
ColumnFamilyDescriptor cfDes = columnFamilyDescriptorBuilder.build();
//建立表和列簇的关联
tableDescriptorBuilder.setColumnFamily(cfDes);
//5. 创建表
TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
admin.createTable(tableDescriptor);
}
小结
创建Hbase链接
创建admin对象
创建表
调用tableExists判断表是否存在
在hbase创建表,构建表描述器TableDescriptor,列簇描述器ColumnFamilyDescriptor
将列簇描述器添加到表描述器中
使用admin.createTable创建表
Connection creation is a heavy-weight operation. Connection implementations are thread-safe,
HTable:An implementation of {@link Table}. Used to communicate with a single HBase table.
* Lightweight. Get as needed and just close when done.
1.4 删除表
@Test
public void dropTable() throws IOException {
// 1. 判断表是否存在
TableName tableName = TableName.valueOf("WATER_BILL");
if (admin.tableExists(tableName)) {
// 2. 如果存在,则禁用表
admin.disableTable(tableName);
// 3. 再删除表
admin.deleteTable(tableName);
}
}
在Hbase中所有的数据都是以Byte[]的形式存储的,所以将java的数据类型进行转换
经常会用到一个工具类:Bytes(hbase包下的Bytes工具类)
字符串、整数、浮点数-->Byte[]
Byte[]-->字符串、整数、浮点数
1.5 往表里面插入一条数据
表中插入一个行,该行只包含一个列。
ROWKEY | 姓名(列名:NAME) |
4944191 | 张可 |
package cn.btks.hbase.data.api_test;
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.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.IOException;
public class DataOpTest {
private Connection connection;
private TableName tableName = TableName.valueOf("WATER_BILL");
@BeforeTest
public void beforeTest() throws IOException {
//从项目目录加载配置
Configuration configuration = HBaseConfiguration.create();
//使用连接的工厂创建链接,重量级对象,不能经常创建
connection = ConnectionFactory.createConnection(configuration);
}
@Test
public void putTest() throws IOException {
//1.使用Hbase连接获取Htable
Table table = connection.getTable(tableName);
//2.构建ROWKEY、列蔟名、列名
String rowKey = "4944191";
String columnFamily = "C1";
String columnName = "NAME";
//3.构建Put对象(对应put命令)
Put put = new Put(Bytes.toBytes(rowKey));
//4.添加姓名列
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnName),Bytes.toBytes("登卫红"));
//5.使用Htable表对象执行put操作
table.put(put);
// 6.关闭Htable表对象
//HTable是一个轻量级的对象,可以经常创建,他是一个非线程安全,及时关闭
table.close();
}
@AfterTest
public void afterTest() throws IOException {
//关闭
connection.close();
}
}
# 查看数据
scan "WATER_BILL",{FORMATTER=>'toString'}

插入其他列
列名 | 说明 | 值 |
ADDRESS | 用户地址 | 贵州省铜仁市德江县7单元267室 |
SEX | 性别 | 男 |
PAY_DATE | 缴费时间 | 2020-05-10 |
NUM_CURRENT | 表示数(本次) | 308.1 |
NUM_PREVIOUS | 表示数(上次) | 283.1 |
NUM_USAGE | 用量(立方) | 25 |
TOTAL_MONEY | 合计金额 | 150 |
RECORD_DATE | 查表日期 | 2020-04-25 |
LATEST_DATE | 最迟缴费日期 | 2020-06-09 |
String columnNameAddress = "ADDRESS";
String columnNameSex = "SEX";
String columnNamePayDate = "PAY_DATE";
String columnNameNumCurrent = "NUM_CURRENT";
String columnNameNumPrevious = "NUM_PREVIOUS";
String columnNameNumUsage = "NUM_USAGE";
String columnNameTotalMoney = "TOTAL_MONEY";
String columnNameRecordDate = "RECORD_DATE";
String columnNameLatestDate = "LATEST_DATE";
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameAddress),Bytes.toBytes("贵州省铜仁市德江县7单元267室"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameSex),Bytes.toBytes("男"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNamePayDate),Bytes.toBytes("2020-05-10"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameNumCurrent),Bytes.toBytes("308.1"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameNumPrevious),Bytes.toBytes("283.1"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameNumUsage),Bytes.toBytes("25"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameTotalMoney),Bytes.toBytes("150"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameRecordDate),Bytes.toBytes("2020-04-25"));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnNameLatestDate),Bytes.toBytes("2020-06-09"));
总结,先获取Table对象,这个对象要和HRegionServer节点连接。所以将来HRegionServer负载比较高
Hbase 的connection对象是一个重量级对象,避免频繁创建,将来spark也需要注意,它是线程安全的
table是一个轻量级的,用完就close,非线程安全的。
1.6 查看一条数据
查询rowkey为4944191的所有列的数据,并打印出来。
@Test
public void getTest() throws IOException {
//实现步骤:
//1.使用Hbase连接获取Htable
Table table = connection.getTable(tableName);
//2. 使用rowkey构建Get对象
Get get = new Get(Bytes.toBytes("4944191"));
//3. 执行get请求
Result result = table.get(get);
//4. 获取所有单元格
List<Cell> cells = result.listCells();
//5. 打印rowkey
byte[] rowKey = result.getRow();
System.out.println(Bytes.toString(rowKey));
//6. 迭代单元格列表
for (Cell cell : cells) {
//将字节数组转化为字符串
//获取列簇的名称
String cf = Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
//获取列的名称
String columnName = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getFamilyLength());
//获取值
String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
System.out.println(cf+":"+columnName+"-->"+value);
}
//7. 关闭表
table.close();
}

1.7 删除一条数据
删除rowkey为4944191的整条数据。
@Test
public void deleteTest() throws IOException {
//
//实现步骤:
//1. 获取HTable对象
Table table = connection.getTable(tableName);
//2. 根据rowkey构建delete对象
Delete delete = new Delete(Bytes.toBytes("4944191"));
//3. 执行delete请求
table.delete(delete);
//4. 关闭表
table.close();
}
代码执行前后对比

1.8 导入测试数据
导入10万条抄表测试数据导入到hbase中
import Job,在Hbase中有一个MR程序,可以专门用来导入数据文件到HBase中
用法:
hbase org.apache.hadoop.hbase.mapreduce.Import 表名 HDFS数据文件路径
步骤和代码
# 1、在HDFS中创建一个文件夹,保存需要导入的数据
hadoop fs -mkdir -p /water_bill/output_ept_10W
#2、上传文件
hadoop fs -put part-m-00000_10w /water_bill/output_ept_10W
数据上传之前

数据上传之后


启动yarn
start-yarn.sh
导入数据
hbase org.apache.hadoop.hbase.mapreduce.Import WATER_BILL /water_bill/output_ept_10W
可以查看到启动一个作业

查看导入数据量
count "WATER_BILL"

导出数据工具
hbase org.apache.hadoop.hbase.mapreduce.Export WATER_BILL /water_bill/output_ept_10W_export


1.9 查询数据,过滤
在Java API中,使用scan + filter来实现过滤查询。查询从2020年6月1日到2020年6月30日的所有抄表数据。
创建类

package cn.btks.hbase.data.api_test;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
public class ScanFilterTest {
private Connection connection;
private TableName tableName = TableName.valueOf("WATER_BILL");
@BeforeTest
public void beforeTest() throws IOException {
//从项目目录加载配置
Configuration configuration = HBaseConfiguration.create();
//使用连接的工厂创建链接,重量级对象,不能经常创建
connection = ConnectionFactory.createConnection(configuration);
}
@Test
public void scanFilterTest() throws IOException {
//1. 获取表
Table table = connection.getTable(tableName);
//2. 构建scan请求对象
Scan scan = new Scan();
//3. 构建两个过滤器
//a) 构建两个日期范围过滤器(注意此处请使用RECORD_DATE——抄表日期比较
SingleColumnValueFilter startFilter = new SingleColumnValueFilter(
Bytes.toBytes("C1"), Bytes.toBytes("RECORD_DATE"),
CompareOperator.GREATER_OR_EQUAL,
new BinaryComparator(Bytes.toBytes("2020-06-01")));
SingleColumnValueFilter endFilter = new SingleColumnValueFilter(
Bytes.toBytes("C1"), Bytes.toBytes("RECORD_DATE"),
CompareOperator.LESS_OR_EQUAL,
new BinaryComparator(Bytes.toBytes("2020-06-30")));
//b) 构建过滤器列表
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL, startFilter, endFilter);
//4. 执行scan扫描请求
scan.setFilter(filterList);
ResultScanner resultScanner = table.getScanner(scan);
//5. 迭代打印result
Iterator<Result> iterator = resultScanner.iterator();
while (iterator.hasNext()){
Result result = iterator.next();
//4. 获取所有单元格
List<Cell> cells = result.listCells();
byte[] rowKey = result.getRow();
System.out.println(Bytes.toString(rowKey));
//6. 迭代单元格列表
for (Cell cell : cells) {
//将字节数组转化为字符串
//获取列簇的名称
String cf = Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
//获取列的名称
String columnName = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
//获取值
String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
System.out.println(cf+":"+columnName+"-->"+value);
}
}
//7. 关闭ResultScanner(这玩意把转换成一个个的类似get的操作,注意要关闭释放资源)
resultScanner.close();
//8. 关闭表
table.close();
}
@AfterTest
public void afterTest() throws IOException {
//关闭
connection.close();
}
}

乱码问题,因为前面我们的代码,在打印所有的列时,都是使用字符串打印的,Hbase中如果存储的是int、double,那么有可能就会乱码了。
要解决的话,我们可以根据列来判断,使用哪种方式转换字节码。如下:
NUM_CURRENT
NUM_PREVIOUS
NUM_USAGE
TOTAL_MONEY
这4列使用double类型展示,其他的使用string类型展示。修改以下代码,对值的字段进行判断。
//获取值
String value = null;
//NUM_CURRENT
//NUM_PREVIOUS
//NUM_USAGE
//TOTAL_MONEY
if ("NUM_CURRENT".equals(columnName)
||"NUM_PREVIOUS".equals(columnName)
||"NUM_USAGE".equals(columnName)
||"TOTAL_MONEY".equals(columnName)){
value = Bytes.toDouble(cell.getValueArray())/100000000+"亿";
}else{
value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
}
2Hbase高可用
2.1 高可用介绍
base集群只有一个Master,一旦master出现故障,hbase将不再可用(创建表、删除表、查看表的元数据)

2.1搭建Hbase高可用
在hbase的conf中创建backup-masters文件
## 进入目录
cd /export/server/hbase-2.1.0/conf
# 创建文件
touch backup-masters

将node2.btks.cn和node3.btks.cn 添加到文件中
vim backup-master
添加内容
node2.btks.cn
node3.btks.cn
分发到其他节点
scp backup-masters root@node2:$PWD
scp backup-masters root@node3:$PWD
重启hbase
stop-hbase.sh
start-hbase.sh
查看结果




2.3实验切换
当前状态


杀死节点1的Hmaster的进程

