1. 程式人生 > >深入理解 JBoss 7/WildFly Domain 模式啟動過程

深入理解 JBoss 7/WildFly Domain 模式啟動過程

ket builder 代碼調試 ref finished rop mas stopped rgs

概述

JBoss 7/WildFly 以 domain 模式啟動時會啟動多個 JVM。比如例如以下通過啟動腳本啟動 domain 模式:

./domain.sh

啟動後我們查看進程:

[[email protected] tdump]$ jps -l
23655 /home/kylin/work/eap/jboss-eap-6.1/jboss-modules.jar
23671 /home/kylin/work/eap/jboss-eap-6.1/jboss-modules.jar
23736 /home/kylin/work/eap/jboss-eap-6.1/jboss-modules.jar

我們能夠發現 domain 模式啟動時會啟動後,有三個進程(相應三個JVM)執行。本文主要研究 domain 模式下是怎樣啟動多個 JVM 的。

另外,`jboss-modules.jar` 是 JBoss 底層類載入機制,用於類載入和啟動一個 JVM。


從啟動腳本開始

domain.sh 腳本中例如以下信息:

      eval \"$JAVA\" -D\"[Process Controller]\" $PROCESS_CONTROLLER_JAVA_OPTS          \"-Dorg.jboss.boot.log.file=$JBOSS_LOG_DIR/process-controller.log\"          \"-Dlogging.configuration=file:$JBOSS_CONFIG_DIR/logging.properties\"          -jar \"$JBOSS_HOME/jboss-modules.jar\"          -mp \"${JBOSS_MODULEPATH}\"          org.jboss.as.process-controller          -jboss-home \"$JBOSS_HOME\"          -jvm \"$JAVA_FROM_JVM\"          -mp \"${JBOSS_MODULEPATH}\"          --          \"-Dorg.jboss.boot.log.file=$JBOSS_LOG_DIR/host-controller.log\"          \"-Dlogging.configuration=file:$JBOSS_CONFIG_DIR/logging.properties\"          $HOST_CONTROLLER_JAVA_OPTS          --          -default-jvm \"$JAVA_FROM_JVM\"          ‘"$@"‘
      JBOSS_STATUS=$?

基於 JBoss Module 類載入機制,我們查看 org.jboss.as.process-controller,在 `module.xml` 的描寫敘述中啟動 Main 方法例如以下:

<module xmlns="urn:jboss:module:1.1" name="org.jboss.as.process-controller">
    <properties>
        <property name="jboss.api" value="private"/>
    </properties>

    <main-class name="org.jboss.as.process.Main"/>

所以我們能夠從 `org.jboss.as.process.Main` 類開始,JBoss 是全然開源的。這樣非常easy開始調試代碼。通過例如以下步驟能夠開始調試啟動代碼:

  • 在 pom.xml 中加入例如以下依賴

                <dependency>
                        <groupId>org.jboss.as</groupId>
                        <artifactId>jboss-as-process-controller</artifactId>
                        <version>7.2.0.Final-redhat-8</version>
                </dependency>

  • 編輯 domain.conf,加入例如以下配置

PROCESS_CONTROLLER_JAVA_OPTS="$PROCESS_CONTROLLER_JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=8788,server=y,suspend=y"
HOST_CONTROLLER_JAVA_OPTS="$HOST_CONTROLLER_JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=y"

  • 以 domain 模式啟動 JBoss。在 `org.jboss.as.process.Main` 的 main 方法中加入斷點。開始代碼調試,例如以下圖所看到的:

技術分享

對比之前的啟動腳本。main 方法傳入的參數例如以下:

-jboss-home, /home/kylin/work/eap/jboss-eap-6.1,
-jvm, /usr/java/jdk1.7.0_21/bin/java,
-mp, /home/kylin/work/eap/jboss-eap-6.1/modules,
--,
-Dorg.jboss.boot.log.file=/home/kylin/work/eap/jboss-eap-6.1/domain/log/host-controller.log,
-Dlogging.configuration=file:/home/kylin/work/eap/jboss-eap-6.1/domain/configuration/logging.properties,
-server, -Xms63m, -Xmx512m, -XX:MaxPermSize=256m, -Djava.net.preferIPv4Stack=true, -Djboss.modules.system.pkgs=org.jboss.byteman,
-Djava.awt.headless=true, -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=y,
--,
-default-jvm, /usr/java/jdk1.7.0_21/bin/java

domain 模式啟動

下圖描寫敘述 domain 模式啟動的過程:

技術分享

依據上圖描寫敘述,我們將 domain 模式啟動描寫敘述為以下步驟:

  • 通過啟動腳本 `domain.sh` 啟動 *Process Controller* JVM,*Process Controller* 啟動後監聽於端口 0。該段口等待 Host Controller JVM 連接
  • Process Controller 啟動 Host Controller。我們隨後具體介紹 Process Controller 怎樣啟動 Host Controller, Host Controller 啟動後。首先解析 `host.xml` 文件。獲取要啟動 Server 信息。並與 Process Controller 建立 TCP 連接,將啟動 Server 的信息發送給 Process Controller
  • Process Controller 依據 Host Controller 發送的 Server 信息,啟動相關的 Server
如上圖。我們能夠看到三個 JVM 啟動的 Main 方法分別位於三個不同的 module 中: `org.jboss.as.process-controller` (相應的 Main 方法為 `org.jboss.as.process.Main`), `org.jboss.as.host-controller ` (相應的 Main 方法為 `org.jboss.as.host.controller.Main`), `org.jboss.as.server` (相應的 Main 方法為 `org.jboss.as.server.DomainServerMain`)。

Process Controller 怎樣啟動 Host Controller

Process Controller 和 Host Controller 屬於不同的 JVM。Process Controller 啟動 Host Controller 是在一個 JVM 中啟動還有一個 JVM,啟動代碼位於 `org.jboss.as.process.ManagedProcess` 例如以下圖所看到的:

技術分享

我們能夠通過例如以下代碼模擬:

        public static void main(String[] args) throws IOException {
                
                 List<String> command = new ArrayList<String>();
                 command.add("/usr/java/jdk1.7.0_21/bin/java");
                 command.add("-jar");
                 command.add("/home/kylin/work/eap/jboss-eap-6.1/jboss-modules.jar");
                 command.add("-version");
                 ProcessBuilder builder = new ProcessBuilder(command);
                 builder.directory(new File("/home/kylin/tmp"));
                 Process process = builder.start();
                 final InputStream stdout = process.getInputStream();
                 Thread stdoutThread = new Thread(new ReadTask(stdout, System.out));
                 stdoutThread.start();
        }

點擊查看完整代碼:org/wildfly/domain/test/PCStartHC.java

執行如上代碼輸出JBoss EAP 6.1 所使用的 JBoss Module 的版本:

[Host Controller] JBoss Modules version 1.2.0.Final-redhat-1

而上面調試過程中 ProcessBuilder 傳入的參數例如以下:

/usr/java/jdk1.7.0_21/bin/java, -D[Host Controller],
-Dorg.jboss.boot.log.file=/home/kylin/work/eap/jboss-eap-6.1/domain/log/host-controller.log,
-Dlogging.configuration=file:/home/kylin/work/eap/jboss-eap-6.1/domain/configuration/logging.properties,
-server, -Xms63m, -Xmx512m, -XX:MaxPermSize=256m, -Djava.net.preferIPv4Stack=true, -Djboss.modules.system.pkgs=org.jboss.byteman,
-Djava.awt.headless=true, -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=y,
-jar, /home/kylin/work/eap/jboss-eap-6.1/jboss-modules.jar,
-mp, /home/kylin/work/eap/jboss-eap-6.1/modules,
-jaxpmodule, javax.xml.jaxp-provider, org.jboss.as.host-controller,

-mp, /home/kylin/work/eap/jboss-eap-6.1/modules,
--pc-address, 127.0.0.1,
--pc-port, 50396,
-default-jvm, /usr/java/jdk1.7.0_21/bin/java,
-Djboss.home.dir=/home/kylin/work/eap/jboss-eap-6.1

如上參數被分為兩個部分,上面部分用來啟動 Host Controller。以下部分作為 Main 方法參數。傳遞給 org.jboss.as.host-controller 模塊定義的 main-class 中,>查看 main-class 定義例如以下:

<module xmlns="urn:jboss:module:1.1" name="org.jboss.as.host-controller">
    <properties>
        <property name="jboss.api" value="private"/>
    </properties>

    <main-class name="org.jboss.as.host.controller.Main"/>

    <resources>
        <resource-root path="jboss-as-host-controller-7.2.0.Final-redhat-8.jar"/>

如上,我們能夠在 `org.jboss.as.host.controller.Main` 中設定斷點,繼續調試 Host Controller 啟動,例如以下

技術分享

`org.jboss.as.host.controller.Main` 中傳入的參數例如以下:

-mp, /home/kylin/work/eap/jboss-eap-6.1/modules,
--pc-address, 127.0.0.1,
--pc-port, 45210,
-default-jvm, /usr/java/jdk1.7.0_21/bin/java,
-Djboss.home.dir=/home/kylin/work/eap/jboss-eap-6.1

註意,如前面分析,這些參數是從 Process Controller 傳遞過來的,且通過代碼調試顯示這些參數與之前分析的同樣。

Process Controller 啟動 Server

Process Controller 啟動 Server 過程全然相似,僅僅是要啟動的 Server 信息是通過 Host Controller傳遞過來的,傳遞過來的 ProcessBuilder 傳入的參數例如以下:

/usr/java/jdk1.7.0_21/bin/java, -D[Server:server-one],
-XX:PermSize=256m, -XX:MaxPermSize=256m, -Xms1303m, -Xmx1303m, -server, -D[Host Controller]=true,
-Djava.awt.headless=true, -Djboss.modules.system.pkgs=org.jboss.byteman, -Djboss.home.dir=/home/kylin/work/eap/jboss-eap-6.1,
-Djava.net.preferIPv4Stack=true, -Djboss.server.log.dir=/home/kylin/work/eap/jboss-eap-6.1/domain/servers/server-one/log,
-Djboss.server.temp.dir=/home/kylin/work/eap/jboss-eap-6.1/domain/servers/server-one/tmp,
-Djboss.server.data.dir=/home/kylin/work/eap/jboss-eap-6.1/domain/servers/server-one/data,
-Dlogging.configuration=file:/home/kylin/work/eap/jboss-eap-6.1/domain/servers/server-one/data/logging.properties,
-jar, /home/kylin/work/eap/jboss-eap-6.1/jboss-modules.jar,
-mp, /home/kylin/work/eap/jboss-eap-6.1/modules, -jaxpmodule, javax.xml.jaxp-provider, org.jboss.as.server

通過 `org.jboss.as.server` 我們能夠找到 Server JVM 啟動的 Main 方法為 `org.jboss.as.server.DomainServerMain`。

怎樣理解 PC。HC,Server

PC 是指 Process Controller。HC 是指 Host Controller(與 HC 相相應的是 Domain Controller,簡稱 DC)。Server 指的是 JBoss 服務器。

從功能層面來說,PC 用來啟動和停止 HC 和 Server,且PC 啟動停止 Server 首先須要得到 HC 發來的指令。domain 模式啟動完畢後會啟動例如以下線程用來停止 HC 和 Servers:

"reaper for Host Controller" prio=10 tid=0x7fc3b000 nid=0x770f in Object.wait() [0x803ab000]
"reaper for Server:server-one" prio=10 tid=0x8320b400 nid=0x7750 in Object.wait() [0x7fdfe000]

PC 啟動 HC 與 Server 是在一個 JVM 中啟動還有一個 JVM,這樣 HC 與 Server 中 System.out 和 System.err 流須要使用 PC 中的相應流。所以 PC 的還有一個左右是輸>出 PC 和 Server 中輸出流打印輸出的信息。domain 模式啟動完畢後會啟動例如以下線程用來完畢此任務:

"stdout for Host Controller" prio=10 tid=0x7fc3a400 nid=0x770e runnable [0x803fc000]
"stderr for Host Controller" prio=10 tid=0x7fc36000 nid=0x770d runnable [0x8087d000]
"stdout for Server:server-one" prio=10 tid=0x83209c00 nid=0x774f runnable [0x7ff5c000]
"stderr for Server:server-one" prio=10 tid=0x83208400 nid=0x774e runnable [0x7ffad000]

HC 用來管理 Server,Domain 模式的設計的一個目的就是在一臺物理服務器上通過端口偏移執行多個 Server,HC 設計用來管理這些 Server。Domain 模式的設計的還有一
個目的是多個物理機器上的多個 Server 能夠被統一管理,HC 也能夠管理多個物理機器上的多個機器,這樣的情況下我們稱 HC 為 DC。關於怎樣配置 DC 不屬於此處討論>的範疇。


Server 是指 JBoss 服務器平臺,為 JEE 應用提供容器。


關閉 Server

本部分演示通過 OS 信號關閉 Server 時 PC 和 HC 相關的 Action。

我們通過例如以下步驟演示:

  • 以 domain 模式啟動JBoss
  • 使用 `kill -9 PID` 關閉 Server

觀察日誌。我們看到例如以下日誌輸出:

21:39:08,800 INFO  [org.jboss.as.process.Server:server-one.status] (reaper for Server:server-one) JBAS012010: Process ‘Server:server-one‘ finished with an exit status of 137
[Host Controller] 21:39:08,801 INFO  [org.jboss.as.host.controller] (ProcessControllerConnection-thread - 2) JBAS010926: Unregistering server server-one
[Host Controller] 21:39:08,806 INFO  [org.jboss.as.host.controller] (Remoting "localhost.localdomain:MANAGEMENT" read-1) JBAS010926: Unregistering server server-one

PC 收到關閉 Server 的信息,HC Unregistering Server,例如以下為 `reaper for Server:server-one` 線程的代碼:

        public void run() {
            final Process process;
            synchronized (lock) {
                process = ManagedProcess.this.process;
            }
            int exitCode;
            for (;;) try {
                exitCode = process.waitFor();
                log.processFinished(processName, Integer.valueOf(exitCode));
                break;
            } catch (InterruptedException e) {
                // ignore
            }
            boolean respawn = false;
            boolean slowRespawn = false;
            boolean unlimitedRespawn = false;
            int respawnCount = 0;
            synchronized (lock) {

                final long endTime = System.currentTimeMillis();
                processController.processStopped(processName, endTime - startTime);

分析代碼。 `reaper for Server:server-one` 線程堵塞於 exitCode = process.waitFor() 行代碼,等待關閉信號,且接收到關閉信號後首先日誌輸出。processController.processStopped(processName, endTime - startTime) 行通知 HC Unregistering Server。

深入理解 JBoss 7/WildFly Domain 模式啟動過程