Storm 系列(六)—— Storm 項目三種打包方式對比分析

1、簡介

在將 Storm Topology 提交到服務器集羣運行時,須要先將項目進行打包。本文主要對比分析各類打包方式,並將打包過程當中須要注意的事項進行說明。主要打包方式有如下三種:html

  • 第一種:不加任何插件,直接使用 mvn package 打包;
  • 第二種:使用 maven-assembly-plugin 插件進行打包;
  • 第三種:使用 maven-shade-plugin 進行打包。

如下分別進行詳細的說明。java

2、mvn package

2.1 mvn package的侷限

不在 POM 中配置任何插件,直接使用 mvn package 進行項目打包,這對於沒有使用外部依賴包的項目是可行的。git

但若是項目中使用了第三方 JAR 包,就會出現問題,由於 mvn package 打包後的 JAR 中是不含有依賴包的,若是此時你提交到服務器上運行,就會出現找不到第三方依賴的異常。github

若是你想採用這種方式進行打包,可是又使用了第三方 JAR,有沒有解決辦法?答案是有的,這一點在官方文檔的Command Line Client 章節有所講解,主要解決辦法以下。redis

2.2 解決辦法

在使用 storm jar 提交 Topology 時,可使用以下方式指定第三方依賴:shell

  • 若是第三方 JAR 包在本地,可使用 --jars 指定;
  • 若是第三方 JAR 包在遠程中央倉庫,可使用 --artifacts 指定,此時若是想要排除某些依賴,可使用 ^ 符號。指定後 Storm 會自動到中央倉庫進行下載,而後緩存到本地;
  • 若是第三方 JAR 包在其餘倉庫,還須要使用 --artifactRepositories 指明倉庫地址,庫名和地址使用 ^ 符號分隔。

如下是一個包含上面三種狀況的命令示例:apache

./bin/storm jar example/storm-starter/storm-starter-topologies-*.jar \
org.apache.storm.starter.RollingTopWords blobstore-remote2 remote  \
--jars "./external/storm-redis/storm-redis-1.1.0.jar,./external/storm-kafka/storm-kafka-1.1.0.jar" \
--artifacts "redis.clients:jedis:2.9.0,org.apache.kafka:kafka_2.10:0.8.2.2^org.slf4j:slf4j-log4j12" \
--artifactRepositories "jboss-repository^http://repository.jboss.com/maven2, \
HDPRepo^http://repo.hortonworks.com/content/groups/public/"

這種方式是創建在你可以鏈接到外網的狀況下,若是你的服務器不能鏈接外網,或者你但願能把項目直接打包成一個 ALL IN ONE 的 JAR,即包含全部相關依賴,此時能夠採用下面介紹的兩個插件。緩存

3、maven-assembly-plugin插件

maven-assembly-plugin 是官方文檔中介紹的打包方法,來源於官方文檔:Running Topologies on a Production Cluster服務器

If you're using Maven, the Maven Assembly Plugin can do the packaging for you. Just add this to your pom.xml:app

<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
 <descriptorRefs>  
   <descriptorRef>jar-with-dependencies</descriptorRef>
 </descriptorRefs>
 <archive>
   <manifest>
     <mainClass>com.path.to.main.Class</mainClass>
   </manifest>
 </archive>
</configuration>
</plugin>

Then run mvn assembly:assembly to get an appropriately packaged jar. Make sure you exclude the Storm jars since the cluster already has Storm on the classpath.

官方文檔主要說明了如下幾點:

  • 使用 maven-assembly-plugin 能夠把全部的依賴一併打入到最後的 JAR 中;
  • 須要排除掉 Storm 集羣環境中已經提供的 Storm jars;
  • 經過 <mainClass> 標籤指定主入口類;
  • 經過 <descriptorRef> 標籤指定打包相關配置。

jar-with-dependencies 是 Maven預約義 的一種最基本的打包配置,其 XML 文件以下:

<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
                              http://maven.apache.org/xsd/assembly-2.0.0.xsd">
    <id>jar-with-dependencies</id>
    <formats>
        <format>jar</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <useProjectArtifact>true</useProjectArtifact>
            <unpack>true</unpack>
            <scope>runtime</scope>
        </dependencySet>
    </dependencySets>
</assembly>

咱們能夠經過對該配置文件進行拓展,從而實現更多的功能,好比排除指定的 JAR 等。使用示例以下:

1. 引入插件

在 POM.xml 中引入插件,並指定打包格式的配置文件爲 assembly.xml(名稱可自定義):

<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptors>
                    <descriptor>src/main/resources/assembly.xml</descriptor>
                </descriptors>
                <archive>
                    <manifest>
                        <mainClass>com.heibaiying.wordcount.ClusterWordCountApp</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

assembly.xml 拓展自 jar-with-dependencies.xml,使用了 <excludes> 標籤排除 Storm jars,具體內容以下:

<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 
                              http://maven.apache.org/xsd/assembly-2.0.0.xsd">
    
    <id>jar-with-dependencies</id>

    <!--指明打包方式-->
    <formats>
        <format>jar</format>
    </formats>

    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <useProjectArtifact>true</useProjectArtifact>
            <unpack>true</unpack>
            <scope>runtime</scope>
            <!--排除 storm 環境中已經提供的 storm-core-->
            <excludes>
                <exclude>org.apache.storm:storm-core</exclude>
            </excludes>
        </dependencySet>
    </dependencySets>
</assembly>

在配置文件中不只能夠排除依賴,還能夠排除指定的文件,更多的配置規則能夠參考官方文檔:Descriptor Format

2. 打包命令

採用 maven-assembly-plugin 進行打包時命令以下:

# mvn assembly:assembly

打包後會同時生成兩個 JAR 包,其中後綴爲 jar-with-dependencies 是含有第三方依賴的 JAR 包,後綴是由 assembly.xml<id> 標籤指定的,能夠自定義修改。提交該 JAR 到集羣環境便可直接使用。

4、maven-shade-plugin插件

4.1 官方文檔說明

第三種方式是使用 maven-shade-plugin,既然已經有了 maven-assembly-plugin,爲何還須要 maven-shade-plugin,這一點在官方文檔中也是有所說明的,來自於官方對 HDFS 整合講解的章節Storm HDFS Integration,原文以下:

When packaging your topology, it's important that you use the maven-shade-plugin as opposed to the maven-assembly-plugin.

The shade plugin provides facilities for merging JAR manifest entries, which the hadoop client leverages for URL scheme resolution.

If you experience errors such as the following:

java.lang.RuntimeException: Error preparing HdfsBolt: No FileSystem for scheme: hdfs

it's an indication that your topology jar file isn't packaged properly.

If you are using maven to create your topology jar, you should use the following maven-shade-plugin configuration to create your topology jar。

這裏第一句就說的比較清晰,在集成 HDFS 時候,你必須使用 maven-shade-plugin 來代替 maven-assembly-plugin,不然會拋出 RuntimeException 異常。

採用 maven-shade-plugin 打包有不少好處,好比你的工程依賴不少的 JAR 包,而被依賴的 JAR 又會依賴其餘的 JAR 包,這樣,當工程中依賴到不一樣的版本的 JAR 時,而且 JAR 中具備相同名稱的資源文件時,shade 插件會嘗試將全部資源文件打包在一塊兒時,而不是和 assembly 同樣執行覆蓋操做。

4.2 配置

採用 maven-shade-plugin 進行打包時候,配置示例以下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <configuration>
        <createDependencyReducedPom>true</createDependencyReducedPom>
        <filters>
            <filter>
                <artifact>*:*</artifact>
                <excludes>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.sf</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.dsa</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                    <exclude>META-INF/*.rsa</exclude>
                    <exclude>META-INF/*.EC</exclude>
                    <exclude>META-INF/*.ec</exclude>
                    <exclude>META-INF/MSFTSIG.SF</exclude>
                    <exclude>META-INF/MSFTSIG.RSA</exclude>
                </excludes>
            </filter>
        </filters>
        <artifactSet>
            <excludes>
                <exclude>org.apache.storm:storm-core</exclude>
            </excludes>
        </artifactSet>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer
                       implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                    <transformer
                       implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

以上配置示例來源於 Storm Github,這裏作一下說明:

在上面的配置中,排除了部分文件,這是由於有些 JAR 包生成時,會使用 jarsigner 生成文件簽名(完成性校驗),分爲兩個文件存放在 META-INF 目錄下:

  • a signature file, with a .SF extension;
  • a signature block file, with a .DSA, .RSA, or .EC extension;

若是某些包的存在重複引用,這可能會致使在打包時候出現 Invalid signature file digest for Manifest main attributes 異常,因此在配置中排除這些文件。

4.3 打包命令

使用 maven-shade-plugin 進行打包的時候,打包命令和普通的同樣:

# mvn  package

打包後會生成兩個 JAR 包,提交到服務器集羣時使用 非 original 開頭的 JAR。

5、結論

經過以上三種打包方式的詳細介紹,這裏給出最後的結論:建議使用 maven-shade-plugin 插件進行打包,由於其通用性最強,操做最簡單,而且 Storm Github 中全部examples 都是採用該方式進行打包。

6、打包注意事項

不管採用任何打包方式,都必須排除集羣環境中已經提供的 storm jars。這裏比較典型的是 storm-core,其在安裝目錄的 lib 目錄下已經存在。

若是你不排除 storm-core,一般會拋出下面的異常:

Caused by: java.lang.RuntimeException: java.io.IOException: Found multiple defaults.yaml resources.   
You're probably bundling the Storm jars with your topology jar.   
[jar:file:/usr/app/apache-storm-1.2.2/lib/storm-core-1.2.2.jar!/defaults.yaml,   
jar:file:/usr/appjar/storm-hdfs-integration-1.0.jar!/defaults.yaml]
        at org.apache.storm.utils.Utils.findAndReadConfigFile(Utils.java:384)
        at org.apache.storm.utils.Utils.readDefaultConfig(Utils.java:428)
        at org.apache.storm.utils.Utils.readStormConfig(Utils.java:464)
        at org.apache.storm.utils.Utils.<clinit>(Utils.java:178)
        ... 39 more

參考資料

關於 maven-shade-plugin 的更多配置能夠參考: maven-shade-plugin 入門指南

更多大數據系列文章能夠參見 GitHub 開源項目大數據入門指南

相關文章
相關標籤/搜索