King's Studio

使用Docker部署HBase并使用Java API连接

字数统计: 2.7k阅读时长: 11 min
2019/12/23 Share

HBase的原型是Google的BigTable论文,受到了该论文思想的启发,目前作为Hadoop的子项目来开发维护,用于支持结构化的数据存储。HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。HBase是Google Bigtable的开源实现,但是也有很多不同之处。HBase利用Hadoop HDFS作为其文件存储系统,它利用Hadoop MapReduce来处理HBase中的海量数据,使用Zookeeper作为协同服务。

首先学习HBase需要搭建对应的环境,这里为了不在环境上浪费过多的时间,我采用了Docker搭建HBase的方式,使用其内置的Zookeeper以及Hadoop实现数据的操作。Docker是一个非常强大的工具,它能够非常方便的帮我们搭建出想要的各种环境,我使用Docker也有快一年的时间,也处于一个学习的状态,后期我会专门写一篇关于Docker使用的文章进行总结。今天我们来介绍一下HBase的搭建以及如何使用Java API进行访问。

使用Docker搭建HBase的环境

Docker的安装不是本篇的重点,后面会专门写一篇关于Docker使用的总结。至于Docker能安装在什么机器上,无论你是本地PC还是远程Linux服务器,Docker都能够实现安装。我使用的是Mac的环境,安装Docker Desktop即可。首先我们需要从镜像仓库中拉取HBase的镜像,使用如下的命令:

1
docker search hbase

我们能够看到有以下的仓库镜像:

仓库镜像

我们选择harisekhon/hbase的1.3的版本,因为是测试成功过的,使用以下命令拉取镜像:

1
docker pull harisekhon/hbase:1.3

然后我们使用docker run的命令启动HBase容器,这里的命令非常重要,和设置端口号有关,与之后能不能使用Java API连接到HBase有很大的关系:

1
docker run -d -h myhbase -p 2181:2181 -p 9090:9090 -p 9095:9095 -p 16000:16000 -p 16010:16010 -p 16201:16201 -p 16301:16301 --name hbase1.3 harisekhon/hbase:1.3
1
2
3
4
-d 表示后台运行
-h 表示设置docker容器的虚拟主机名
-p 表示端口映射,冒号前是主机端口号,冒号后是外部映射端口号,其中2181是Zookeeper的端口,16010是HBase的端口
--name 表示给docker容器取名

然后我们使用下面的命令查看容器启动状态:

1
docker ps

docker容器状态

首先可以使用下面的命令进入容器内部,测试HBase能否正常使用:

1
2
3
docker exec -it hbase1.3 bash #进入容器的bash
hbase shell #进入hbase命令行
list #查看表列表

能出现以下的结果则说明容器启动成功:

hbase命令行

或者可以直接访问本地的16010端口查看web页面:

web页面

至此,本地的Docker搭建HBase的环境就结束了,后面我们就能使用API对HBase进行一系列的数据库操作。

HBase的概述

HBase的架构

首先我们先看一下HBase的架构图:

HBase架构

从图中可以看出Hbase是由Client、Zookeeper、Master、HRegionServer、HDFS等几个组件组成,下面来介绍一下几个组件的相关功能:

1.Client

​ Client包含了访问HBase的接口,另外Client还维护了对应的cache来加速HBase的访问,比如cache的META元数据的信息。

2.Zookeeper

​ HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。具体工作如下:

​ 通过Zoopkeeper来保证集群中只有1个master在运行,如果master异常,会通过竞争机制产生新的master提供服务;通过Zoopkeeper来监控RegionServer的状态,当RegionSevrer有异常的时候,通过回调的形式通知Master RegionServer上下线的信息;通过Zoopkeeper存储元数据的统一入口地址。

3.HMaster(NameNode)

​ master节点的主要职责如下:为RegionServer分配Region;维护整个集群的负载均衡;维护集群的元数据信息;发现失效的Region,并将失效的Region分配到正常的RegionServer上;当RegionSever失效的时候,协调对应Hlog的拆分。

4.HRegionServer(DataNode)

​ HRegionServer直接对接用户的读写请求,是真正”干活”的节点。它的功能概括如下:管理master为其分配的Region;处理来自客户端的读写请求;负责和底层HDFS的交互,存储数据到HDFS;负责Region变大以后的拆分;负责Storefile的合并工作。

5.HDFS

​ HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用(Hlog存储在HDFS)的支持,具体功能概括如下:提供元数据和表数据的底层分布式存储服务;数据多副本,保证的高可靠和高可用性。

附上HBase的读写数据原理图:

hbase读写原理

HBase中的角色

1.HMaster

​ 监控RegionServer;处理RegionServer故障转移;处理元数据的变更;处理region的分配或转移;在空闲时间进行数据的负载均衡;通过Zookeeper发布自己的位置给客户端。

2.RegionServer

​ 负责存储HBase的实际数据;处理分配给它的Region;刷新缓存到HDFS;维护Hlog;执行压缩;负责处理Region分区。

HBase中的其他组件

1.Write-Ahead logs

​ HBase的修改记录,当对HBase读写数据的时候,数据不是直接写进磁盘,它会在内存中保留一段时间(时间以及数据量阈值可以设定)。但把数据保存在内存中可能有更高的概率引起数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入内存中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。

2.Region

​ HBase表的分区,HBase表会根据RowKey值被切分成不同的region存储在RegionServer中,在一个RegionServer中可以有多个不同的region。

3.Store

​ HFile存储在Store中,一个Store对应HBase表中的一个列族(列簇,Column Family)。

4.MemStore

​ 顾名思义,就是内存存储,位于内存中,用来保存当前的数据操作,所以当数据保存在WAL中之后,RegsionServer会在内存中存储键值对。

5.HFile

​ 这是在磁盘上保存原始数据的实际的物理文件,是实际的存储文件。StoreFile是以Hfile的形式存储在HDFS的。

使用API访问HBase

HBase也支持shell命令进行操作数据库,进入到docker容器中然后输入hbase shell命令回车,即可进入到命令行模式,但通常我们更多的是使用API进行操作数据库,因为很多时候我们写的MapReduce就需要放在Hadoop上运行,所以掌握API操作HBase是非常重要的。

导入Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>

封装工具类

测试API的过程中发现每次连接HBase都需要配置连接信息,还有关闭资源这些操作,就想到将他们封装成一个工具类,其中还包含了生成分区键和分区号的方法,采用了和Map存放类似的生成hash码的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.jinqi.util;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

/**
* HBase操作工具类
*/
public class HBaseUtil {

//保证线程安全,从本地线程缓存中获取连接
private static ThreadLocal<Connection> connHolder = new ThreadLocal<Connection>();

// private static Connection conn = null;

private HBaseUtil() {
}

/**
* 获取连接对象
* @return
*/
public static void makeHBaseConnection(String hostName,String post) throws IOException {
// Configuration conf = HBaseConfiguration.create();
// conf.set("hbase.zookeeper.quorum", hostName);
// conf.set("hbase.zookeeper.property.clientPort", post);
// conn = ConnectionFactory.createConnection(conf);

Connection conn = connHolder.get();
if (conn == null){
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", hostName);
conf.set("hbase.zookeeper.property.clientPort", post);
conn = ConnectionFactory.createConnection(conf);
connHolder.set(conn);
}

}

/**
* 生成分区键,例如3个分区需要两个分区键
* @param regionCount
* @return
*/
public static byte[][] genRegionKeys(int regionCount){
byte[][] bs = new byte[regionCount-1][];

for (int i = 0; i < regionCount - 1; i++) {
bs[i] = Bytes.toBytes(i+"|");
}

return bs;
}

/**
* 生成分区号
* @param rowKey 根据rowKey生成分区号
* @param regionCount 分区数量
*/
public static String genRegionNum(String rowKey,int regionCount){
int regionNum;
int hash = rowKey.hashCode();
if (regionCount > 0 && (regionCount & (regionCount -1)) == 0){
//2的n次方
regionNum = hash & (regionCount - 1);
}else {
regionNum = hash & (regionCount);
}

return regionNum + "_" + rowKey;
}


/**
* 插入数据
* @param tableName
* @param rowKey
* @param family
* @param value
*/
public static void insertData(String tableName,String rowKey,String family,String column,String value) throws IOException {
Connection conn = connHolder.get();
Table table = conn.getTable(TableName.valueOf(tableName));

Put put = new Put(Bytes.toBytes(rowKey));
put.addColumn(Bytes.toBytes(family),Bytes.toBytes(column),Bytes.toBytes(value));

table.put(put);
table.close();
}

/**
* 关闭连接
*/
public static void close() throws IOException {
Connection conn = connHolder.get();
if (conn != null){
conn.close();
connHolder.remove();
}
}
}

连接、插入数据测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jinqi.hbase;

import com.jinqi.util.HBaseUtil;
import java.io.IOException;

/**
* 测试HBase API
*/
public class TestHbaseAPI3 {

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

//创建连接对象
HBaseUtil.makeHBaseConnection("myhbase", "2181");

HBaseUtil.insertData("ross:student", "1003", "info", "name", "jinqi");

//关闭连接
HBaseUtil.close();
}
}

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.jinqi.hbase;

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.filter.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

/**
* 查询
*/
public class TestHbaseAPI4 {

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

Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "myhbase");
conf.set("hbase.zookeeper.property.clientPort", "2181");

Connection connection = ConnectionFactory.createConnection(conf);

TableName tableName = TableName.valueOf("student");

Table table = connection.getTable(tableName);
Scan scan = new Scan();
// scan.addFamily(Bytes.toBytes("info"));
BinaryComparator bc = new BinaryComparator(Bytes.toBytes("1002"));
RegexStringComparator rsc = new RegexStringComparator("^\\d{5}$");
// Filter filter = new RowFilter(CompareFilter.CompareOp.GREATER_OR_EQUAL,bc);
Filter filter = new RowFilter(CompareFilter.CompareOp.EQUAL,rsc);

RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL,bc);

//MUST_PASS_ALL:必须满足所有条件
//MUST_PASS_ONE:只需要满足一个即可,能显示所有满足条件的数据
//过滤器的集合
FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ONE);

list.addFilter(filter);
list.addFilter(rowFilter);

//扫描时,增加过滤器,即添加查询规则
//过滤,每条数据都会过滤,性能较低
scan.setFilter(list);

ResultScanner scanner = table.getScanner(scan);

for (Result result : scanner) {
//展示数据
for (Cell cell : result.rawCells()) {
System.out.println("rowKey=" + Bytes.toString(CellUtil.cloneRow(cell)));
System.out.println("family=" + Bytes.toString(CellUtil.cloneFamily(cell)));
System.out.println("column=" + Bytes.toString(CellUtil.cloneQualifier(cell)));
System.out.println("value=" + Bytes.toString(CellUtil.cloneValue(cell)));
}
}

table.close();
connection.close();

}
}

简单API的操作已经介绍完了,但通过HBase的相关Java API,我们可以实现伴随HBase操作的MapReduce过程,比如使用MapReduce将数据从本地文件系统导入到HBase的表中,比如我们从HBase中读取一些原始数据后使用MapReduce做数据分析,这些内容我们会在后面的文章中总结。

原文作者:金奇

原文链接:https://www.rossontheway.com/2019/12/23/使用Docker部署HBase并使用Java-API连接/

发表日期:December 23rd 2019, 12:00:00 am

更新日期:December 25th 2019, 5:21:42 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可,除特别声明外,转载请注明出处!

CATALOG
  1. 1. 使用Docker搭建HBase的环境
  2. 2. HBase的概述
    1. 2.1. HBase的架构
    2. 2.2. HBase中的角色
    3. 2.3. HBase中的其他组件
  3. 3. 使用API访问HBase
    1. 3.1. 导入Maven依赖
    2. 3.2. 封装工具类
    3. 3.3. 查询数据