Mengelola dependensi Java dan Scala untuk Apache Spark

Aplikasi Spark sering kali bergantung pada library Java atau Scala pihak ketiga. Berikut pendekatan yang direkomendasikan untuk menyertakan dependensi ini saat Anda mengirimkan tugas Spark ke cluster Dataproc:

  1. Saat mengirimkan tugas dari komputer lokal dengan perintah gcloud dataproc jobs submit, gunakan flag --properties spark.jars.packages=[DEPENDENCIES].

    Contoh:

    gcloud dataproc jobs submit spark \
        --cluster=my-cluster \
        --region=region \
        --properties=spark.jars.packages='com.google.cloud:google-cloud-translate:1.35.0,org.apache.bahir:spark-streaming-pubsub_2.11:2.2.0'
    

  2. Saat mengirimkan tugas secara langsung di cluster Anda, gunakan perintah spark-submit dengan parameter --packages=[DEPENDENCIES].

    Contoh:

    spark-submit --packages='com.google.cloud:google-cloud-translate:1.35.0,org.apache.bahir:spark-streaming-pubsub_2.11:2.2.0'
    

Menghindari konflik dependensi

Pendekatan sebelumnya mungkin gagal jika dependensi aplikasi Spark berkonflik dengan dependensi Hadoop. Konflik ini dapat muncul karena Hadoop menyuntikkan dependensinya ke classpath aplikasi, sehingga dependensinya lebih diutamakan daripada dependensi aplikasi. Jika terjadi konflik, NoSuchMethodError atau error lainnya dapat terjadi.

Contoh:
Guava adalah library inti Google untuk Java yang digunakan oleh banyak library dan framework, termasuk Hadoop. Konflik dependensi dapat terjadi jika tugas atau dependensinya memerlukan versi Guava yang lebih baru daripada yang digunakan oleh Hadoop.

Hadoop v3.0 menyelesaikan masalah ini , tetapi aplikasi yang mengandalkan versi Hadoop sebelumnya memerlukan solusi dua bagian berikut untuk menghindari kemungkinan konflik dependensi.

  1. Buat satu JAR yang berisi paket aplikasi dan semua dependensinya.
  2. Pindahkan paket dependensi yang berkonflik dalam JAR uber untuk mencegah nama jalur paket tersebut berkonflik dengan nama jalur paket dependensi Hadoop. Daripada mengubah kode, gunakan plugin (lihat di bawah) untuk otomatis melakukan pemindahan ini (alias "shading") sebagai bagian dari proses pengemasan.

Membuat JAR uber terinkorporasi dengan Maven

Maven adalah alat pengelolaan paket untuk mem-build aplikasi Java. Plugin Maven scala dapat digunakan untuk membangun aplikasi yang ditulis dalam Scala, bahasa yang digunakan oleh aplikasi Spark. Plugin Maven shade dapat digunakan untuk membuat JAR yang terinkorporasi.

Berikut adalah contoh file konfigurasi pom.xml yang meng-shade library Guava, yang berada di paket com.google.common. Konfigurasi ini mengarahkan Maven untuk mengganti nama paket com.google.common menjadi repackaged.com.google.common dan memperbarui semua referensi ke class dari paket asli.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <groupId><!-- YOUR_GROUP_ID --></groupId>
  <artifactId><!-- YOUR_ARTIFACT_ID --></artifactId>
  <version><!-- YOUR_PACKAGE_VERSION --></version>

  <dependencies>

    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-sql_2.11</artifactId>
      <version><!-- YOUR_SPARK_VERSION --></version>
      <scope>provided</scope>
    </dependency>

    <!-- YOUR_DEPENDENCIES -->

  </dependencies>

  <build>
    <plugins>

      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <scalaVersion><!-- YOUR_SCALA_VERSION --></scalaVersion>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass><!-- YOUR_APPLICATION_MAIN_CLASS --></mainClass>
                </transformer>
                <!-- This is needed if you have dependencies that use Service Loader. Most Google Cloud client libraries do. -->
                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              </transformers>
              <filters>
                <filter>
                  <artifact>*:*</artifact>
                  <excludes>
                    <exclude>META-INF/maven/**</exclude>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                  </excludes>
                </filter>
              </filters>
              <relocations>
                <relocation>
                  <pattern>com</pattern>
                  <shadedPattern>repackaged.com.google.common</shadedPattern>
                  <includes>
                    <include>com.google.common.**</include>
                  </includes>
                </relocation>
              </relocations>
            </configuration>
          </execution>
        </executions>
      </plugin>

    </plugins>
  </build>

</project>

Untuk menjalankan build:

mvn package

Catatan tentang pom.xml:

  • ManifestResourceTransformer memproses atribut dalam file manifes uber JAR (MANIFEST.MF). Manifes juga dapat menentukan titik entri untuk aplikasi Anda.
  • Cakupan Spark adalah provided, karena Spark diinstal di Dataproc.
  • Tentukan versi Spark yang diinstal di cluster Dataproc Anda (lihat Daftar Versi Dataproc). Jika aplikasi Anda memerlukan versi Spark yang berbeda dengan versi yang diinstal di cluster Dataproc, Anda dapat menulis tindakan inisialisasi atau membuat image kustom yang menginstal versi Spark yang digunakan oleh aplikasi Anda.
  • Entri <filters> mengecualikan file tanda tangan dari direktori META-INF dependensi Anda. Tanpa entri ini, pengecualian runtime java.lang.SecurityException: Invalid signature file digest for Manifest main attributes dapat terjadi karena file tanda tangan tidak valid dalam konteks uber JAR Anda.
  • Anda mungkin perlu menaungi beberapa library. Untuk melakukannya, sertakan beberapa jalur. Contoh berikutnya akan mengarsir library Guava dan Protobuf.
    <relocation>
      <pattern>com</pattern>
      <shadedPattern>repackaged.com</shadedPattern>
      <includes>
        <include>com.google.protobuf.**</include>
        <include>com.google.common.**</include>
      </includes>
    </relocation>

Membuat JAR uber yang di-shade dengan SBT

SBT adalah alat untuk membuat aplikasi Scala. Untuk membuat JAR yang di-shade dengan SBT, tambahkan plugin sbt-assembly ke definisi build Anda, pertama dengan membuat file bernama assembly.sbt di direktori project/:

├── src/
└── build.sbt
└── project/
    └── assembly.sbt

... lalu dengan menambahkan baris berikut di assembly.sbt:

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")

Berikut adalah contoh file konfigurasi build.sbt yang menyertakan library Guava, yang terletak di com.google.common package:

lazy val commonSettings = Seq(
 organization := "YOUR_GROUP_ID",
 name := "YOUR_ARTIFACT_ID",
 version := "YOUR_PACKAGE_VERSION",
 scalaVersion := "YOUR_SCALA_VERSION",
)

lazy val shaded = (project in file("."))
 .settings(commonSettings)

mainClass in (Compile, packageBin) := Some("YOUR_APPLICATION_MAIN_CLASS")

libraryDependencies ++= Seq(
 "org.apache.spark" % "spark-sql_2.11" % "YOUR_SPARK_VERSION" % "provided",
 // YOUR_DEPENDENCIES
)

assemblyShadeRules in assembly := Seq(
  ShadeRule.rename("com.google.common.**" -> "repackaged.com.google.common.@1").inAll
)

Untuk menjalankan build:

sbt assembly

Catatan tentang build.sbt:

  • Aturan shade dalam contoh sebelumnya mungkin tidak menyelesaikan semua konflik dependensi karena SBT menggunakan strategi penyelesaian konflik yang ketat. Oleh karena itu, Anda mungkin perlu memberikan aturan yang lebih terperinci yang secara eksplisit menggabungkan jenis file yang bertentangan tertentu menggunakan strategi MergeStrategy.first, last, concat, filterDistinctLines, rename, atau discard. Lihat strategi penggabungan sbt-assembly untuk mengetahui detail selengkapnya.
  • Anda mungkin perlu menaungi beberapa library. Untuk melakukannya, sertakan beberapa jalur. Contoh berikutnya akan mengarsir library Guava dan Protobuf.
    assemblyShadeRules in assembly := Seq(
      ShadeRule.rename("com.google.common.**" -> "repackaged.com.google.common.@1").inAll,
      ShadeRule.rename("com.google.protobuf.**" -> "repackaged.com.google.protobuf.@1").inAll
    )

Kirim JAR uber ke Dataproc

Setelah membuat JAR uber yang terinkorporasi dan berisi aplikasi Spark serta dependensinya, Anda siap mengirimkan tugas ke Dataproc.

Langkah berikutnya