web-dev-qa-db-ja.com

Scalaでdbutilsに適切にアクセスする方法

Databricks Connectを使用して、Azure DatabricksクラスターでIntelliJからローカルでコードを実行していますIDEA(Scala))。

すべてが正常に動作します。 IDEでローカルに接続、デバッグ、検査できます。

Databricksジョブを作成してカスタムアプリJARを実行しましたが、次の例外で失敗します。

19/08/17 19:20:26 ERROR Uncaught throwable from user code: Java.lang.NoClassDefFoundError: com/databricks/service/DBUtils$
at Main$.<init>(Main.scala:30)
at Main$.<clinit>(Main.scala)

Main.scalaクラスの行30は

val dbutils: DBUtils.type = com.databricks.service.DBUtils

このドキュメントのページで説明されているように

そのページは、ローカルとクラスターの両方で機能するDBUtilsにアクセスする方法を示しています。しかし、この例はPythonのみを示しており、私はScalaを使用しています。

ローカルでdatabricks-connectを使用して機能する方法と、JARを実行するDatabricksジョブで機能する方法のどちらでアクセスするのが適切ですか?

[〜#〜]更新[〜#〜]

DBUtilsを使用するには2つの方法があるようです。

1)DbUtilsクラスは ここに記述されています 。ドキュメントを引用すると、このライブラリを使用すると、プロジェクトをビルドしてコンパイルできますが、実行することはできません。これでは、クラスターでローカルコードを実行できません。

2)説明されているDatabricks Connectは こちら に記述されています。これにより、ローカルのSparkコードをDatabricksクラスターで実行できます。

問題は、これら2つの方法のセットアップとパッケージ名が異なることです。 Databricks Connectをローカルで使用する方法(クラスターでは使用できません)はないようですが、クラスターがアクセスできるように、sbt/mavenを介して追加されたDbUtilsクラスを使用するjarアプリケーションを使用します。

6
emzero

なぜだかわかりません あなたが言及したドキュメント 動作しません。多分あなたは別の依存関係を使用していますか?

これらのドキュメント できるサンプルアプリケーションがあります download 。これは非常に最小限のテストを行うプロジェクトなので、ジョブを作成したり、クラスターで実行しようとしたりはしませんが、それは始まりです。また、0.0.1の古いdbutils-apiバージョンを使用していることに注意してください。

したがって、現在の問題を修正するには、com.databricks.service.DBUtilsを使用する代わりに、dbutilsを別の場所からインポートしてみてください。

import com.databricks.dbutils_v1.DBUtilsHolder.dbutils

または、必要に応じて:

import com.databricks.dbutils_v1.{DBUtilsV1, DBUtilsHolder}

type DBUtils = DBUtilsV1
val dbutils: DBUtils = DBUtilsHolder.dbutils

また、SBTに次の依存関係があることを確認してください(0.0.3が機能しない場合は、バージョンを試してみてください。最新のバージョンは0.0.4です)。

libraryDependencies += "com.databricks" % "dbutils-api_2.11" % "0.0.3"

この質問と回答 は私を正しい方向に向けました。答えには、dbutilswaimak を使用する作業Githubリポジトリへのリンクが含まれています。このレポがDatabricksの構成と依存関係に関するさらなる質問に役立つことを願っています。

幸運を!


[〜#〜]更新[〜#〜]

わかりました。2つの類似したAPIがありますが、同じではありません。ローカルバージョンとバックエンドバージョンを切り替える適切な方法はありません(ただし、Databricks Connectは、なんらかの動作を保証しています)。回避策を提案させてください。

Scalaはアダプタの作成に便利です。ここにブリッジとして機能するコードスニペットがあります。ここで定義されているDBUtilsオブジェクトが、十分なAPI抽象化を提供します。 APIの2つのバージョン:Databricks Connect one on com.databricks.service.DBUtils、およびバックエンドcom.databricks.dbutils_v1.DBUtilsHolder.dbutils AP​​I。ロードとその後のリフレクションによるcom.databricks.service.DBUtilsの両方の使用により、これを実現できます- -ハードコードされたインポートはありません。

package com.example.my.proxy.adapter

import org.Apache.hadoop.fs.FileSystem
import org.Apache.spark.sql.catalyst.DefinedByConstructorParams

import scala.util.Try

import scala.language.implicitConversions
import scala.language.reflectiveCalls


trait DBUtilsApi {
    type FSUtils
    type FileInfo

    type SecretUtils
    type SecretMetadata
    type SecretScope

    val fs: FSUtils
    val secrets: SecretUtils
}

trait DBUtils extends DBUtilsApi {
    trait FSUtils {
        def dbfs: org.Apache.hadoop.fs.FileSystem
        def ls(dir: String): Seq[FileInfo]
        def rm(dir: String, recurse: Boolean = false): Boolean
        def mkdirs(dir: String): Boolean
        def cp(from: String, to: String, recurse: Boolean = false): Boolean
        def mv(from: String, to: String, recurse: Boolean = false): Boolean
        def head(file: String, maxBytes: Int = 65536): String
        def put(file: String, contents: String, overwrite: Boolean = false): Boolean
    }

    case class FileInfo(path: String, name: String, size: Long)

    trait SecretUtils {
        def get(scope: String, key: String): String
        def getBytes(scope: String, key: String): Array[Byte]
        def list(scope: String): Seq[SecretMetadata]
        def listScopes(): Seq[SecretScope]
    }

    case class SecretMetadata(key: String) extends DefinedByConstructorParams
    case class SecretScope(name: String) extends DefinedByConstructorParams
}

object DBUtils extends DBUtils {

    import Adapters._

    override lazy val (fs, secrets): (FSUtils, SecretUtils) = Try[(FSUtils, SecretUtils)](
        (ReflectiveDBUtils.fs, ReflectiveDBUtils.secrets)    // try to use the Databricks Connect API
    ).getOrElse(
        (BackendDBUtils.fs, BackendDBUtils.secrets)    // if it's not available, use com.databricks.dbutils_v1.DBUtilsHolder
    )

    private object Adapters {
        // The apparent code copying here is for performance -- the ones for `ReflectiveDBUtils` use reflection, while
        // the `BackendDBUtils` call the functions directly.

        implicit class FSUtilsFromBackend(underlying: BackendDBUtils.FSUtils) extends FSUtils {
            override def dbfs: FileSystem = underlying.dbfs
            override def ls(dir: String): Seq[FileInfo] = underlying.ls(dir).map(fi => FileInfo(fi.path, fi.name, fi.size))
            override def rm(dir: String, recurse: Boolean = false): Boolean = underlying.rm(dir, recurse)
            override def mkdirs(dir: String): Boolean = underlying.mkdirs(dir)
            override def cp(from: String, to: String, recurse: Boolean = false): Boolean = underlying.cp(from, to, recurse)
            override def mv(from: String, to: String, recurse: Boolean = false): Boolean = underlying.mv(from, to, recurse)
            override def head(file: String, maxBytes: Int = 65536): String = underlying.head(file, maxBytes)
            override def put(file: String, contents: String, overwrite: Boolean = false): Boolean = underlying.put(file, contents, overwrite)
        }

        implicit class FSUtilsFromReflective(underlying: ReflectiveDBUtils.FSUtils) extends FSUtils {
            override def dbfs: FileSystem = underlying.dbfs
            override def ls(dir: String): Seq[FileInfo] = underlying.ls(dir).map(fi => FileInfo(fi.path, fi.name, fi.size))
            override def rm(dir: String, recurse: Boolean = false): Boolean = underlying.rm(dir, recurse)
            override def mkdirs(dir: String): Boolean = underlying.mkdirs(dir)
            override def cp(from: String, to: String, recurse: Boolean = false): Boolean = underlying.cp(from, to, recurse)
            override def mv(from: String, to: String, recurse: Boolean = false): Boolean = underlying.mv(from, to, recurse)
            override def head(file: String, maxBytes: Int = 65536): String = underlying.head(file, maxBytes)
            override def put(file: String, contents: String, overwrite: Boolean = false): Boolean = underlying.put(file, contents, overwrite)
        }

        implicit class SecretUtilsFromBackend(underlying: BackendDBUtils.SecretUtils) extends SecretUtils {
            override def get(scope: String, key: String): String = underlying.get(scope, key)
            override def getBytes(scope: String, key: String): Array[Byte] = underlying.getBytes(scope, key)
            override def list(scope: String): Seq[SecretMetadata] = underlying.list(scope).map(sm => SecretMetadata(sm.key))
            override def listScopes(): Seq[SecretScope] = underlying.listScopes().map(ss => SecretScope(ss.name))
        }

        implicit class SecretUtilsFromReflective(underlying: ReflectiveDBUtils.SecretUtils) extends SecretUtils {
            override def get(scope: String, key: String): String = underlying.get(scope, key)
            override def getBytes(scope: String, key: String): Array[Byte] = underlying.getBytes(scope, key)
            override def list(scope: String): Seq[SecretMetadata] = underlying.list(scope).map(sm => SecretMetadata(sm.key))
            override def listScopes(): Seq[SecretScope] = underlying.listScopes().map(ss => SecretScope(ss.name))
        }
    }
}

object BackendDBUtils extends DBUtilsApi {
    import com.databricks.dbutils_v1

    private lazy val dbutils: DBUtils = dbutils_v1.DBUtilsHolder.dbutils
    override lazy val fs: FSUtils = dbutils.fs
    override lazy val secrets: SecretUtils = dbutils.secrets

    type DBUtils = dbutils_v1.DBUtilsV1
    type FSUtils = dbutils_v1.DbfsUtils
    type FileInfo = com.databricks.backend.daemon.dbutils.FileInfo

    type SecretUtils = dbutils_v1.SecretUtils
    type SecretMetadata = dbutils_v1.SecretMetadata
    type SecretScope = dbutils_v1.SecretScope
}

object ReflectiveDBUtils extends DBUtilsApi {
    // This throws a ClassNotFoundException when the Databricks Connection API isn't available -- it's much better than
    // the NoClassDefFoundError, which we would get if we had a hard-coded import of com.databricks.service.DBUtils .
    // As we're just using reflection, we're able to recover if it's not found.
    private lazy val dbutils: DBUtils =
        Class.forName("com.databricks.service.DBUtils$").getField("MODULE$").get().asInstanceOf[DBUtils]

    override lazy val fs: FSUtils = dbutils.fs
    override lazy val secrets: SecretUtils = dbutils.secrets

    type DBUtils = AnyRef {
        val fs: FSUtils
        val secrets: SecretUtils
    }

    type FSUtils = AnyRef {
        def dbfs: org.Apache.hadoop.fs.FileSystem
        def ls(dir: String): Seq[FileInfo]
        def rm(dir: String, recurse: Boolean): Boolean
        def mkdirs(dir: String): Boolean
        def cp(from: String, to: String, recurse: Boolean): Boolean
        def mv(from: String, to: String, recurse: Boolean): Boolean
        def head(file: String, maxBytes: Int): String
        def put(file: String, contents: String, overwrite: Boolean): Boolean
    }

    type FileInfo = AnyRef {
        val path: String
        val name: String
        val size: Long
    }

    type SecretUtils = AnyRef {
        def get(scope: String, key: String): String
        def getBytes(scope: String, key: String): Array[Byte]
        def list(scope: String): Seq[SecretMetadata]
        def listScopes(): Seq[SecretScope]
    }

    type SecretMetadata = DefinedByConstructorParams { val key: String }

    type SecretScope = DefinedByConstructorParams { val name: String }
}

Mainで言及したval dbutils: DBUtils.type = com.databricks.service.DBUtilsval dbutils: DBUtils.type = com.example.my.proxy.adapter.DBUtilsに置き換えると、すべてがローカルでもリモートでもドロップイン置換として機能するはずです。

新しいNoClassDefFoundErrorsがある場合は、JARジョブに特定の依存関係を追加するか、それらを再配置するか、バージョンを変更するか、提供されているように依存関係をマークしてください。

このアダプターはきれいではなく、リフレクションを使用しますが、回避策としては十分なはずです。幸運を :)

1
VBel