web-dev-qa-db-fra.com

Comment gérer les exceptions dans Spark et Scala

J'essaie de gérer les exceptions courantes dans Spark, comme une opération .map ne fonctionnant pas correctement sur tous les éléments des données ou une exception FileNotFound. J'ai lu toutes les questions existantes et les deux posts suivants:

https://rcardin.github.io/big-data/Apache-spark/scala/programming/2016/09/25/try-again-Apache-spark.html

https://www.nicolaferraro.me/2016/02/18/exception-handling-in-Apache-spark

J'ai essayé une instruction Try dans la ligne attributes => mHealthUser(attributes(0).toDouble, attributes(1).toDouble, attributes(2).toDouble
donc il se lit attributes => Try(mHealthUser(attributes(0).toDouble, attributes(1).toDouble, attributes(2).toDouble) 

Mais ça ne compilera pas; le compilateur ne reconnaîtra pas l'instruction .toDF() ultérieurement. J'ai également essayé un bloc Try {Catch {}} semblable à Java, mais je ne parviens pas à définir correctement la portée. df n'est alors pas renvoyé. Est-ce que quelqu'un sait comment faire cela correctement? Dois-je même gérer ces exceptions, car la structure Spark semble traiter déjà une exception FileNotFound sans que j'en ajoute une. Mais je voudrais générer une erreur avec le nombre de champs du schéma si le fichier d'entrée a un nombre de colonnes incorrect, par exemple.

Voici le code:

object DataLoadTest extends SparkSessionWrapper {
/** Helper function to create a DataFrame from a textfile, re-used in        subsequent tests */
def createDataFrame(fileName: String): DataFrame = {

import spark.implicits._

//try {
val df = spark.sparkContext
  .textFile("/path/to/file" + fileName)
  .map(_.split("\\t"))
//mHealth user is the case class which defines the data schema
  .map(attributes => mHealthUser(attributes(0).toDouble, attributes(1).toDouble, attributes(2).toDouble,
        attributes(3).toDouble, attributes(4).toDouble,
        attributes(5).toDouble, attributes(6).toDouble, attributes(7).toDouble,
        attributes(8).toDouble, attributes(9).toDouble, attributes(10).toDouble,
        attributes(11).toDouble, attributes(12).toDouble, attributes(13).toDouble,
        attributes(14).toDouble, attributes(15).toDouble, attributes(16).toDouble,
        attributes(17).toDouble, attributes(18).toDouble, attributes(19).toDouble,
        attributes(20).toDouble, attributes(21).toDouble, attributes(22).toDouble,
        attributes(23).toInt))
  .toDF()
  .cache()
df
} catch {
    case ex: FileNotFoundException => println(s"File $fileName not found")
    case unknown: Exception => println(s"Unknown exception: $unknown")

}
}

Toutes les suggestions ont apprécié. Merci!

4
LucieCBurgess

Une autre option serait d’utiliser Try en scala.

Par exemple:

def createDataFrame(fileName: String): Try[DataFrame] = {

try {
      //create dataframe df
      Success(df)
    } catch {
      case ex: FileNotFoundException => {
        println(s"File $fileName not found")
        Failure(ex)
      }
      case unknown: Exception => {
        println(s"Unknown exception: $unknown")
        Failure(unknown)
      }
    }
  }

Maintenant, du côté de l'appelant, gérez cela comme suit:

createDataFrame("file1.csv") match {
  case Success(df) => {
    // proceed with your pipeline
  }
  case Failure(ex) => //handle exception
}

Ceci est légèrement mieux que d’utiliser Option car l’appelant connaitrait la raison de l’échec et pourrait mieux gérer.

10
Neeraj Malhotra

Soit vous laissez l'exception rejetée par la méthode createDataFrame (et la manipulez à l'extérieur), soit vous modifiez la signature pour renvoyer Option[DataFrame]:

  def createDataFrame(fileName: String): Option[DataFrame] = {

    import spark.implicits._

    try {
      val df = spark.sparkContext
        .textFile("/path/to/file" + fileName)
        .map(_.split("\\t"))
        //mHealth user is the case class which defines the data schema
        .map(attributes => mHealthUser(attributes(0).toDouble, attributes(1).toDouble, attributes(2).toDouble,
        attributes(3).toDouble, attributes(4).toDouble,
        attributes(5).toDouble, attributes(6).toDouble, attributes(7).toDouble,
        attributes(8).toDouble, attributes(9).toDouble, attributes(10).toDouble,
        attributes(11).toDouble, attributes(12).toDouble, attributes(13).toDouble,
        attributes(14).toDouble, attributes(15).toDouble, attributes(16).toDouble,
        attributes(17).toDouble, attributes(18).toDouble, attributes(19).toDouble,
        attributes(20).toDouble, attributes(21).toDouble, attributes(22).toDouble,
        attributes(23).toInt))
        .toDF()
        .cache()

      Some(df)
    } catch {
      case ex: FileNotFoundException => {
        println(s"File $fileName not found")
        None
      }
      case unknown: Exception => {
        println(s"Unknown exception: $unknown")
        None
      }
    }
  }

EDIT: sur le côté appelant de createDataFrame, il existe plusieurs modèles. Si vous traitez plusieurs noms de fichiers, vous pouvez par exemple faire:

 val dfs : Seq[DataFrame] = Seq("file1","file2","file3").map(createDataFrame).flatten

Si vous travaillez sur un seul nom de fichier, vous pouvez faire:

createDataFrame("file1.csv") match {
  case Some(df) => {
    // proceed with your pipeline
    val df2 = df.filter($"activityLabel" > 0).withColumn("binaryLabel", when($"activityLabel".between(1, 3), 0).otherwise(1))
  }
  case None => println("could not create dataframe")
}
1
Raphael Roth

applique try et intercepte le bloc sur les colonnes de la structure de données:

(try{$"credit.amount"} catch{case e:Exception=> lit(0)}).as("credit_amount")
0
Rajiv Singh