簡體   English   中英

Spark Struded Streaming 自動將時間戳轉換為本地時間

[英]Spark Strutured Streaming automatically converts timestamp to local time

我的時間戳為 UTC 和 ISO8601,但使用結構化流,它會自動轉換為本地時間。 有沒有辦法阻止這種轉換? 我想在UTC中使用它。

我正在從 Kafka 讀取 json 數據,然后使用from_json Spark 函數解析它們。

輸入:

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

流程:

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

架構:

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

輸出:

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

如您所見,小時已自行增加。

PS:我嘗試嘗試使用from_utc_timestamp Spark 函數,但沒有成功。

對我來說,它可以使用:

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

它告訴 spark SQL 使用 UTC 作為時間戳的默認時區。 例如,我在 spark SQL 中使用了它:

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

我知道它在 2.0.1 中不起作用。 但適用於 Spark 2.2。 我也在SQLTransformer使用過,它工作正常。

我不確定流媒體。

注意

這個答案主要在 Spark < 2.2 中很有用。 對於較新的 Spark 版本,請參閱astro-asz的答案

但是我們應該注意,從 Spark 2.4.0 開始, spark.sql.session.timeZone沒有設置user.timezone ( java.util.TimeZone.getDefault )。 因此,單獨設置spark.sql.session.timeZone會導致 SQL 和非 SQL 組件使用不同時區設置的尷尬情況。

因此,我還是建議設置user.timezone明確,即使spark.sql.session.timeZone設置。

TL;DR不幸的是,這就是 Spark 現在處理時間戳的方式,除了直接在紀元時間上操作,而不使用日期/時間實用程序之外,實際上沒有內置的替代方法。

您可以在 Spark 開發人員列表上進行有見地的討論: SQL TIMESTAMP 語義 vs. SPARK-18350

到目前為止,我發現的最干凈的解決方法是將驅動程序和執行程序的-Duser.timezone設置為UTC 例如提交:

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

或者通過調整配置文件( spark-defaults.conf ):

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

雖然已經提供了兩個非常好的答案,但我發現它們都是解決問題的重錘。 我不想要任何需要在整個應用程序中修改時區解析行為的東西,或者一種會改變我的 JVM 的默認時區的方法。 我確實在痛苦之后找到了解決方案,我將在下面分享...

將 time[/date] 字符串解析為時間戳以進行日期操作,然后正確呈現結果

首先,讓我們解決如何讓 Spark SQL 正確解析日期 [/時間] 字符串(給定格式)到時間戳的問題,然后正確地將時間戳呈現回,以便它顯示與日期相同的日期 [/時間]原始字符串輸入。 一般的做法是:

- 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 

下面的測試代碼實現了這種方法。 “我們所在的時區”作為第一個參數傳遞給 timeTricks 方法。 該代碼將輸入字符串“1970-01-01”轉換為 localizedTimeStamp(通過 from_utc_timestamp)並驗證該時間戳的“valueOf”是否與“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")
  }
}

Structured Streaming Interpretingcoming date[/time] strings as UTC (not local time)問題的解決方案

下面的代碼說明了如何應用上述技巧(稍作修改),以糾正時間戳被本地時間和 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()
  }
}

代碼需要通過套接字輸入輸入......我使用程序'nc'(net cat),如下所示:

nc -l 9999

然后我啟動 Spark 程序並為 net cat 提供一行輸入:

1970-01-01 4

我得到的輸出說明了偏移偏移的問題:

-------------------------------------------
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|
+------------------------------------------+-----+-------------------+-------------------+

請注意,date_window 的開始和結束時間從輸入偏移了八小時(因為我在 GMT-7/8 時區,PST)。 但是,我使用 to_utc_timestamp 更正了這一轉變,以獲得包含輸入的一天窗口的正確開始和結束日期時間:1970-01-01 00:00:00,1970-01-02 00:00:00。

請注意,在呈現的第一塊代碼中,我們使用了 from_utc_timestamp,而對於結構化流解決方案,我們使用了 to_utc_timestamp。 我還沒有弄清楚在給定情況下使用這兩個中的哪一個。 (如果你知道,請告訴我!)。

另一個對我有用的解決方案是將 jvm 默認時區設置為您的目標時區(在您的情況下為 UTC)。

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

在將我的 spark 數據框寫入數據庫之前,我添加了上面的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM