## 一、簡介
在將 Storm Topology 提交到服務器集群運行時,需要先將項目進行打包。本文主要對比分析各種打包方式,并將打包過程中需要注意的事項進行說明。主要打包方式有以下三種:
* 第一種:不加任何插件,直接使用 mvn package 打包;
* 第二種:使用 maven-assembly-plugin 插件進行打包;
* 第三種:使用 maven-shade-plugin 進行打包。
以下分別進行詳細的說明。
## 二、mvn package
### 2.1 mvn package的局限
不在 POM 中配置任何插件,直接使用 `mvn package` 進行項目打包,這對于沒有使用外部依賴包的項目是可行的。
但如果項目中使用了第三方 JAR 包,就會出現問題,因為 `mvn package` 打包后的 JAR 中是不含有依賴包的,如果此時你提交到服務器上運行,就會出現找不到第三方依賴的異常。
如果你想采用這種方式進行打包,但是又使用了第三方 JAR,有沒有解決辦法?答案是有的,這一點在官方文檔的[Command Line Client](http://storm.apache.org/releases/2.0.0-SNAPSHOT/Command-line-client.html) 章節有所講解,主要解決辦法如下。
### 2.2 解決辦法
在使用 `storm jar` 提交 Topology 時,可以使用如下方式指定第三方依賴:
* 如果第三方 JAR 包在本地,可以使用 `--jars` 指定;
* 如果第三方 JAR 包在遠程中央倉庫,可以使用 `--artifacts` 指定,此時如果想要排除某些依賴,可以使用 `^` 符號。指定后 Storm 會自動到中央倉庫進行下載,然后緩存到本地;
* 如果第三方 JAR 包在其他倉庫,還需要使用 `--artifactRepositories` 指明倉庫地址,庫名和地址使用 `^` 符號分隔。
以下是一個包含上面三種情況的命令示例:
~~~
./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,即包含所有相關依賴,此時可以采用下面介紹的兩個插件。
## 三、maven-assembly-plugin插件
maven-assembly-plugin 是官方文檔中介紹的打包方法,來源于官方文檔:[Running Topologies on a Production Cluster](http://storm.apache.org/releases/2.0.0-SNAPSHOT/Running-topologies-on-a-production-cluster.html)
> If you're using Maven, the [Maven Assembly Plugin](http://maven.apache.org/plugins/maven-assembly-plugin/) can do the packaging for you. Just add this to your pom.xml:
>
> ~~~
> <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](http://maven.apache.org/plugins/maven-assembly-plugin/examples/single/including-and-excluding-artifacts.html) 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[預定義](http://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html#jar-with-dependencies) 的一種最基本的打包配置,其 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](http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html#)
### 2\. 打包命令
采用 maven-assembly-plugin 進行打包時命令如下:
~~~
# mvn assembly:assembly
~~~
打包后會同時生成兩個 JAR 包,其中后綴為 `jar-with-dependencies` 是含有第三方依賴的 JAR 包,后綴是由 `assembly.xml` 中 `<id>` 標簽指定的,可以自定義修改。提交該 JAR 到集群環境即可直接使用。

## 四、maven-shade-plugin插件
### 4.1 官方文檔說明
第三種方式是使用 maven-shade-plugin,既然已經有了 maven-assembly-plugin,為什么還需要 maven-shade-plugin,這一點在官方文檔中也是有所說明的,來自于官方對 HDFS 整合講解的章節[Storm HDFS Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html),原文如下:
> When packaging your topology, it's important that you use the [maven-shade-plugin](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html) as opposed to the [maven-assembly-plugin](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html).
>
> 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。

## 五、結論
通過以上三種打包方式的詳細介紹,這里給出最后的結論:**建議使用 maven-shade-plugin 插件進行打包**,因為其通用性最強,操作最簡單,并且 Storm Github 中所有[examples](https://github.com/apache/storm/tree/master/examples) 都是采用該方式進行打包。
## 六、打包注意事項
無論采用任何打包方式,都必須排除集群環境中已經提供的 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
~~~

作者:heibaiying
鏈接:https://juejin.cn/post/6844903950034944014
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊