开发笔记 – Spring Boot集成HBase

最近在重新整理搜书吧(一个做图书比价的平台)的系统架构,目前图书产品数量超过了200万条。各种数据加起来超过40G了,使用Mysql数据库存储服务器吃不消,于是考虑使用HBase存储大部分数据。


一、摘要

以前搜书吧的数据量比较小,使用数据库+静态文件存储的方式就可以搞定,主要有2个系统组成:网站前端+后台服务。事先把图书详情等一些固定内容生成html静态文件和前端的其他静态文件打包部署,动态变化的数据使用js通过REST接口获取。后台服务系统主要处理业务逻辑以及提供REST接口调用(为节省资源,很多其他个人项目的后台服务也运行在这个系统上)。现在图书数量增加到了200多万条,数据量比原来大很多,使用一台服务器不仅硬盘不足,Mysql存储内容太多,内存资源也不够用。于是想借鉴微服务的解决方案,使用Spring Boot+HBase搭建单独的服务,作为一个小型的数据中心,可为不同的项目存储数据。

二、软件

  • Ubuntu 16.04
  • IntelliJ IDEA  2018.01
  • JDK 1.8.0
  • Hadoop 2.8.5
  • HBase 2.1.0
  • spring-data-hadoop 2.5.0
  • hbase-client 1.4.4

三、HBase介绍

Hbase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用Hbase技术可在廉价PC Server上搭建起大规模集群。它是一个可以随机访问的存储和检索数据的平台,允许动态的灵活的数据模型。

HBase的服务器体系结构遵从简单的主从服务器架构,它由HRegion服务器(HRegion Server)和HMaster 服务器组成。HMaster负责管理所有的HRegion服务器,而HBase中的所有的服务器都是通过zookeeper来进行协调并处理HBase服务器运行期间可能遇到的错误。HBase Master并不存储HBase中的任何数据.HBase逻辑上的表可能会被划分成多个HRegion,然后存储到HRegion服务器中,HBase Master服务器中存储的是从数据到HRegion 服务器的映射。

四、SSH/HOST等安装配置

4.1 修改主机名

由于我使用的是阿里云的ECS,主机名有点长,先修改主机名称。输入一下命令,把名称修改自己想要的即可,比如我的修改为luoxudong02,修改完以后重启系统。

vim /etc/hostname

4.2 修改host

vim /etc/hosts

增加一条主机映射记录,其中前面为IP,后面为主机名称。IP地址需要是主机内网IP(可以使用ifconfig查看),阿里云ECS有一个公网IP,有一个私有IP,需要填写私有IP。

4.3 创建用户

为了方便管理,创建一个hadoop用户,如果是完全分布式的话要创建一个用户组,因为master和slaves要求用户和组要完全一样。

创建hadoop用户,并使用/bin/bash作为shell

sudo useradd -m hadoop -s /bin/bash

为hadoop用户设置密码

sudo passwd hadoop

为了后续操作方便,增加管理员权限

sudo adduser hadoop sudo

后续的操作都切换到hadoop用户下执行。

4.4 配置SSH免密登录

如果没有安装openssh-server则先安装

sudo apt-get install openssh-server

先创建秘钥

ssh-keygen -t rsa

一直按回车即可,完成以后把新创建的秘钥追加到autorized_keys中,该文件没有的话会自动创建。

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

五、JDK安装配置

5.1 下载JDK

下载JDK1.8,从官网下载对应环境的安装包:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

下载完成以后解压到指定位置

tar -zxvf jdk-8u191-linux-x64.tar.gz -C ~/local

配置JDK环境变量,打开~/.bashrc文件,

vim ~/.bashrc

在文件最后添加以下代码。

export JAVA_HOME=~/local/jdk1.8.0_191
export CLASSPAT=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH

使配置生效

source ~/.bashrc

六、Hadoop+HBase环境搭建

由于我只有一台空闲的服务器,所以目前我只是搭建了伪分布式环境,后续再根据需要扩展。

下载安装包时要选择对应的版本号,要不然会容易采坑。大家可以查看官方文档,里面有介绍详细的配置要求。

6.1 环境要求

下面的表格是JDK版本的要求,其中JDK8是支持所有版本

以下是各版本的Hadoop和HBase对应表,从表格可以看出,支持的最新版本是Hadoop-2.83+和HBase-2.1.x。我选择的是Hadoop2.8.5HBase-2.1.0

6.2 安装配置Hadoop

6.2.1 下载安装包

从官网下载Hadoop安装包,我安装的版本是2.8.5,下载地址:https://www-eu.apache.org/dist/hadoop/common/hadoop-2.8.5/hadoop-2.8.5.tar.gz。下载后解压到指定目录。

tar -zxvf hadoop-2.8.5.tar.gz -C ~/local

解压以后Hadoop目录名称带版本号,大家可以重命名,去掉版本号,方便维护。

6.2.2 配置环境变量

跟JDK配置类似,打开bashrc文件,在后面添加一下代码

export HADOOP_HOME=~/local/hadoop
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export YARN_CONF_DIR=$HADOOP_HOME/etc/hadoop
export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH

6.2.3 设置Hadoop配置文件

进入${HADOOP_HOME}/etc/hadoop目录,修改一下几个文件

  • hadoop-env.sh
  • core-site.xml
  • hdfs-site.xml
  • yarn-site.xml
  • mapred-site.xml(mapred-site.xml.template重命名)
  • slaves

1) hadoop-env.sh文件

如果文件中没有配置JAVA_HOME,如果存在以下代码则不需要修改。

# The java implementation to use.
export JAVA_HOME=${JAVA_HOME}

否则需要在文件最后配置JDK路径

export JAVA_HOME=~/local/jdk1.8.0_191(如果文件中已经存在export JAVA_HOME=${JAVA_HOME}就可以不需要)

2) core-site.xml文件

在configuration节点中加入以下代码:

<property>
    <name>hadoop.tmp.dir</name>
    <value>/home/hadoop/local/hadoop/tmp</value>
    <description>Abase for other temporary directories.</description>
</property>
<property>
    <name>fs.defaultFS</name>
    <value>hdfs://luoxudong02:9000</value>
</property>

hadoop.tmp.dir是HDFS与本地磁盘的临时文件,是文件系统依赖的基本配置,很多配置路径都依赖它,它的默认位置在/tmp/{$user}下面。需要指定一个持久化路径,否则系统tmp被自动清掉以后会出fs.defaultFS是默认文件系统的名称,通常是NameNode的hostname:port,其中luoxudong02是主机名称,9000是默认端口号

3) hdfs-site.xml文件

在configuration节点中加入以下代码:

<property>
    <name>dfs.replication</name>
    <value>1</value>
</property>
<property>
    <name>dfs.namenode.name.dir</name>
    <value>/home/hadoop/local/hadoop/dfsdata/name</value>
</property>
<property>
    <name>dfs.datanode.data.dir</name>
    <value>/home/hadoop/local/hadoop/dfsdata/data</value>
</property>
<property>
    <name>dfs.permissions</name>
    <value>false</value>
</property>

dfs.replication 是指在文件被下入的时候,每一块将要被复制多少份,默认是3,单主机设置1就可以了

dfs.namenode.name.dir 是NameNode元数据存放位置,默认存放在${hadoop.tmp.dir}/dfs/name目录。

dfs.datanode.data.dir 是DataNode在本地磁盘存放block的位置,可以使用逗号分隔的目录列表,默认存放在${hadoop.tmp.dir}/dfs/data目录。

dfs.permissions 标识是否要检查权限,默认是true,设置false则允许每个人都可以存取文件。

4) yarn-site.xml文件

在configuration节点中加入以下代码:

<property>
    <name>yarn.resourcemanager.hostname</name>
    <value>luoxudong02</value>
</property>
<property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
</property>

yarn.resourcemaneger.hostname 指定主机名称

5) mapred-site.xml文件

这个文件本身是不存在,需要把目录中的mapred-site.xml.template重命名,在其中的configuration节点加入以下代码:

<property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
</property>

6) slaves文件

把文件内容改成主机名称,如:luoxudong02

这样配置基本就完成了,接下来启动hadoop

第一次启动之前需要格式化HDFS(只需要执行一次,后面启动Hadoop服务器不需要执行格式化命令)

bin/hdfs namenode -format

启动服务

sbin/start-dfs.sh
sbin/start-yarn.sh

然后输入jps命令,如果启动成功将会看到以下服务

6.3 安装配置HBase

6.3.1 下载安装包

从官网下载HBase安装包,我安装的是HBase-2.1.0,官网下载地址:http://archive.apache.org/dist/hbase/2.1.0/hbase-2.1.0-bin.tar.gz。下载完成后解压到指定目录

tar -zxvf hbase-2.1.0-bin.tar.gz -C ~/local

把解压后的目录名称修改为HBase,去掉版本号。

6.3.2 配置环境变量

跟JDK配置类似,打开bashrc文件,在后面添加一下代码

export HBASE_HOME=~/local/hbase
export PATH=$HBASE_HOME/bin:$PATH

重新整理JDK、Hadoop和HBase的环境变量后如下

export JAVA_HOME=~/local/jdk1.8.0_191
export CLASSPAT=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib
export HADOOP_HOME=~/local/hadoop
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export YARN_CONF_DIR=$HADOOP_HOME/etc/hadoop
export HBASE_HOME=~/local/hbase
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HBASE_HOME/bin:$PATH

6.3.3 设置HBase配置文件

HBase配置稍微简单一些,只需要配置3个文件

  • hbase-env.sh
  • hbase-site.xml
  • regionservers

1) hbase-env.sh文件

修改两个地方

export JAVA_HOME=~/local/jdk1.8.0_191
export HBASE_MANAGES_ZK=true

第一行是关联JDK路径,第二个是指定使用HBase自带的ZK。

2) hbase-site.xml文件

在configuration节点中增加以下代码:

<property>
    <name>hbase.zookeeper.quorum</name>
    <value>luoxudong02</value>
</property>
 <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/home/hadoop/local/hbase/zkdata</value>
</property>
<property>
    <name>hbase.tmp.dir</name>
    <value>/home/hadoop/local/hbase/tmp</value>
</property>
<property>
    <name>hbase.rootdir</name>
    <value>hdfs://luoxudong02:9000/hbase</value>
</property>
<property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
</property>

hbase.zookeeper.quorum是集群的地址列表,使用逗号分割开,由于我们使用的是伪分布式,只有一台主机,设置成主机名称就可以。

hbase.zookeeper.property.dataDir是快照的存储位置

hbase.tmp.dir是本地文件系统的临时文件夹

hbase.rootdir是regionserver的共享目录,用来持久化HBase

hbase.cluster.distributed指运行模式,false表示单机模式,true标识分布式模式

3) 修改regionservers文件

把内容修改成主机名称,如:luoxudong02

这样基本配置完成,接下来启动服务

bin/start-hbase.sh

这里有一个小问题,启动的时候提示slg4j有多个,那是因为hadoop安装包下和hbase安装包下都存在,网上有人说删除hbase安装包下的slf4j-log412文件,我试了下删除会包其他错误,导致hbase无法正常启动,暂时没有找到比较好的解决办法。由于不影响使用,暂时不管。

Hadoop和HBase成功启动后会有以下服务

大家在启动后可能发现HMaster服务或者HRegionServer服务没有。通过查看log/hbase-hadoop-master-主机名.log中的日志发下出现类似以下错误:

java.lang.NoClassDefFoundError: org/apache/htrace/SamplerBuilder
    at org.apache.hadoop.hdfs.DFSClient.<init>(DFSClient.java:635)
    at org.apache.hadoop.hdfs.DFSClient.<init>(DFSClient.java:619)
    at org.apache.hadoop.hdfs.DistributedFileSystem.initialize(DistributedFileSystem.java:149)
    at org.apache.hadoop.fs.FileSystem.createFileSystem(FileSystem.java:2669)
    at org.apache.hadoop.fs.FileSystem.access$200(FileSystem.java:94)
    at org.apache.hadoop.fs.FileSystem$Cache.getInternal(FileSystem.java:2703)
    at org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:2685)
    at org.apache.hadoop.fs.FileSystem.get(FileSystem.java:373)
    at org.apache.hadoop.fs.Path.getFileSystem(Path.java:295)
    at org.apache.hadoop.hbase.util.CommonFSUtils.getRootDir(CommonFSUtils.java:358)
    at org.apache.hadoop.hbase.util.CommonFSUtils.isValidWALRootDir(CommonFSUtils.java:407)
    at org.apache.hadoop.hbase.util.CommonFSUtils.getWALRootDir(CommonFSUtils.java:383)
    at org.apache.hadoop.hbase.regionserver.HRegionServer.initializeFileSystem(HRegionServer.java:691)
    at org.apache.hadoop.hbase.regionserver.HRegionServer.<init>(HRegionServer.java:600)
    at org.apache.hadoop.hbase.master.HMaster.<init>(HMaster.java:484)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.apache.hadoop.hbase.master.HMaster.constructMaster(HMaster.java:2965)
    at org.apache.hadoop.hbase.master.HMasterCommandLine.startMaster(HMasterCommandLine.java:236)
    at org.apache.hadoop.hbase.master.HMasterCommandLine.run(HMasterCommandLine.java:140)
    at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:70)
    at org.apache.hadoop.hbase.util.ServerCommandLine.doMain(ServerCommandLine.java:149)
    at org.apache.hadoop.hbase.master.HMaster.main(HMaster.java:2983)

这时需要把HBase下的lib/client-facing-thirdparty/htrace-core-xxx.jar包拷贝到lib下

cp lib/client-facing-thirdparty/htrace-core-3.1.0-incubating.jar lib/

然后再重新启动HBase就可以了,如果发现还是启动失败,可以查看日志,针对具体问题google一下。

启动成功以后可以通过HBase shell命令测试一下。

Hadoop和HBase的配置就完成了,接下来通过Spring Boot创建一个Web服务来访问HBase。

6.4 Spring Boot配置

Spring Boot配置比较简单

6.4.1 HOST配置

打开本地hosts文件,添加HBase主机映射,跟Hadoop服务器配置的host类似,有一点不同就是IP地址需要是Master主机的外网IP地址。

6.4.2 添加依赖包

我是使用maven来管理jar包,在pom.xml中增加以下依赖包

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-hadoop-boot</artifactId>
    <version>2.5.0.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>javax.servlet</groupId>
	    <artifactId>servlet-api</artifactId>
	</exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-hadoop</artifactId>
    <version>2.5.0.RELEASE</version>
    <exclusions>
        <exclusion>
	    <groupId>org.slf4j</groupId>
	    <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
	    <groupId>log4j</groupId>
	    <artifactId>log4j</artifactId>
        </exclusion>
        <exclusion>
            <groupId>javax.servlet</groupId>
	    <artifactId>servlet-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.4.4</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
	    <artifactId>slf4j-log4j12</artifactId>
	</exclusion>
	<exclusion>
	    <groupId>log4j</groupId>
	    <artifactId>log4j</artifactId>
	</exclusion>
        <exclusion>
	    <groupId>javax.servlet</groupId>
	    <artifactId>servlet-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-1.2-api</artifactId>
    <version>2.11.0</version>
</dependency>

6.4.3 新建HBaseProperties.java

package com.luoxudong.bigdata.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Map;

@ConfigurationProperties(prefix = "hbase")
public class HBaseProperties {

    private Map<String, String> config;

    public Map<String, String> getConfig() {
        return config;
    }

    public void setConfig(Map<String, String> config) {
        this.config = config;
    }
}

6.4.4 新建HBaseConfig.java

package com.luoxudong.bigdata.config;

import java.util.Map;
import java.util.Set;

import org.apache.hadoop.hbase.HBaseConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.hadoop.hbase.HbaseTemplate;

@Configuration
@EnableConfigurationProperties(HBaseProperties.class)
public class HBaseConfig {

    private final HBaseProperties properties;

    public HBaseConfig(HBaseProperties properties) {
        this.properties = properties;
    }

    @Bean
    public HbaseTemplate hbaseTemplate() {
        HbaseTemplate hbaseTemplate = new HbaseTemplate();
        hbaseTemplate.setConfiguration(configuration());
        hbaseTemplate.setAutoFlush(true);
        return hbaseTemplate;
    }

    public org.apache.hadoop.conf.Configuration configuration() {

        org.apache.hadoop.conf.Configuration configuration = HBaseConfiguration.create();

        Map<String, String> config = properties.getConfig();
        Set<String> keySet = config.keySet();
        for (String key : keySet) {
            configuration.set(key, config.get(key));
        }

        return configuration;
    }
}

6.4.5 application.yml配置

hbase:
  config:
    hbase.zookeeper.quorum: luoxudong02
    hbase.zookeeper.property.clientPort: 2181

6.4.6 使用

配置基本完成,然后再需要的地方应用HaseTemplate对象对hbase进行操作。

@Autowired
private HbaseTemplate hbaseTemplate;

七、注意事项

也许按照上面的配置完成,你发现客户端没法访问HBase。一般无法访问是因为客户端或者服务器配置出错,可以通过查看前端控制台日志和HBase/log下的日志可以发现错误原因。有一个在调试过程中发现客户端和后台都没有报错,就是调用hbaseTemplate操作hbase时不继续往下执行,找了半天才找到问题。因为我使用的是案例云ECS,安全组默认是不允许22以外的端口访问,默认情况下前端需要访问HBase的2181和16020端口,需要把这两个端口放开。如果服务端单独配置了防火墙,也需要放开这两个端口。以下是HBase涉及到的端口表格,大家可以根据具体情况放开相关端口。

八、其他

以上哪里写的不对或者有待改进,欢迎大家提意见,谢谢!

转载请注明出处:http://www.luoxudong.com/?p=505

发表评论

电子邮件地址不会被公开。