目录
1. 前言
2. 准备工作
3. 项目配置
3.1 项目导入
3.2 编译配置
3.3 程序包 javax.annotation 不存在
3.4 maven-shade-plugin 升级版本
3.5 程序包 javax.xml.ws.http 不存在
3.6 Some Enforcer rules have failed
3.7 hbase-spark 模块编译报错
3.8 处理一些编译时的警告信息
4. 本地启动和功能测试
4.1 HMaster Application 配置
4.2 localhost/unresolved:2181 异常的详细分析与暴力解决
4.3 HBase Shell 进程运行,验证数据读写功能
5. 集成 hbase-hbtop
6. CDH 上部署编译后的 HBase
6.1 环境准备
6.2 配置 CDH 中 HBase 相关服务的 java 参数
6.3 测试 HBase 的可用性
7. 总结
8. 参考链接
1. 前言
为探索 JDK15 的 ZGC 特性在 HBase 中的表现力是否犹如传言中的那么优秀,我用AdoptOpenJDK15
重新编译了社区版本的hbase-1.4.8
,接着完成了编译之后,HBase 完全分布式的部署和功能上的测试,并把整个编译的流程与解决过的问题一一分享在了之前的文章中。
HBase源码篇 | ZGC初体验——OpenJdk 15编译HBase 1.4.8
HBase 运维篇 | ZGC 初体验——HBase1.4.8 安装部署和测试
但是我们线上在用的 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 项目,如下图所示:

第一次加载项目时会下载大量的第三方依赖,请耐心等候,待依赖下载完成之后,我们不做任何修改,尝试是否可以用 jdk1.8 直接编译和打包该项目。
# 这里我直接运行打tar.gz包命令,Hadoop的版本默认选择的是3.0
mvn clean package -DskipTests assembly:single

编译过程中如果遇到如上异常,在项目根路径的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

tar 包的位置:

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 不存在

项目根 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 版本升级为 3.2.4
3.5 程序包 javax.xml.ws.http 不存在

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

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

上述异常是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 配置

编译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,配置之后尝试运行此进程。

Application 的名称 HMaster 进程所属的模块 VM Options HMaster 主类全路径名 HADOOP_HOME,本地解压缩了一份 hadoop 的目录,主要为了消除警告 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 按钮,运行该进程啦。

HMaster 进程启动报错,无法连接上内嵌在 HBase 中的 zookeeper,原因就是对 zookeeper 的地址解析出错,出现了这样的主机地址:localhost/<unresolved>:2181
HBase 大佬估计一眼就能看得出导致该异常的原因,但该问题着实让我费解了一两天,而且我还傻傻地认为,在集群环境中,连接上独立的 ZK 地址之后,就不会出现该异常啦。哪成想,替换完编译后的 jar 包之后,CDH 上的 HBase 服务死活起不来,报错与本地启动时的一致。
最终依靠略微改动 cdh-zookeeper 的StaticHostProvider
类中的部分代码之后,才顺利解决了这个地址解析的异常。但我还是想记录下当时解决该问题的整个思维过程,着急的伙伴可以直接跳读至下文解决问题的具体步骤上。
面对该异常,我第一时间的想法是内嵌的 zookeeper 服务没有正常启动,为了验证此猜想,我先打断点到 zookeeper 服务端启动后的那个代码位置,扒拉下日志。

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

断点的位置确定,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.ClientCnxn
、zookeeper.ZooKeeper
,main
,main-SendThread
有了这些关键词,就算我们对 zookeeper 的源码非常陌生,也大致能猜得出,ClientCnxn 和 ZooKeeper 属于 zookeeper 这个对象,ZooKeeper 在主线程中活动,ClientCnxn 在该主线程中的一个名为 SendThread 的子线程中活动。所以,直接找不到 ClientCnxn 的话,就去找 ZooKeeper 这个类吧。

DEBUG 信息:

继续追踪

addr
这个变量如何产生、赋值并传递给最终的调用,DEBUG 到这里就十分清晰啦。
addr
最终被 addr = ClientCnxn.this.hostProvider.next(1000L);赋值,hostProvider 是 ClientCnxn 的一个成员变量,在构造 ClientCnxn 对象时被赋值。
然后我们继续追踪上层的调用栈,需要到其上层调用类 ZooKeeper 中 DEBUG,重要的是观察 ClientCnxn 在 ZooKeeper 中如何初始化,核心是注意 ClientCnxn 接收的那个 addr 变量是怎么赋值的。

不墨迹了,我们传的 connectionString=localhost:2181 是从 HBase 的配置文件中拿到的,是一个正常有效的 ZK 地址。ConnectStringParser
解析这个地址的时候,把正常地址解析成了localhost/<unresolved>:2181
HostProvider 被赋值了错误的地址,接着往下传给 ClientCnxn,ClientCnxn 拿着错误的地址链接 ZK,可就连不上嘛。所以,我们最终能确认是,该异常是由ConnectStringParser
解析 ZK 地址时导致的。
为何 JDK8 中无此异常,为何同样是 JDK15 编译的hbase-1.4.8
中也无此异常。
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;
}JDK15 编译的
hbase-1.4.8
中为何不会报错社区版 hbase-1.4.8 中 zookeeper 的类
StaticHostProvider
实现与 cdh-zookeeper 不同,感兴趣可以仔细对比。不能说 cdh-zookeeper 的实现有错,只能说,它没有考虑高版本 JDK 的情况,而社区版的 HBase 大佬们考虑到了,这也变向证明啦,CDH 的产品确实比社区版的‘慢半拍’。
解决办法
大佬们一般会修改 JDK 的 InetSocketAddress 的源码,然后重新编译下 JDK 一般点的大佬会拉下 CDH 对应版本的 zookeeper 的源码,修修改改,重新 ant
一下 zookeeper 的源码菜一点的只能解压 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 的进程。

localhost:2181 的地址已经被解析成正确的 IPv4 和 IPv6 地址,HMaster 顺利启动,不再报错啦!
4.3 HBase Shell 进程运行,验证数据读写功能

HBaseShell Application Name hbase-shell 模块 VM 参数 运行主类 org.jruby.Main 运行时 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

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
。
大致的步骤如下:
下载并解压缩 jdk15 的安装包,无需设置 Java 的环境变量 替换 CDH 安装目录中的 HBase 相关的 jar 包 修改集群 java 相关的配置参数,然后重启集群
jdk15 的下载和解压缩在此略过。
第二步,替换 jar 包:
/opt/cloudera/parcels/CDH/lib/hbase/lib
ll | grep hbase
该目录为HBase的jar包加载目录,可以看到其所需的所有jar都软连自../../../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
。
HBase 服务环境高级配置代码段(安全阀) hbase-env.sh 的 HBase 客户端环境高级配置代码段(安全阀) HBase REST Server 环境高级配置代码段(安全阀) HBase Thrift Server 环境高级配置代码段(安全阀) Master 环境高级配置代码段(安全阀) RegionServer 环境高级配置代码段(安全阀)
为上述服务分别设置启动时所需的 JVM 参数
在配置搜索框中搜索java
,由上到下依次设置。
客户端 Java 配置选项
原有配置:-XX:+HeapDumpOnOutOfMemoryError -Djava.net.preferIPv4Stack=true
HBase REST Server 的 Java 配置选项
原有配置:{{JAVA_GC_ARGS}}
HBase Thrift Server 的 Java 配置选项
原有配置:{{JAVA_GC_ARGS}}
HBase Master 的 Java 配置选项
原有配置:{{JAVA_GC_ARGS}} -XX:ReservedCodeCacheSize=256m
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'

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

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 可以正常使用。

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




