HBase探索篇 _ OpenJdk15编译并部署CDH版HBase

[toc]

1. 前言

为探索JDK15的ZGC特性在HBase中的表现力是否犹如传言中的那么优秀,我用AdoptOpenJDK15重新编译了社区版本的hbase-1.4.8,接着完成了编译之后,HBase完全分布式的部署和功能上的测试,并把整个编译的流程与解决过的问题一一分享在了之前的文章中。

但是我们线上在用的HBase的版本是cdh6.3.2-hbase2.1.0,CDH组装的大数据组件与原生版本相比,还是略有差异。社区版HBase的编译只是用来试水,如果线上想要体验ZGC的强悍特性,还需要对CDH版的HBase做做文章。

这篇文章将以cdh6.3.1-hbase2.1.0(测试环境)和cdh6.3.2-hbase2.1.0(线上环境)两个版本为例,记录AdoptOpenJDK15编译CDH-HBase的完整过程,并针对期间解决问题的思维过程和采取的方法也做了比较详细的阐述。

且在编译成功之后,会在CDH测试集群中替换原有HBase的jar包,并重点修改Java相关的参数,然后重启集群,测试HBase服务的可用性,记录详细的部署流程,排查每一个遇到的坑。

之后的计划是,替换CDH中原有的HBase的jar包,修改HBase集群的启动参数,成功启动CDH上的HBase服务,对ZGC参数进行设置,对HBase做基准性能压测(刚好我们腾出来了三台线上节点),这将在下篇文章中继续为大家分享。

2. 准备工作

  • cdh6.3.2-hbase2.1.0源码下载,请移步至GitHub Cloudera的官方仓库,选择你喜欢的版本
  • AdoptOpenJDK15 请自行搜索该JDK的下载页面,下载对应操作系统的安装包
  • maven-3.5.0 ,maven配置文件推荐的配置,请参考我的历史文章
  • IDEA-2020.3 付费版更好,社区版也够用,下载最新版的IDEA,主要为了可以在上面选jdk15
  • Mac OS,坑会少很多

3. 项目配置

3.1 项目导入

导入项目到IDEA中,导入完成之后,它是一个十分标准的maven项目,如下图所示:

project

第一次加载项目时会下载大量的第三方依赖,请耐心等候,待依赖下载完成之后,我们不做任何修改,尝试是否可以用jdk1.8直接编译和打包该项目。

# 这里我直接运行打tar.gz包命令,Hadoop的版本默认选择的是3.0
mvn clean package -DskipTests assembly:single
maven-error1

编译过程中如果遇到如上异常,在项目根路径的pom.xml中加入如下配置:

 <repositories>
    <repository>
      <id>cloudera</id>
      <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

上述mvn命令运行完成之后,就可以在hbase-assembly模块的target目录中找到hbase-2.1.0-cdh6.3.2-bin.tar.gz

build-success

tar包的位置:

tar-position

3.2 编译配置

我们下载的源码在JDK8的环境中可以正常编译和打包,这个版本的HBase基于社区版的2.1.0,大佬们为兼容更高版本的JDK已经为我们做了大量的工作,所以我们只需稍加改动,就可以使之在JDK15的环境中顺利通过编译。

首先来修改编译插件maven.compiler的配置:

<!-- 旧配置 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>${maven.compiler.version}</version>
    <configuration>
        <source>${compileSource}</source>
        <target>${compileSource}</target>
        <showWarnings>true</showWarnings>
        <showDeprecation>false</showDeprecation>
        <useIncrementalCompilation>false</useIncrementalCompilation>
        <compilerArgument>-Xlint:-options</compilerArgument>
     </configuration>
</plugin>
<!-- 新配置 -->
<plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-compiler-plugin</artifactId>
     <version>${maven.compiler.version}</version>
     <configuration>
            <source>${compileSource}</source>
            <target>${compileSource}</target>
            <showWarnings>true</showWarnings>
            <showDeprecation>false</showDeprecation>
            <useIncrementalCompilation>false</useIncrementalCompilation>
            <compilerArgs>
              <arg>--add-exports=java.base/jdk.internal.access=ALL-UNNAMED</arg>
              <arg>--add-exports=java.base/jdk.internal=ALL-UNNAMED</arg>
              <arg>--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED</arg>
              <arg>--add-exports=java.base/sun.security.pkcs=ALL-UNNAMED</arg>
              <arg>--add-exports=java.base/sun.nio.ch=ALL-UNNAMED</arg>
              <!-- --add-opens 的配置可以不需要-->
              <arg>--add-opens=java.base/java.nio=ALL-UNNAMED</arg>
              <arg>--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED</arg>
            </compilerArgs>
      </configuration>
</plugin>

compileSource修改为15

<!-- <compileSource>1.8</compileSource>-->
<compileSource>15</compileSource>

更新下pom配置,运行下mvn编译命令:

# 1. 切换本地jdk版本为15
jdk15
# 2. 运行mvn编译命令
mvn clean package -DskipTests

3.3 程序包javax.annotation不存在

annotation-not-found

项目根pom文件中加入如下依赖,注意加在dependencies标签下面:

<dependencies>
   <dependency>
       <groupId>javax.annotation</groupId>
       <artifactId>javax.annotation-api</artifactId>
       <version>1.3.1</version>
    </dependency>   
</dependencies>

3.4 maven-shade-plugin升级版本

maven-shade-plugin

maven-shade-plugin版本升级为3.2.4

3.5 程序包javax.xml.ws.http不存在

http-error

pom中加入以下依赖:

<dependency>
      <groupId>jakarta.xml.ws</groupId>
      <artifactId>jakarta.xml.ws-api</artifactId>
      <version>2.3.3</version>
</dependency>

3.6 Some Enforcer rules have failed.

enforcer-error

maven-enforcer-plugin版本升级至3.0.0-M3。

版本升级之后发现报错依旧。往上继续排查报错信息,可能是license的受检异常导致,之前在文章中亦有提及,请参考解决。你也可以在mvn编译命令的后面增加-X参数,来查看mvn编译命令执行时更详细的DEBUG日志信息。

3.7 hbase-spark模块编译报错

hbase-spark-error

上述异常是hbase-spark模块编译时抛出的,先尝试升级该模块pom文件中的scala-maven-plugin插件的版本到4.4.0,升级之后依然无法解决问题,-X输出详细的异常日志信息后也于事无补。

可以接着尝试下面的方法,在hbase-spark模块的pom文件中对scala-maven-plugin插件配置作如下修改:

      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
<!--        <version>3.3.1</version>-->
        <version>3.4.6</version>
        <configuration>
          <charset>${project.build.sourceEncoding}</charset>
          <scalaVersion>${scala.version}</scalaVersion>
          <args>
            <arg>-nobootcp</arg>
            <arg>-target:jvm-1.8</arg>
            <!-- <arg>-feature</arg>
             <arg>-target:jvm-1.8</arg>-->
          </args>
        </configuration>
    ......

如果实在解决不了这个异常,也可以选择跳过hbase-spark模块的编译,忽略它并不会影响你对HBase的使用,它仅是一个工具模块,而不是作为一个组件运行在HBase服务的内部,所以,我们完全可以使用JDK8编译出的hbase-spark来实现spark读写HBase集群中的数据。

重新运行编译命令,该模块的编译也顺利通过。

解决完上述的编译问题后,继续来运行我们的编译命令:

mvn clean package -DskipTests
mvn clean package -DskipTests assembly:single

编译命令成功执行,可以用JDK15打包HBase。

3.8 处理一些编译时的警告信息

如果你是一个强迫症患者,可以留心整个编译时的日志输出,针对性地优化一些警告信息。

如:org.apache.maven.plugins:maven-resources-plugin is missing

--add-opens 在编译时没有任何效果,删除编译插件中的 --add-opens配置

Unsafe

编译hbase-1.4.8的时候,我们重点修改了Bytes.java和与之相关类中的import sun.misc.Unsafe;

Bytes和UnsafeAccess两个类中的sun.misc.Unsafe替换为jdk.internal.misc.Unsafe

但是在编译此版本时,并没有相关的异常报出,只有此处的警告,我们也可以做上述类的替换,以使用高版本JDK中推荐的做法。

逐一检查了编译日志,大多是使用了过时API的警告,这个之后用更高版本JDK再编译的时候,只需要注意过时API是否被删除,然后寻找替代类就OK。

4. 本地启动和功能测试

4.1 HMaster Application配置

上述编译的命令虽然已经可以成功执行,但HBase是否可以真正提供读写服务还有待验证,我们最好不要直接就去替换线上的jar包,可以先在本地尝试启动一下HMaster的进程,来简单测试下HBase的基本功能。关于HBase的源码在IDEA中如何DEBUG运行,之前也在公众号里分享过多篇文章,这里就不过多赘述详细的配置过程。

我们直接在IDEA中添加HMaster的Application,配置之后尝试运行此进程。

HMaster
  1. Application的名称
  2. HMaster进程所属的模块
  3. VM Options
  4. HMaster主类全路径名
  5. HADOOP_HOME,本地解压缩了一份hadoop的目录,主要为了消除警告
  6. start 运行时main函数传参

VM Options更详细的配置如下:

-Dproc_master
-XX:OnOutOfMemoryError="kill -9 %p"
-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
-Dhbase.log.dir=/Users/mac/other_project/cloudera/jdk15/cloudera-hbase/hbase-data/logs
-Dhbase.log.file=hbase-root-master.log
-Dhbase.home.dir=/Users/mac/other_project/cloudera/jdk15/cloudera-hbase/bin/.
-Dhbase.id.str=root
-Dhbase.root.logger=INFO,console,DRFA
--illegal-access=deny
--add-exports=java.base/jdk.internal.access=ALL-UNNAMED
--add-exports=java.base/jdk.internal=ALL-UNNAMED
--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED
--add-exports=java.base/sun.security.pkcs=ALL-UNNAMED
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dorg.apache.hbase.thirdparty.io.netty.tryReflectionSetAccessible=true

一些JDK15运行时所需的JVM参数非常重要,大家有兴趣可以琢磨下每个配置的作用。这些配置的添加,基本上就是报什么错,然后找对应的配置来解决即可。

拷贝conf目录到hbase-server目录下,并使之变成一个resources文件夹,hbase-site.xml配置文件中加入以下配置:

   <property>
        # 此配置是为了跳过版本检查
        <name>hbase.defaults.for.version.skip</name>
        <value>true</value>
    </property>
    <property>
        <name>hbase.master.info.port</name>
        <value>16010</value>
    </property>
    <property>
        <name>hbase.regionserver.info.port</name>
        <value>16030</value>
    </property>
    <property>
        <name>hbase.rootdir</name>
        <value>file:///Users/mac/other_project/cloudera/jdk15/cloudera-hbase/hbase-data/hbase</value>
        <description>hbase本地数据地址</description>
    </property>
    <property>
        <name>hbase.zookeeper.property.dataDir</name>
        <value>/Users/mac/other_project/cloudera/jdk15/cloudera-hbase/hbase-data/zookeeper-data</value>
        <description>hbase 内置zk数据目录地址</description>
    </property>
    <property>
        <name>hbase.unsafe.stream.capability.enforce</name>
        <value>false</value>
    </property>

4.2 localhost/unresolved:2181异常的详细分析与暴力解决

上述配置完成之后,就可以点击run按钮,运行该进程啦。

run-error

HMaster进程启动报错,无法连接上内嵌在HBase中的zookeeper,原因就是对zookeeper的地址解析出错,出现了这样的主机地址:localhost/<unresolved>:2181

HBase大佬估计一眼就能看得出导致该异常的原因,但该问题着实让我费解了一两天,而且我还傻傻地认为,在集群环境中,连接上独立的ZK地址之后,就不会出现该异常啦。哪成想,替换完编译后的jar包之后,CDH上的HBase服务死活起不来,报错与本地启动时的一致。

最终依靠略微改动cdh-zookeeper的StaticHostProvider类中的部分代码之后,才顺利解决了这个地址解析的异常。但我还是想记录下当时解决该问题的整个思维过程,着急的伙伴可以直接跳读至下文解决问题的具体步骤上。

面对该异常,我第一时间的想法是内嵌的zookeeper服务没有正常启动,为了验证此猜想,我先打断点到zookeeper服务端启动后的那个代码位置,扒拉下日志。

zk-server

在日志中可以看到,MiniZooKeeperCluster已经成功启动了,在这个类里搜索这句日志的关键部分。

debug-position

断点的位置确定,DEBUG运行HMaster进程。

telnet localhost 2181
# 本机2181端口可以顺利监听

现在可以证明,内嵌的zookeeper服务成功启动,2181这个端口也已经被占用了,然后可以用zookeeper-shell之类的客户端工具连接下我们的zk服务。这里,我使用的是zk-shell。

zk-shell localhost:2181
Welcome to zk-shell (1.3.2)
(DISCONNECTED) />
# 无法连接

zk服务虽已启动,端口被占用,但是客户端无法连接。

zk服务为何是这样的状况,这里根本没有头绪去解释。所以,为了避免被带到弯路,远离真相更远。还是需要回过头来重点关注异常的表象,就像剥洋葱一样,你若想看到最内层的情况,你必须撕掉外面的表皮。

localhost/<unresolved>:2181这个地址中unresolved关键词究竟在哪里产生或者如何触发,这是首先要解决的问题。

DEBUG是最好的工具,可以依次来追踪每个方法的执行链路、观察每个变量的数据流转。

异常是类zookeeper.ClientCnxn抛出的,找到这个类,搜索日志关键信息,打断点,观察方法调用栈、关注变量的变化。

ClientCnxn,直接搜这个类,还真没被我搜到,观察日志上下文,zookeeper.ClientCnxnzookeeper.ZooKeepermainmain-SendThread

有了这些关键词,就算我们对zookeeper的源码非常陌生,也大致能猜得出,ClientCnxn和ZooKeeper属于zookeeper这个对象,ZooKeeper在主线程中活动,ClientCnxn在该主线程中的一个名为SendThread的子线程中活动。所以,直接找不到ClientCnxn的话,就去找ZooKeeper这个类吧。

ClientCnxn-debug

DEBUG信息:

debug-info

继续追踪

debug-go-on

addr这个变量如何产生、赋值并传递给最终的调用,DEBUG到这里就十分清晰啦。

addr最终被addr = ClientCnxn.this.hostProvider.next(1000L);赋值,hostProvider是ClientCnxn的一个成员变量,在构造ClientCnxn对象时被赋值。

然后我们继续追踪上层的调用栈,需要到其上层调用类ZooKeeper中DEBUG,重要的是观察ClientCnxn在ZooKeeper中如何初始化,核心是注意ClientCnxn接收的那个addr变量是怎么赋值的。

addr-error

不墨迹了,我们传的connectionString=localhost:2181是从HBase的配置文件中拿到的,是一个正常有效的ZK地址。ConnectStringParser解析这个地址的时候,把正常地址解析成了localhost/<unresolved>:2181

HostProvider被赋值了错误的地址,接着往下传给ClientCnxn,ClientCnxn拿着错误的地址链接ZK,可就连不上嘛。所以,我们最终能确认是,该异常是有ConnectStringParser解析ZK地址时导致的。

为何JDK8中无此异常,为何同样是JDK15编译的hbase-1.4.8中也无此异常。

  1. JDK8中为何不会报错

    jdk8与jdk15中InetSocketAddress的createUnresolved方法实现有差异,具体的对比请查看两个JDK版本的源码实现。

    InetSocketAddress.createUnresolved(host, port)
    
    # jdk15
     @Override
            public String toString() {
    
                String formatted;
    
                if (isUnresolved()) {
                    formatted = hostname + "/<unresolved>";
                } else {
                    formatted = addr.toString();
                    if (addr instanceof Inet6Address) {
                        int i = formatted.lastIndexOf("/");
                        formatted = formatted.substring(0, i + 1)
                                + "[" + formatted.substring(i + 1) + "]";
                    }
                }
                return formatted + ":" + port;
            }
    
  2. JDK15编译的hbase-1.4.8中为何不会报错

    社区版hbase-1.4.8中zookeeper的类StaticHostProvider 实现与cdh-zookeeper不同,感兴趣可以仔细对比。不能说cdh-zookeeper的实现有错,只能说,它没有考虑高版本JDK的情况,而社区版的HBase大佬们考虑到了,这也变向证明啦,CDH的产品确实比社区版的‘慢半拍’。

解决办法

  1. 大佬们一般会修改JDK的InetSocketAddress的源码,然后重新编译下JDK
  2. 一般点的大佬会拉下CDH对应版本的zookeeper的源码,修修改改,重新ant一下zookeeper的源码
  3. 菜一点的只能解压zookeeper编译好的jar,把里面的源码抠出来,涂涂改改StaticHostProvider.java这个文件,编译好后,再替换回去。那为啥不直接修改StaticHostProvider.class?只能说,编译器的活,一般的凡人真干不来。

具体的操作过程

解压缩zookeeper-3.4.5-cdh6.3.2.jar,新建空的maven项目,把解压后的代码全部拷贝过去。源项目的cloudera/maven-packaging/zookeeper下的pom.xml的依赖考到maven项目。build.xml中查找【Dependency versions】,将Dependency versions下的版本对应到pom.xml,以下是完整pom。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cloudera.cdh</groupId>
    <artifactId>zookeeper-root</artifactId>
    <version>3.4.5-cdh6.3.1</version>
    <packaging>pom</packaging>

    <name>CDH ZooKeeper root</name>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>jline</groupId>
            <artifactId>jline</artifactId>
            <version>2.11</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.yetus/audience-annotations -->
        <dependency>
            <groupId>org.apache.yetus</groupId>
            <artifactId>audience-annotations</artifactId>
            <version>0.5.0</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty</artifactId>
            <version>3.10.6.Final</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.8.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>checkstyle</groupId>
            <artifactId>checkstyle</artifactId>
            <version>5.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>jdiff</groupId>
            <artifactId>jdiff</artifactId>
            <version>1.0.9</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>xerces</groupId>
            <artifactId>xerces</artifactId>
            <version>1.4.4</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.apache.rat</groupId>
            <artifactId>apache-rat-tasks</artifactId>
            <version>0.6</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.4</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

改完源码,没有打包需求,只需要它能编译就成。

private void init(Collection<InetSocketAddress> serverAddresses) {
        try{
            for (InetSocketAddress address : serverAddresses) {
                InetAddress ia = address.getAddress();
                InetAddress resolvedAddresses[] = InetAddress.getAllByName((ia!=null) ? ia.getHostAddress():
                        address.getHostName());
                for (InetAddress resolvedAddress : resolvedAddresses) {
                    if (resolvedAddress.toString().startsWith("/")
                            && resolvedAddress.getAddress() != null) {
                        this.serverAddresses.add(
                                new InetSocketAddress(InetAddress.getByAddress(
                                        address.getHostName(),
                                        resolvedAddress.getAddress()),
                                        address.getPort()));
                    } else {
                        this.serverAddresses.add(new InetSocketAddress(resolvedAddress.getHostAddress(), address.getPort()));
                    }
                }
            }
        }catch (UnknownHostException e){
            throw new IllegalArgumentException("UnknownHostException ws cached!");
        }

        if (this.serverAddresses.isEmpty()) {
            throw new IllegalArgumentException("A HostProvider may not be empty!");
        }
        // 关注这里哈
        // Collections.shuffle(this.serverAddresses);
        // this.serverAddresses.addAll(serverAddresses);
        Collections.shuffle(this.serverAddresses);
    }

把编译好的StaticHostProvider.class文件,替换回之前的解压缩目录中,然后,把该目录再打成jar。

# 相关命令
cd /Users/mac/.m2/repository/org/apache/zookeeper/zookeeper/3.4.5-cdh6.3.2

jar cf zookeeper-3.4.5-cdh6.3.2.jar ./*

替换结束后,更新下maven配置,然后重新运行HMaster的进程。

success

localhost:2181的地址已经被解析成正确的IPv4和IPv6地址,HMaster顺利启动,不再报错啦!

4.3 HBase Shell进程运行,验证数据读写功能

hbase-shell-app
  1. HBaseShell Application Name
  2. hbase-shell模块
  3. VM参数
  4. 运行主类org.jruby.Main
  5. 运行时main函数传参,注意路径

VM参数的具体配置:

-Dhbase.ruby.sources=/Users/mac/other_project/cloudera/jdk15/cloudera-hbase/hbase-shell/src/main/ruby
--illegal-access=deny
--add-exports=java.base/jdk.internal.access=ALL-UNNAMED
--add-exports=java.base/jdk.internal=ALL-UNNAMED
--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED
--add-exports=java.base/sun.security.pkcs=ALL-UNNAMED
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dorg.apache.hbase.thirdparty.io.netty.tryReflectionSetAccessible=

数据读写验证:

create 'leo_test','info',SPLITS=>['1000000','2000000','3000000']
create 'leo_test2', 'info', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
put 'leo_test','10001','info:name','leo'
scan 'leo_test'
flush 'leo_test'
major_compact 'leo_test'
status 'replication'
list_snapshots
snapshot 'leo_test','leo_test_snapshot'
clone_snapshot 'leo_test_snapshot','leo_test_clone'
scan 'leo_test_clone'
count 'leo_test_clone'
truncate 'leo_test2'
truncate_preserve 'leo_test'

上述命令可以正常使用。

WEB-UI

hbase-ui

WEB-UI可以正常访问。

5. 集成hbase-hbtop

线上集群集成了hbase-hbtop,此次编译的版本也需要对hbase-hbtop模块重新编译下,具体的操作过程可以参考历史文章。

然后,重新编译项目。

上述安装包百度云盘地址,感兴趣的伙伴可以安装体验一下。

链接:https://pan.baidu.com/s/1RlMGcfAOoIYbTn7GCvz4_w  密码:q5z8
修改后代码托管地址:
https://gitee.com/weixiaotome/cloudera-hbase
克隆源码之后请切换对应的分支。

6. CDH上部署编译后的HBase

6.1 环境准备

这一小节记录用编译好的HBase的jar包替换CDH平台上原有的HBase的jar包,并单独为CDH的HBase指定JDK的版本为jdk15,然后修改集群的启动参数,使集群能够顺利启动,并测试其功能是否存在异常。

我先在CDH测试集群中做修改,CDH测试集群HBase的版本是cdh6.3.1-hbase2.1.0

大致的步骤如下:

  1. 下载并解压缩jdk15的安装包,无需设置Java的环境变量
  2. 替换CDH安装目录中的HBase相关的jar包
  3. 修改集群java相关的配置参数,然后重启集群

jdk15的下载和解压缩在此略过。

第二步,替换jar包:

/opt/cloudera/parcels/CDH/lib/hbase/lib
ll | grep hbase
该目录为HBase的jar包加载目录,可以看到其所需的所有jar都软连自../../../jars目录
jars
cd /opt/cloudera/parcels/CDH/jars
ll | grep hbase & 筛选出来的所有与HBase相关的jar,我们依次对比编译好的HBase安装包中涉及到的jar,从中剔除不需要替换的。

hbase-metrics-api-2.1.0-cdh6.3.1.jar
hbase-rest-2.1.0-cdh6.3.1.jar
hbase-shaded-protobuf-2.2.1.jar
hbase-http-2.1.0-cdh6.3.1.jar
hbase-zookeeper-2.1.0-cdh6.3.1-tests.jar
hbase-it-2.1.0-cdh6.3.1.jar
hbase-rsgroup-2.1.0-cdh6.3.1-tests.jar
hbase-it-2.1.0-cdh6.3.1-tests.jar
hbase-external-blockcache-2.1.0-cdh6.3.1.jar
hbase-rsgroup-2.1.0-cdh6.3.1.jar
hbase-replication-2.1.0-cdh6.3.1.jar
hbase-mapreduce-2.1.0-cdh6.3.1.jar
hbase-common-2.1.0-cdh6.3.1-tests.jar
hbase-protocol-shaded-2.1.0-cdh6.3.1.jar
hbase-shaded-netty-2.2.1.jar
hbase-common-2.1.0-cdh6.3.1.jar
hbase-annotations-2.1.0-cdh6.3.1.jar
hbase-shaded-miscellaneous-2.2.1.jar
hbase-procedure-2.1.0-cdh6.3.1.jar
hbase-hadoop-compat-2.1.0-cdh6.3.1.jar
hbase-annotations-2.1.0-cdh6.3.1-tests.jar
hbase-examples-2.1.0-cdh6.3.1.jar
hbase-client-2.1.0-cdh6.3.1.jar
hbase-metrics-2.1.0-cdh6.3.1.jar
hbase-server-2.1.0-cdh6.3.1.jar
hbase-shell-2.1.0-cdh6.3.1.jar
hbase-testing-util-2.1.0-cdh6.3.1.jar
hbase-mapreduce-2.1.0-cdh6.3.1-tests.jar
hbase-server-2.1.0-cdh6.3.1-tests.jar
hbase-resource-bundle-2.1.0-cdh6.3.1.jar
hbase-protocol-2.1.0-cdh6.3.1.jar
hbase-hadoop2-compat-2.1.0-cdh6.3.1.jar
hbase-spark-it-2.1.0-cdh6.3.1.jar
hbase-hadoop2-compat-2.1.0-cdh6.3.1-tests.jar
hbase-spark-2.1.0-cdh6.3.1.jar
hbase-hadoop-compat-2.1.0-cdh6.3.1-tests.jar
hbase-endpoint-2.1.0-cdh6.3.1.jar
hbase-thrift-2.1.0-cdh6.3.1.jar
hbase-zookeeper-2.1.0-cdh6.3.1.jar

同时别忘记替换zookeeper-3.4.5-cdh6.3.1.jar

上述过程最好写一个脚本自动执行,并准备回滚方案,防止集群无法启动时可以一键回滚到之前的状态。

6.2 配置CDH中HBase相关服务的java参数

为CDH的HBase指定单独的JDK

在CDH界面中配置Java相关的参数,类似于之前的文章中记录的,在hbase-env.sh相关的脚本中增加Java相关的参数。

配置参数时,最好先关闭HBase的服务。

在配置搜索框中搜索环境高级配置,由上到下依次设置JAVA_HOME=/usr/java/openjdk-15.0.2

  1. HBase 服务环境高级配置代码段(安全阀)
  2. hbase-env.sh 的 HBase 客户端环境高级配置代码段(安全阀)
  3. HBase REST Server 环境高级配置代码段(安全阀)
  4. HBase Thrift Server 环境高级配置代码段(安全阀)
  5. Master 环境高级配置代码段(安全阀)
  6. RegionServer 环境高级配置代码段(安全阀)

为上述服务分别设置启动时所需的JVM参数

在配置搜索框中搜索java,由上到下依次设置。

  1. 客户端 Java 配置选项

    原有配置:-XX:+HeapDumpOnOutOfMemoryError -Djava.net.preferIPv4Stack=true

  2. HBase REST Server 的 Java 配置选项

    原有配置:{{JAVA_GC_ARGS}}

  3. HBase Thrift Server 的 Java 配置选项

    原有配置:{{JAVA_GC_ARGS}}

  4. HBase Master 的 Java 配置选项

    原有配置:{{JAVA_GC_ARGS}} -XX:ReservedCodeCacheSize=256m

  5. HBase RegionServer 的 Java 配置选项

    原有配置:{{JAVA_GC_ARGS}} -XX:ReservedCodeCacheSize=256m

-XX:+UseZGC
-XX:+UnlockExperimentalVMOptions
--illegal-access=deny
--add-exports=java.base/jdk.internal.access=ALL-UNNAMED
--add-exports=java.base/jdk.internal=ALL-UNNAMED
--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED
--add-exports=java.base/sun.security.pkcs=ALL-UNNAMED
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dorg.apache.hbase.thirdparty.io.netty.tryReflectionSetAccessible=true

配置完成之后尝试重启HBase的服务,可以按照提示分发下客户端配置,可以重启下整个CDH集群,观察是否有依赖HBase的服务可能会无法启动。

6.3 测试HBase的可用性

hbase shell

CDH上的HBase重启,以及其上所有服务重启,都能顺利启动,无异常。然后我们运行hbase-shell,连接HBase服务,运行以下测试命令,并测试原有的表是否可以正常读写。

create 'leo_test','info',SPLITS=>['1000000','2000000','3000000']
create 'leo_test2', 'info', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
put 'leo_test','10001','info:name','leo'
scan 'leo_test'
flush 'leo_test'
major_compact 'leo_test'
status 'replication'
list_snapshots
snapshot 'leo_test','leo_test_snapshot'
clone_snapshot 'leo_test_snapshot','leo_test_clone'
scan 'leo_test_clone'
count 'leo_test_clone'
truncate 'leo_test2'
truncate_preserve 'leo_test'
hbase-shell-error

出现此异常时,取消客户端Java配置中的换行。重新分发客户端配置后,就可以连接到hbase啦。

success

HBase的命令可以正常使用,之前的表也可以正常读写。

WEB-UI

可以正常访问

ThriftServer的API

import happybase
pool = happybase.ConnectionPool(size=3, host='xx.x.x.xx', port=9090)
with pool.connection() as connection:
    table = connection.table("leo_test")
    print list(table.scan())

Thrift API 可以正常使用。

thrift-success

7. 总结

我们针对线上的HBase,已经采取了很多优化的手段,从核心参数的调整、G1的优化、主备熔断、以及近期在测试的HDFS异构存储等等,以求最大化地突破HBase的整体性能瓶颈。对ZGC的持续探索,也是想从GC的层面,继续尝试优化HBase的读写性能,不断突破瓶颈,同时也加深自己对JVM的了解和学习。

8. 参考链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容