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

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的镜像,使用如下的命令:

docker search hbase

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

使用Docker部署HBase并使用Java-API连接_第1张图片

我们选择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

docker

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

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

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

使用Docker部署HBase并使用Java-API连接_第2张图片

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

使用Docker部署HBase并使用Java-API连接_第3张图片

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

HBase的概述

HBase的架构

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

使用Docker部署HBase并使用Java-API连接_第4张图片

从图中可以看出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的读写数据原理图:

使用Docker部署HBase并使用Java-API连接_第5张图片

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依赖

<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做数据分析,这些内容我们会在后面的文章中总结。

你可能感兴趣的:(HBase,docker,hbase)