[英]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.