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的安装不是本篇的重点,后面会专门写一篇关于Docker使用的总结。至于Docker能安装在什么机器上,无论你是本地PC还是远程Linux服务器,Docker都能够实现安装。我使用的是Mac的环境,安装Docker Desktop即可。首先我们需要从镜像仓库中拉取HBase的镜像,使用如下的命令:
docker search hbase
我们能够看到有以下的仓库镜像:
我们选择harisekhon/hbase的1.3的版本,因为是测试成功过的,使用以下命令拉取镜像:
docker pull harisekhon/hbase:1.3
然后我们使用docker run的命令启动HBase容器,这里的命令非常重要,和设置端口号有关,与之后能不能使用Java API连接到HBase有很大的关系:
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
-d 表示后台运行
-h 表示设置docker容器的虚拟主机名
-p 表示端口映射,冒号前是主机端口号,冒号后是外部映射端口号,其中2181是Zookeeper的端口,16010是HBase的端口
--name 表示给docker容器取名
然后我们使用下面的命令查看容器启动状态:
docker ps
首先可以使用下面的命令进入容器内部,测试HBase能否正常使用:
docker exec -it hbase1.3 bash #进入容器的bash
hbase shell #进入hbase命令行
list #查看表列表
能出现以下的结果则说明容器启动成功:
或者可以直接访问本地的16010端口查看web页面:
至此,本地的Docker搭建HBase的环境就结束了,后面我们就能使用API对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的读写数据原理图:
1.HMaster
监控RegionServer;处理RegionServer故障转移;处理元数据的变更;处理region的分配或转移;在空闲时间进行数据的负载均衡;通过Zookeeper发布自己的位置给客户端。
2.RegionServer
负责存储HBase的实际数据;处理分配给它的Region;刷新缓存到HDFS;维护Hlog;执行压缩;负责处理Region分区。
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的。
HBase也支持shell命令进行操作数据库,进入到docker容器中然后输入hbase shell命令回车,即可进入到命令行模式,但通常我们更多的是使用API进行操作数据库,因为很多时候我们写的MapReduce就需要放在Hadoop上运行,所以掌握API操作HBase是非常重要的。
<dependencies>
<dependency>
<groupId>org.apache.hbasegroupId>
<artifactId>hbase-serverartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>org.apache.hbasegroupId>
<artifactId>hbase-clientartifactId>
<version>1.3.2version>
dependency>
dependencies>
测试API的过程中发现每次连接HBase都需要配置连接信息,还有关闭资源这些操作,就想到将他们封装成一个工具类,其中还包含了生成分区键和分区号的方法,采用了和Map存放类似的生成hash码的方式。
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();
}
}
}
连接、插入数据测试:
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();
}
}
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做数据分析,这些内容我们会在后面的文章中总结。