J'utilise Databricks Connect pour exécuter le code dans mon cluster Azure Databricks localement à partir d'IntelliJ IDEA (Scala).
Tout fonctionne bien. Je peux me connecter, déboguer, inspecter localement dans l'IDE.
J'ai créé un travail Databricks pour exécuter mon JAR d'application personnalisée, mais il échoue à l'exception suivante:
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)
La ligne 30 de ma classe Main.scala est
val dbutils: DBUtils.type = com.databricks.service.DBUtils
Tout comme c'est décrit sur cette page de documentation
Ces pages montrent un moyen d'accéder à DBUtils qui fonctionne à la fois localement et dans le cluster. Mais l'exemple ne montre que Python, et j'utilise Scala.
Quelle est la bonne façon d'y accéder d'une manière qui fonctionne à la fois localement en utilisant databricks-connect et dans un travail Databricks exécutant un JAR?
[~ # ~] mise à jour [~ # ~]
Il semble qu'il existe deux façons d'utiliser DBUtils.
1) La classe DbUtils décrite ici . Citant les documents, cette bibliothèque vous permet de construire et de compiler le projet, mais pas de l'exécuter. Cela ne vous permet pas d'exécuter votre code local sur le cluster.
2) Le Databricks Connect décrit ici . Celui-ci vous permet d'exécuter votre code local Spark dans un cluster Databricks.
Le problème est que ces deux méthodes ont des configurations et un nom de package différents. Il ne semble pas y avoir de moyen d'utiliser Databricks Connect localement (qui n'est pas disponible dans le cluster), mais ajoutez ensuite l'application jar utilisant la classe DbUtils via sbt/maven pour que le cluster y ait accès.
Je ne sais pas pourquoi les documents que vous avez mentionnés ne fonctionnent pas. Peut-être que vous utilisez une dépendance différente?
Ces documents ont un exemple d'application que vous pouvez télécharger . C'est un projet avec un test très minimal, donc il ne crée pas de travaux ou essaie de les exécuter sur le cluster - mais c'est un début. Veuillez également noter qu'il utilise l'ancien 0.0.1
version de dbutils-api
.
Donc, pour résoudre votre problème actuel, au lieu d'utiliser com.databricks.service.DBUtils
, essayez d'importer le dbutils
depuis un autre endroit:
import com.databricks.dbutils_v1.DBUtilsHolder.dbutils
Ou, si vous préférez:
import com.databricks.dbutils_v1.{DBUtilsV1, DBUtilsHolder}
type DBUtils = DBUtilsV1
val dbutils: DBUtils = DBUtilsHolder.dbutils
Assurez-vous également que vous disposez des dépendances suivantes dans SBT (essayez peut-être de jouer avec les versions si 0.0.3
ne fonctionne pas - le dernier est 0.0.4
):
libraryDependencies += "com.databricks" % "dbutils-api_2.11" % "0.0.3"
Cette question et réponse m'a dirigé dans la bonne direction. La réponse contient un lien vers un dépôt Github qui utilise dbutils
: waimak . J'espère que ce dépôt pourrait vous aider dans d'autres questions sur la configuration et les dépendances de Databricks.
Bonne chance!
[~ # ~] mise à jour [~ # ~]
Je vois, nous avons donc deux API similaires mais pas identiques, et aucun bon moyen de basculer entre la version locale et la version backend (bien que Databricks Connect promet que cela devrait fonctionner de toute façon). Veuillez me laisser proposer une solution de contournement.
Il est bon que Scala est pratique pour écrire des adaptateurs. Voici un extrait de code qui devrait fonctionner comme un pont - il y a l'objet DBUtils
défini ici qui fournit une abstraction API suffisante pour les deux versions de l'API: celle de Databricks Connect sur com.databricks.service.DBUtils
, et le backend com.databricks.dbutils_v1.DBUtilsHolder.dbutils
API. Nous pouvons y parvenir en chargeant puis en utilisant le com.databricks.service.DBUtils
par réflexion - nous n'en avons pas d'importations codées en dur.
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 }
}
Si vous remplacez le val dbutils: DBUtils.type = com.databricks.service.DBUtils
que vous avez mentionné dans votre Main
avec val dbutils: DBUtils.type = com.example.my.proxy.adapter.DBUtils
, tout devrait fonctionner comme un remplacement, localement et à distance.
Si vous avez de nouveaux NoClassDefFoundError
, essayez d'ajouter des dépendances spécifiques au travail JAR, ou essayez de les réorganiser, de changer les versions ou de marquer les dépendances comme indiqué.
Cet adaptateur n'est pas joli et il utilise la réflexion, mais il devrait être suffisant comme solution de contournement, j'espère. Bonne chance :)