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アプリケーションを使用します。
なぜだかわかりません あなたが言及したドキュメント 動作しません。多分あなたは別の依存関係を使用していますか?
これらのドキュメント できるサンプルアプリケーションがあります 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"
この質問と回答 は私を正しい方向に向けました。答えには、dbutils
: waimak を使用する作業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
API。ロードとその後のリフレクションによる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.DBUtils
をval dbutils: DBUtils.type = com.example.my.proxy.adapter.DBUtils
に置き換えると、すべてがローカルでもリモートでもドロップイン置換として機能するはずです。
新しいNoClassDefFoundError
sがある場合は、JARジョブに特定の依存関係を追加するか、それらを再配置するか、バージョンを変更するか、提供されているように依存関係をマークしてください。
このアダプターはきれいではなく、リフレクションを使用しますが、回避策としては十分なはずです。幸運を :)