【Scala】実行可能なJARの作成(開発環境はEclipse)
-
カテゴリ:
- Scala
-
タグ:
- #Spark
Scalaは基本コンパイルして動かす言語ですが、標準の機能としてShellのようにコマンドから実行することも可能です。(ただその場合も結局裏側でコンパイルして動いています。)
運用や検証時など手動で処理を実行する場合は、コマンドラインから実行するだけで事足りますが、ほかのシステムと連携したり、プログラムとして組み込む場合は別の方法で実行する必要があります。
実行方法としては一つの機能をJARファイルに固めて実行する形となります。今回はSparkでプログラミングした時の構成を例に説明します。(Sparkは裏側はScalaで動かしています。)
設定する中でエラーになったことに関して、そのままの流れで説明していきます。さらにメモ書きベースになっているのでご注意ください。
ディレクトリ構成
spark_test/ (プロジェクトトップ)
|_ project/
| |_ build.properties
| |_ assembly.sbt
| ・・・
|_ src/
| |_ main/
| |_ scala/
| |_ sample/
| |_ InsertYm.scala
| ・・・
|_ target/
| |_resolution-cache/
| |_streams/
| |_scala-2.11/
| |_classes/
| |_sample-assembly-0.01.jar (実行可能なJAR)
| |_sample-0.01.jar (実行可能じゃないJAR??)
|_ build.sbt (sbtビルダーの設定ファイル)
設定手順
1.ディレクトリ構成の準備
cd ~/cas_test/spark/
mkdir spark_test
touch spark_test/build.sbt
touch spark_test/build.sbt
mkdir spark_test/src
mkdir spark_test/src/main
mkdir spark_test/src/main/scala
mkdir spark_test/src/main/scala/sample
cp ~/eclipse/workspace/spark_test/src/sample/InsertYmd.scala ~/cas_test/spark/spark_test/src/main/scala/sample/InsertYmd.scala
cp ~/eclipse/workspace/spark_test/src/sample/InsertYm.scala ~/cas_test/spark/spark_test/src/main/scala/sample/InsertYm.scala
cp ~/eclipse/workspace/spark_test/src/sample/OutputYmd.scala ~/cas_test/spark/spark_test/src/main/scala/sample/OutputYmd.scala
cp ~/eclipse/workspace/spark_test/src/sample/OutputYm.scala ~/cas_test/spark/spark_test/src/main/scala/sample/OutputYm.scala
cp ~/eclipse/workspace/spark_test/src/sample/OutputYmd2.scala ~/cas_test/spark/spark_test/src/main/scala/sample/OutputYmd2.scala
cp ~/eclipse/workspace/spark_test/src/sample/OutputYm2.scala ~/cas_test/spark/spark_test/src/main/scala/sample/OutputYm2.scala
2.sbtビルダーの設定
sbtのインストール
curl https://bintray.com/sbt/rpm/rpm | sudo tee /etc/yum.repos.d/bintray-sbt-rpm.repo
sudo yum install sbt
vi ~/cas_test/spark_test/build.sbt (暫定版)
name := "spark_test"
version := "0.01"
scalaVersion := "2.11.8"
resolvers += (
"Local Maven Repository" at "/home/user/.m2/repository/"
)
libraryDependencies ++= Seq(
"org.apache.spark" % "spark-core_2.11" % "2.1.0"
,"org.apache.spark" % "spark-sql_2.11" % "2.1.0"
,"org.apache.spark" % "spark-mllib_2.11" % "2.1.0"
,"org.apache.spark" % "spark-graphx_2.11" % "2.1.0"
,"com.datastax.spark" % "spark-cassandra-connector_2.11" % "2.0.0-M3"
)
3.ビルド+jarファイルの作成
sbt clean package
[エラー1 ビルドがなんかうまくいかない]
⇒※オンライン接続した状態にする?
⇒build.sbtに記載したライブラリを起動時にネット上から取得しているっぽいから、オンラインの状態で作業する必要がある。
[エラー2 [error] Modules were resolved with conflicting cross-version suffixes in {file:/home/user/cas_test/spark/spark_test/}spark_test:]
[error] org.scala-lang.modules:scala-xml _2.11, _2.12
⇒原因は、scalaのバージョン間違い。build.sbtの「scalaVersion := "2.12.1"」
⇒「scalaVersion := "2.11.8"」にする
再度実行したら一様JARファイルは作成された。(ただこのときJARの中にクラスが何も入っていないことは知る由もなかった。。。><;)
4.環境変数を通す
~/.bash_profileに[JAVA_HOME]を追記(jvmの場所:/usr/lib/jvm/java-1.6.0-openjdk.x86_64)
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
# JAVA_HOME=/usr/lib/jvm/java-1.6.0-openjdk.x86_64
JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-2.b15.el7_3.x86_64
SPARK_HOME=/usr/local/lib/spark
PATH=$PATH:$HOME/.local/bin:$HOME/bin:$JAVA_HOME/bin:$SPARK_HOME/bin
export PATH
5.Jarからプログラムを実行
cd /home/user/cas_test/spark/target/scala-2.10
/usr/local/lib/spark/bin/spark-submit --class sample.InsertYmd --master local[4] ~/cas_test/spark/spark_test/target/scala-2.11/sample_2.11-0.01.jar
※「sample.InsertYmd」は自分で作ったサンプルソースのクラス名です。詳細は今回登場しません。。。
[エラー3 Exception in thread "main" java.lang.NoClassDefFoundError: com/datastax/spark/connector/mapper/ColumnMapper]
⇒要は読み込み対象のクラスが存在しない
⇒原因としてライブラリ依存が問題で、sbt は [Apache Ivy] を使ってマネージ依存性を実装しているので、eclipseなどでMavenを使用して依存関係を解決している場合クラスがうまく読み込まれない
⇒参考:https://www.playframework.com/documentation/ja/2.2.x/SBTDependencies、http://www.scala-sbt.org/0.13/docs/ja/Library-Dependencies.html
6.Sbtのプラグイン、assemblyの導入
作戦変更して、jarファイル作成には「assembly」を使用する。
sbt-assemblyはSBTのプラグインで、jarファイルを作る上で依存関係にあるライブラリをまとめるのに便利!
project/assembly.sbtを作成する
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.4")
7.実行可能なjarファイルを作成する
sbt assembly
[エラー4 実行中にエラー java.lang.RuntimeException: deduplicate: different file contents found in the following]
⇒対処法:build.sbtにMerge Strategyを追加する。
build.sbt(最終版)の以下の通りに修正
// ↓以下2行、初回にsbt assemblyするときにコメントアウト外して実行してください
import AssemblyKeys._ // put this at the top of the file
assemblySettings
lazy val root = (project in file(".")).
settings(
name := "sample"
, version := "0.01"
, scalaVersion := "2.11.8"
)
libraryDependencies ++= Seq(
"org.apache.spark" % "spark-core_2.11" % "2.1.0"
,"org.apache.spark" % "spark-sql_2.11" % "2.1.0"
,"org.apache.spark" % "spark-mllib_2.11" % "2.1.0"
,"org.apache.spark" % "spark-graphx_2.11" % "2.1.0"
,"com.datastax.spark" % "spark-cassandra-connector_2.11" % "2.0.0-M3"
)
resolvers ++= Seq(
"Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
,"Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/"
)
assemblyMergeStrategy in assembly := {
case PathList("javax", "servlet", xs @ _*) => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith ".properties" => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith ".xml" => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith ".types" => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith ".class" => MergeStrategy.first
case "application.conf" => MergeStrategy.concat
case "unwanted.txt" => MergeStrategy.discard
case x =>
val oldStrategy = (assemblyMergeStrategy in assembly).value
oldStrategy(x)
}
8.再度実行可能なjarファイルを作成する
sbt assembly
⇒成功!!
sbt clean package assembly
9.jarから実行
/usr/local/lib/spark/bin/spark-submit --class sample.InsertYmd --master local[4] <jarファイルのパス>
⇒正常に実行!!!
./bin/spark-submit \
--class <main-class>
--master <master-url> \
--deploy-mode <deploy-mode> \
--conf <key>=<value> \
... # other options
<application-jar> \
[application-arguments(パラメータの設定)]