Java隔離容器之sofa-ark使用說明及原始碼解析

Hugo_Gao發表於2018-08-09

一.使用方法及示例

簡介:當引入二方依賴包或三方依賴包時,可能出現外部依賴jar包與自己的工程需要依賴的衝突,或者多個二方三方依賴包互相沖突。這時候就需要一個隔離容器對他們進行隔離,其依賴的原理就是jvm認為不同classloader載入的類即使包名類名相同,也認為他們是不同的。sofa-ark將需要隔離的jar包打成plugin,對每個plugin都用獨立的classloader去載入。

(溫馨提示:若對sofa-ark不太瞭解的,最好先去看看官方文件,簡單瞭解下)

使用的基本步驟:

  1. 在會發生衝突的jar包的POM檔案加入sofa-ark提供的maven外掛,將其打成特定格式的jar包(plugin)。
  2. 在外部工程按照約定引入jar包。如果外部工程想打包成可執行的jar(fat-jar),還需要加入特定的maven外掛。
  3. 直接執行。

名詞解釋:

  • Ark Container: Ark 容器,是元件 SOFAArk 的核心,執行 Ark 包時,Ark 容器會最先啟動,負責應用執行時的管理,主要包括構建 Ark Plugin 和 Ark Biz 的類匯入匯出關係表、啟動並初始化 Ark Plugin 和 Ark Biz、管理 Ark Plugin 服務的釋出和引用等等。
  • Biz:即業務工程,該工程引用一個或多個外部jar包。
  • plugin:會發生衝突的外部依賴jar包經過提供的Maven外掛打成的fat-jar包。執行時,由獨立的類載入器載入,因此有隔離需求的 Jar 包建議打包成 Ark Plugin 供應用依賴。

1.0 sofa-ark原理

在sofa-ark中,使用container容器啟動外部工程(Biz)和衝突jar包(plugin),sofa-ark的plugin maven外掛將衝突的jar包打包成為plugin,外部工程只能引用plugin exported出來的類,並且這個類是由獨立的PluginClassLoader載入的,從而解決了jar包衝突的問題。

image-20180724204016919

1.1 衝突示例

jar衝突

1.2 安裝sofa-ark

sofa-ark官網 將整個工程下載下來,在最外層pom.xml所在路徑執行mvn packagemvn install 將sofa-ark依賴jar包安裝到本地。

1.3 建立基礎依賴Jar包(衝突的包)

自己建立myjar工程,先後正常打包兩個版本安裝到本地maven倉庫。

如:v1版本

image-20180724185922039

v2版本

image-20180724190102751

1.4 建立service包(plugin)

建立如圖所示兩個工程:

Java隔離容器之sofa-ark使用說明及原始碼解析

myJarservice-v1myJarservice-v2的pom中分別引用之前寫的兩個基礎依賴jar包。然後在MyJarService1MyJarService2中分別引用對應版本的myjar包裡的方法。

如MyJarService1.java:

image-20180724190836319

MyJarService2.java

image-20180724191026890

注意:這裡兩個Service引用的是不同版本的myjar。

接下來需要對兩個工程打包,和平常打包不一樣的是需要加入sofa-ark-plugin的maven外掛。兩個工程下的pom.xml都要加入:

 <build>
        <plugins>
            <plugin>
                <groupId>com.alipay.sofa</groupId>
                <artifactId>sofa-ark-plugin-maven-plugin</artifactId>
                <version>0.4.0-SNAPSHOT</version>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <goal>ark-plugin</goal>
                        </goals>

                        <configuration>
                            <!-- configure exported class -->
                            <exported>
                                <!-- configure class-level exported class -->
                                <classes>
                                 
           <class>com.netease.sofaservice.MyJar1Service</class>
                                </classes>
                            </exported>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
複製程式碼

exported標籤裡寫出要對外提供的方法,外部要引用的所有方法都必須寫在這裡,可以以類(<classes>)為單位和包(packages)為單位匯出。

到parent工程路徑下mvn packagemvn install 即可。

1.5 外部工程引用(Biz)

新建一個工程,在pom.xml引入以下依賴:

<dependencies>
        <dependency>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>sofa-ark-support-starter</artifactId>
            <version>0.4.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v1</artifactId>
            <classifier>ark-plugin</classifier>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v2</artifactId>
            <classifier>ark-plugin</classifier>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v1</artifactId>
            <version>1.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v2</artifactId>
            <version>1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

複製程式碼

注意要新增<classifier>標籤,因為IDE識別不了ark-plugin的jar包,所以需要再引入一個範圍為provided的jar包。

然後pom.xml還要新增maven外掛:

<build>
        <plugins>
            <plugin>
                <groupId>com.alipay.sofa</groupId>
                <artifactId>sofa-ark-maven-plugin</artifactId>
                <version>0.4.0-SNAPSHOT</version>
                <executions>
                    <execution>
                        <id>default-cli</id>

                        <!--goal executed to generate executable-ark-jar -->
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                        <configuration>
                            <!--specify destination where executable-ark-jar will be saved, default saved to ${project.build.directory}-->
                            <outputDirectory>./</outputDirectory>

                            <!--default none-->
                            <arkClassifier>executable-ark</arkClassifier>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
複製程式碼

然後再隨便寫個類引入兩個版本service包的類使用即可,在main函式入口處需要加上一句:

SofaArkBootstrap.launch(args);

image-20180724203423465

可以看到列印結果:

image-20180724203546696

二.sofa-ark-plugin-maven-plugin外掛原理分析

2.1 檢視plugin jar包內容

使用sofa-ark-plugin-maven-plugin Maven外掛即可將jar包打包成可在container中隔離載入的jar包(如myjarservice-v1-1.0-ark-plugin.jarr和myjarservice-v2-1.0-ark-plugin.jar)。進入本地maven倉庫myJarservice-v1工程所在位置,可以看到maven打了兩個包:一個是maven自帶的外掛打的普通的包:myjarservice-v1-1.0.jar ,另一個是sofa-ark提供的maven外掛打的包myjarservice-v1-1.0-ark-plugin.jar,開啟myjarservice-v1-1.0-ark-plugin.jar 可以看到如下目錄:

Java隔離容器之sofa-ark使用說明及原始碼解析

相關目錄說明:

  • com/alipay/sofa/ark/plugin/mark :標記檔案,標記該 Jar 包是 sofa-ark-plugin-maven-plugin 打包生成的 Ark Plugin 檔案。
  • META-INF/MANIFEST.MF :記錄外掛元資訊,其中包含要匯出的和匯入的類
Manifest-Version: 1.0
groupId: com.netease
artifactId: myjarservice-v1
version: 1.0
priority: 100
pluginName: myjarservice-v1
activator: 
import-packages: 
import-classes: 
import-resources: 
export-packages: 
export-classes: com.netease.sofaservice.MyJar1Service
export-resources: 
複製程式碼
  • conf/export.index :外掛匯出類索引檔案;為了避免在執行時計算MANIFEST.MF 中export-packages 下面具體的匯出類,在打包生成 Ark Plugin 時,會生成外掛所有匯出類的索引檔案,縮短 Ark Container 解析配置時間。
  • lib/ : lib 目錄存放外掛工程依賴的普通 Jar 包,一般包含外掛需要和其他外掛或者業務有隔離需求的 Jar 包;外掛配置的匯出類都包含在這些 Jar 包中。

2.2 分析sofa-ark-plugin-maven-plugin原始碼

進入下載的sofa-ark原始碼中的ark-plugin-maven-plugin工程,可以看到ArkPluginMojo繼承了

@Mojo(name = "ark-plugin", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME)
public class ArkPluginMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.artifactId}")
    public String                   pluginName;

    @Parameter(defaultValue = "100", property = "sofa.ark.plugin.priority")
    protected Integer               priority;

    @Parameter
    protected String                activator;

    @Parameter
    protected ExportConfig          exported;

    @Parameter
    protected ImportConfig          imported;
...
複製程式碼

@Mojo 就是一個 goal,可以繫結到某個 phase (這裡是package)執行,@Parameter 是從外面傳進來的引數,可以直接獲取xml中配置的引數。Maven外掛標準要求必須重寫execute()方法,外掛執行時主要就是執行execute()

    @Override
    public void execute() throws MojoExecutionException 
    {
        Archiver archiver;//zip歸檔
        archiver = getArchiver();
        outputDirectory.mkdirs();
        String fileName = getFileName();
        File destination = new File(outputDirectory, fileName);
        archiver.setDestFile(destination);
        Set<Artifact> artifacts = project.getArtifacts();
        artifacts = filterExcludeArtifacts(artifacts);
        Set<Artifact> conflictArtifacts = filterConflictArtifacts(artifacts);
        addArkPluginArtifact(archiver, artifacts, conflictArtifacts);
        addArkPluginConfig(archiver);
      	archiver.createArchive();
        projectHelper.attachArtifact(project, destination, getClassifier());
    }
複製程式碼

這裡的將execute()方法進行了精簡,這個方法主要做了一下幾件事情:

  1. 建立一個zip格式的歸檔,用來儲存引入的jar包和其他檔案,建立輸出路徑。
  2. 獲取引入的所有依賴(Artifacts),並且將需要exclude的包排除出去。
  3. 將所有依賴寫入zip歸檔中的lib目錄下

image-20180725153520102

  1. 將配置資訊寫入zip歸檔中,包括之前提到的export.indexMANIFEST.MFmark
    image-20180725153659204

經過上述步驟後即把依賴的dependence和配置檔案都寫入zip中了,然後將其轉換為jar字尾即可。

三. Sofa-ark原理分析

3.1 初始化ArkContainer

前面講解了plugin外掛如何工作,這節講述外部工程是如何引用運用plugin外掛打包而成的plugin jar包來解決隔離衝突的。可以從main方法加入的SofaArkBootstrap.launch(args)進行單步跟蹤,這句程式碼主要是將Container啟動起來,然後讓Container去載入Plugin和Biz。在launch方法裡通過反射呼叫了SofaArkBootstrapremain方法,在remain方法裡主要乾了兩件事:

 private static void remain(String[] args) throws Exception {// NOPMD
        URL[] urls = getURLClassPath();
        new ClasspathLauncher(new ClassPathArchive(urls)).launch(args, getClasspath(urls),
            entryMethod.getMethod());
    }
複製程式碼
  1. 獲取classpath下的所有jar包,包括jdk自己的jar包和maven引入的jar包。
  2. 將所有依賴jar包和自己寫的啟動類及其main函式以url的形式傳入ClasspathLauncherClasspathLauncher反射呼叫ArkContainermain方法,並且使用ContainerClassLoader載入ArkContainer。至此,就開始啟動ArkContainer了。

3.2 啟動ArkContainer

接著就執行到了ArkContainer中的main方法,傳入的引數args即之前ClasspathLauncher傳入的url

public static Object main(String[] args) throws ArkException
    {
            //使用LaunchCommand將傳入的引數按型別分類
            LaunchCommand launchCommand = LaunchCommand.parse(args[ARK_COMMAND_ARG_INDEX], Arrays.copyOfRange(args, MINIMUM_ARGS_SIZE, args.length));
            //ClassPathArchive將傳入依賴的Jar包分類,並提供獲得plugin和biz的filter方法
            ClassPathArchive classPathArchive = new ClassPathArchive(launchCommand.getEntryClassName(), launchCommand.getEntryMethodName(), launchCommand.getEntryMethodDescriptor(), launchCommand.getClasspath());
            return new ArkContainer(classPathArchive, launchCommand).start();
            }
    }
複製程式碼

這個方法主要做了一下幾件事:

  1. 使用LaunchCommand將傳入的引數分類,將classpath的url和自己寫的啟動類的main方法提取出來
    image-20180726151249913
  2. LaunchCommand傳入ArkContainer並啟動:

ArkContainer.start()中:

public Object start() throws ArkException
    {
        if (started.compareAndSet(false, true))
        {
            arkServiceContainer.start();
            Pipeline pipeline = arkServiceContainer.getService(Pipeline.class);
            pipeline.process(pipelineContext);
        }
        return this;
    }
複製程式碼

arkServiceContainer中包含了一些Container啟動前需要執行的Service,這些Service被封裝到一個個的PipelineStage中,這些PipelineStage又被封裝成List到一個pipeline中。主要包含這麼幾個PipelineStage,依次執行:

  1. HandleArchiveStage篩選所有第三方jar包中含有mark標記的plugin jar,說明這些jar是sofa ark maven外掛打包成的需要隔離的jar。從jar中的export.index中提取需要隔離的類,把他們加入一個PluginList中,並給每個plugin,分配一個獨立的PluginClassLoader。同時以同樣的操作給Biz也分配一個BizClassLoader
  2. DeployPluginStage 建立一個map,key是需要隔離的類,value是這個載入這個類使用的PluginClassLoader例項。
  3. DeployBizStage 使用BizClassLoader反射呼叫Biz的main方法。

至此,Container就啟動完了。後面再呼叫需要隔離的類時,由於啟動Biz的執行緒已經被換成了BizClassLoader,在loadClass時BizClassLoader會首先看看在DeployPluginStage建立的Map中是否有PluginClassLoader能載入這個類,如果能就委託PluginClassLoader載入。就實現了不同類使用不同的類載入器載入。

相關文章