web-dev-qa-db-fra.com

Comment accéder correctement à dbutils dans Scala lors de l'utilisation de Databricks Connect

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.

6
emzero

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 :)

1
VBel