web-dev-qa-db-fra.com

Spark Le streaming structuré convertit automatiquement l'horodatage en heure locale

J'ai mon horodatage en UTC et ISO8601, mais en utilisant le streaming structuré, il est automatiquement converti en heure locale. Existe-t-il un moyen d'arrêter cette conversion? Je voudrais l'avoir en UTC.

Je lis les données json de Kafka puis je les analyse en utilisant le from_json Spark.

Contribution:

{"Timestamp":"2015-01-01T00:00:06.222Z"}

Couler:

SparkSession
  .builder()
  .master("local[*]")
  .appName("my-app")
  .getOrCreate()
  .readStream()
  .format("kafka")
  ... //some magic
  .writeStream()
  .format("console")
  .start()
  .awaitTermination();

Schéma:

StructType schema = DataTypes.createStructType(new StructField[] {
        DataTypes.createStructField("Timestamp", DataTypes.TimestampType, true),});

Sortie:

+--------------------+
|           Timestamp|
+--------------------+
|2015-01-01 01:00:...|
|2015-01-01 01:00:...|
+--------------------+

Comme vous pouvez le voir, l'heure s'est incrémentée d'elle-même.

PS: j'ai essayé d'expérimenter le from_utc_timestamp Spark fonction, mais pas de chance.

17
Martin Brišiak

Pour moi, cela a fonctionné d'utiliser:

spark.conf.set("spark.sql.session.timeZone", "UTC")

Il indique au SQL spark pour utiliser UTC comme fuseau horaire par défaut pour les horodatages. Je l'ai utilisé dans spark SQL par exemple:

select *, cast('2017-01-01 10:10:10' as timestamp) from someTable

Je sais que cela ne fonctionne pas dans 2.0.1. mais fonctionne dans Spark 2.2. J'ai également utilisé dans SQLTransformer et cela a fonctionné.

Je ne suis pas sûr du streaming cependant.

28
astro_asz

Remarque :

Cette réponse est utile principalement dans Spark <2.2. Pour les versions plus récentes Spark version voir la réponse = par astro-asz

Cependant, nous devons noter qu'à partir d'aujourd'hui (Spark 2.4.0), spark.sql.session.timeZone ne définit pas user.timezone (Java.util.TimeZone.getDefault). Ainsi, la définition de `` spark.sql.session.timeZone` seule peut entraîner une situation plutôt délicate où les composants SQL et non SQL utilisent des paramètres de fuseau horaire différents.

Par conséquent, je recommande toujours de définir user.timezone explicitement, même si spark.sql.session.timeZone est défini.

TL; DR Malheureusement, c'est ainsi que Spark gère les horodatages en ce moment et il n'y a vraiment aucune alternative intégrée, autre que de fonctionner directement à l'heure Epoch, sans utiliser les utilitaires de date/heure.

Vous pouvez une discussion perspicace sur la Spark: sémantique SQL TIMESTAMP vs SPARK-1835

La solution la plus propre que j'ai trouvée jusqu'à présent consiste à définir -Duser.timezone à UTC pour le pilote et les exécuteurs. Par exemple avec soumettre:

bin/spark-Shell --conf "spark.driver.extraJavaOptions=-Duser.timezone=UTC" \
                --conf "spark.executor.extraJavaOptions=-Duser.timezone=UTC"

ou en ajustant les fichiers de configuration (spark-defaults.conf):

spark.driver.extraJavaOptions      -Duser.timezone=UTC
spark.executor.extraJavaOptions    -Duser.timezone=UTC
15
user6910411

Bien que deux très bonnes réponses aient été fournies, je les ai trouvées toutes les deux un peu lourdes pour résoudre le problème. Je ne voulais rien qui nécessiterait de modifier le comportement d'analyse du fuseau horaire dans l'ensemble de l'application, ou une approche qui modifierait le fuseau horaire par défaut de ma machine virtuelle Java. J'ai trouvé une solution après beaucoup de douleur, que je partagerai ci-dessous ...

Analyse des chaînes de temps [/ date] en horodatages pour les manipulations de date, puis restitue correctement le résultat

Tout d'abord, abordons la question de savoir comment obtenir Spark SQL pour analyser correctement une chaîne de date [/ heure] (en fonction d'un format) dans un horodatage, puis restituer correctement cet horodatage pour qu'il affiche la même date [/ heure] que l'entrée de chaîne d'origine. L'approche générale est la suivante:

- convert a date[/time] string to time stamp [via to_timestamp]
    [ to_timestamp  seems to assume the date[/time] string represents a time relative to UTC (GMT time zone) ]
- relativize that timestamp to the timezone we are in via from_utc_timestamp 

Le code de test ci-dessous implémente cette approche. 'timezone we are in' est passé comme premier argument à la méthode timeTricks. Le code convertit la chaîne d'entrée "1970-01-01" en localizedTimeStamp (via from_utc_timestamp) et vérifie que la "valeurOf" de cet horodatage est la même que "1970-01-01 00:00:00".

object TimeTravails {
  def main(args: Array[String]): Unit = {

    import org.Apache.spark.sql.SparkSession
    import org.Apache.spark.sql.functions._

    val spark: SparkSession = SparkSession.builder()
      .master("local[3]")
      .appName("SparkByExample")
      .getOrCreate()

    spark.sparkContext.setLogLevel("ERROR")

    import spark.implicits._
    import Java.sql.Timestamp

    def timeTricks(timezone: String): Unit =  {
      val df2 = List("1970-01-01").toDF("timestr"). // can use to_timestamp even without time parts !
        withColumn("timestamp", to_timestamp('timestr, "yyyy-MM-dd")).
        withColumn("localizedTimestamp", from_utc_timestamp('timestamp, timezone)).
        withColumn("weekday", date_format($"localizedTimestamp", "EEEE"))
      val row = df2.first()
      println("with timezone: " + timezone)
      df2.show()
      val (timestamp, weekday) = (row.getAs[Timestamp]("localizedTimestamp"), row.getAs[String]("weekday"))

      timezone match {
        case "UTC" =>
          assert(timestamp ==  Timestamp.valueOf("1970-01-01 00:00:00")  && weekday == "Thursday")
        case "PST" | "GMT-8" | "America/Los_Angeles"  =>
          assert(timestamp ==  Timestamp.valueOf("1969-12-31 16:00:00")  && weekday == "Wednesday")
        case  "Asia/Tokyo" =>
          assert(timestamp ==  Timestamp.valueOf("1970-01-01 09:00:00")  && weekday == "Thursday")
      }
    }

    timeTricks("UTC")
    timeTricks("PST")
    timeTricks("GMT-8")
    timeTricks("Asia/Tokyo")
    timeTricks("America/Los_Angeles")
  }
}

Solution au problème du Streaming Structuré Interprétant les chaînes de date [/ heure] entrantes comme UTC (pas l'heure locale)

Le code ci-dessous illustre comment appliquer les astuces ci-dessus (avec une légère modification) afin de corriger le problème des horodatages décalés par le décalage entre l'heure locale et GMT.

object Struct {
  import org.Apache.spark.sql.SparkSession
  import org.Apache.spark.sql.functions._

  def main(args: Array[String]): Unit = {

    val timezone = "PST"

    val spark: SparkSession = SparkSession.builder()
      .master("local[3]")
      .appName("SparkByExample")
      .getOrCreate()

    spark.sparkContext.setLogLevel("ERROR")

    val df = spark.readStream
      .format("socket")
      .option("Host", "localhost")
      .option("port", "9999")
      .load()

    import spark.implicits._


    val splitDf = df.select(split(df("value"), " ").as("arr")).
      select($"arr" (0).as("tsString"), $"arr" (1).as("count")).
      withColumn("timestamp", to_timestamp($"tsString", "yyyy-MM-dd"))
    val grouped = splitDf.groupBy(window($"timestamp", "1 day", "1 day").as("date_window")).count()

    val tunedForDisplay =
      grouped.
        withColumn("windowStart", to_utc_timestamp($"date_window.start", timezone)).
        withColumn("windowEnd", to_utc_timestamp($"date_window.end", timezone))

    tunedForDisplay.writeStream
      .format("console")
      .outputMode("update")
      .option("truncate", false)
      .start()
      .awaitTermination()
  }
}

Le code nécessite une entrée via une socket ... J'utilise le programme 'nc' (net cat) démarré comme ceci:

nc -l 9999

Ensuite, je démarre le programme Spark et je fournis à net cat une ligne d'entrée:

1970-01-01 4

La sortie que j'obtiens illustre le problème du décalage de décalage:

-------------------------------------------
Batch: 1
-------------------------------------------
+------------------------------------------+-----+-------------------+-------------------+
|date_window                               |count|windowStart        |windowEnd          |
+------------------------------------------+-----+-------------------+-------------------+
|[1969-12-31 16:00:00, 1970-01-01 16:00:00]|1    |1970-01-01 00:00:00|1970-01-02 00:00:00|
+------------------------------------------+-----+-------------------+-------------------+

Notez que le début et la fin de date_window sont décalés de huit heures par rapport à l'entrée (car je suis dans le fuseau horaire GMT-7/8, PST). Cependant, je corrige ce décalage en utilisant to_utc_timestamp pour obtenir les heures de date de début et de fin appropriées pour la fenêtre d'un jour qui subsume l'entrée: 1970-01-01 00: 00: 00,1970-01-02 00:00:00.

Notez que dans le premier bloc de code présenté, nous avons utilisé from_utc_timestamp, tandis que pour la solution de streaming structuré, nous avons utilisé to_utc_timestamp. Je n'ai pas encore trouvé lequel de ces deux utiliser dans une situation donnée. (Veuillez m'indiquer si vous le savez!).

0
Chris Bedford