1. 程式人生 > 其它 >HBase探索篇 _ OpenJdk15編譯並部署CDH版HBase

HBase探索篇 _ OpenJdk15編譯並部署CDH版HBase

目錄

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專案,如下圖所示:

第一次載入專案時會下載大量的第三方依賴,請耐心等候,待依賴下載完成之後,我們不做任何修改,嘗試是否可以用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,配置之後嘗試執行此程序。

  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按鈕,執行該程序啦。

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.ClientCnxnzookeeper.ZooKeepermainmain-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中也無此異常。

  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的程序。

localhost:2181的地址已經被解析成正確的IPv4和IPv6地址,HMaster順利啟動,不再報錯啦!

4.3 HBase Shell程序執行,驗證資料讀寫功能

  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

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目錄
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'

出現此異常時,取消客戶端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. 參考連結