Apache spark 如何在spark流媒体中刷新加载的数据帧内容?
使用spark sql 2.4.1和kafka进行实时流式处理。 我有以下用例Apache spark 如何在spark流媒体中刷新加载的数据帧内容?,apache-spark,apache-spark-sql,spark-structured-streaming,Apache Spark,Apache Spark Sql,Spark Structured Streaming,使用spark sql 2.4.1和kafka进行实时流式处理。 我有以下用例 需要从hdfs加载元数据,以便与来自kafka的流数据帧连接 流数据记录的特定列应该在元数据数据帧特定列(col-X)数据中查找。 如果找到,选择元数据列(col-Y)数据 否则,将流记录/列数据插入元数据数据帧,即hdfs。也就是说,如果 流式数据帧再次包含相同的数据 作为spark作业开始时加载的元数据,如何在流式作业中再次刷新其内容以查找并与另一个流式数据帧连接?编辑此解决方案更为详细,并且适用于所有用例。 对
作为spark作业开始时加载的元数据,如何在流式作业中再次刷新其内容以查找并与另一个流式数据帧连接?编辑此解决方案更为详细,并且适用于所有用例。
对于将数据附加到现有文件而不更改文件或从数据库中读取数据的简单情况,可以如前所述使用更简单的解决方案。
这是因为dataframe(和底层RDD)分区只创建一次,每次使用datafframe时都会读取数据。(除非由spark缓存)
如果你能负担得起,你可以试着在每一个微型计算机上(重新)读取这个元数据数据帧 更好的方法是将元数据数据帧放在缓存中(不要与spark缓存数据帧混淆)。缓存与映射类似,只是它不会提供插入的条目超过配置的生存时间 在代码中,您将尝试为每个微批从缓存中获取此元数据数据帧一次。如果缓存返回null。您将再次读取数据帧,放入缓存,然后使用数据帧
缓存
类将是
import scala.collection.mutable
// cache class to store the dataframe
class Cache[K, V](timeToLive: Long) extends mutable.Map[K, V] {
private var keyValueStore = mutable.HashMap[K, (V, Long)]()
override def get(key: K):Option[V] = {
keyValueStore.get(key) match {
case Some((value, insertedAt)) if insertedAt+timeToLive > System.currentTimeMillis => Some(value)
case _ => None
}
}
override def iterator: Iterator[(K, V)] = keyValueStore.iterator
.filter({
case (key, (value, insertedAt)) => insertedAt+timeToLive > System.currentTimeMillis
}).map(x => (x._1, x._2._1))
override def -=(key: K): this.type = {
keyValueStore-=key
this
}
override def +=(kv: (K, V)): this.type = {
keyValueStore += ((kv._1, (kv._2, System.currentTimeMillis())))
this
}
}
通过缓存访问元数据数据帧的逻辑
import org.apache.spark.sql.DataFrame
object DataFrameCache {
lazy val cache = new Cache[String, DataFrame](600000) // ten minutes timeToLive
def readMetaData: DataFrame = ???
def getMetaData: DataFrame = {
cache.get("metadataDF") match {
case Some(df) => df
case None => {
val metadataDF = readMetaData
cache.put("metadataDF", metadataDF)
metadataDF
}
}
}
}
我可能误解了这个问题,但是刷新元数据数据帧应该是一个现成的特性 你根本不需要做任何事情 让我们看一看这个例子:
// a batch dataframe
val metadata = spark.read.text("metadata.txt")
scala> metadata.show
+-----+
|value|
+-----+
|hello|
+-----+
// a streaming dataframe
val stream = spark.readStream.text("so")
// join on the only value column
stream.join(metadata, "value").writeStream.format("console").start
只要so
目录中的文件内容与metadata.txt
文件匹配,您就应该在控制台上打印出一个数据帧
-------------------------------------------
Batch: 1
-------------------------------------------
+-----+
|value|
+-----+
|hello|
+-----+
将
metadata.txt
更改为,比如说,world
,并且只匹配新文件中的世界。下面是我在spark 2.4.5中使用流连接进行左外部连接时遵循的场景。下面的过程推动spark读取最新维度数据更改
流程用于批处理维度的流联接(始终更新)
步骤1:-
启动Spark流媒体作业之前:-
确保维度批次数据文件夹只有一个文件,并且该文件应至少有一条记录(由于某些原因,放置空文件不起作用)
步骤2:-
启动流媒体作业并在kafka stream中添加流记录
步骤3:-
用值覆盖dim数据(文件应为相同的名称且不更改,并且维度文件夹应只有一个文件)
注意:-不要使用spark写入此文件夹使用Java或Scala filesystem.io覆盖该文件,或使用bash删除该文件并替换为同名的新数据文件
步骤4:-
在下一批中,spark能够在加入kafka流时读取更新的维度数据
示例代码:-
package com.broccoli.streaming.streamjoinupdate
import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.types.{StringType, StructField, StructType, TimestampType}
import org.apache.spark.sql.{DataFrame, SparkSession}
object BroadCastStreamJoin3 {
def main(args: Array[String]): Unit = {
@transient lazy val logger: Logger = Logger.getLogger(getClass.getName)
Logger.getLogger("akka").setLevel(Level.WARN)
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("com.amazonaws").setLevel(Level.ERROR)
Logger.getLogger("com.amazon.ws").setLevel(Level.ERROR)
Logger.getLogger("io.netty").setLevel(Level.ERROR)
val spark = SparkSession
.builder()
.master("local")
.getOrCreate()
val schemaUntyped1 = StructType(
Array(
StructField("id", StringType),
StructField("customrid", StringType),
StructField("customername", StringType),
StructField("countrycode", StringType),
StructField("timestamp_column_fin_1", TimestampType)
))
val schemaUntyped2 = StructType(
Array(
StructField("id", StringType),
StructField("countrycode", StringType),
StructField("countryname", StringType),
StructField("timestamp_column_fin_2", TimestampType)
))
val factDf1 = spark.readStream
.schema(schemaUntyped1)
.option("header", "true")
.csv("src/main/resources/broadcasttest/fact")
val dimDf3 = spark.read
.schema(schemaUntyped2)
.option("header", "true")
.csv("src/main/resources/broadcasttest/dimension")
.withColumnRenamed("id", "id_2")
.withColumnRenamed("countrycode", "countrycode_2")
import spark.implicits._
factDf1
.join(
dimDf3,
$"countrycode_2" <=> $"countrycode",
"inner"
)
.writeStream
.format("console")
.outputMode("append")
.start()
.awaitTermination
}
}
package com.brocoli.streaming.streamjoinupdate
导入org.apache.log4j.{Level,Logger}
导入org.apache.spark.sql.types.{StringType,StructField,StructType,TimestampType}
导入org.apache.spark.sql.{DataFrame,SparkSession}
对象3{
def main(参数:数组[字符串]):单位={
@瞬态延迟val记录器:logger=logger.getLogger(getClass.getName)
Logger.getLogger(“akka”).setLevel(Level.WARN)
Logger.getLogger(“org”).setLevel(Level.ERROR)
Logger.getLogger(“com.amazonaws”).setLevel(Level.ERROR)
Logger.getLogger(“com.amazon.ws”).setLevel(Level.ERROR)
Logger.getLogger(“io.netty”).setLevel(Level.ERROR)
val spark=火花会话
.builder()
.master(“本地”)
.getOrCreate()
val schemaUntyped1=StructType(
排列(
StructField(“id”,StringType),
StructField(“customrid”,StringType),
StructField(“customername”,StringType),
StructField(“countrycode”,StringType),
StructField(“时间戳\列\财务\ 1”,时间戳类型)
))
val schemaUntyped2=StructType(
排列(
StructField(“id”,StringType),
StructField(“countrycode”,StringType),
StructField(“countryname”,StringType),
StructField(“时间戳\列\财务\ 2”,时间戳类型)
))
val factDf1=spark.readStream
.schema(schemaUntyped1)
.选项(“标题”、“正确”)
.csv(“src/main/resources/broadcastedtest/fact”)
val dimDf3=spark.read
.schema(schemaUntyped2)
.选项(“标题”、“正确”)
.csv(“src/main/resources/broadcastedtest/dimension”)
.WithColumnRename(“id”、“id_2”)
.WithColumn重命名(“国家代码”、“国家代码_2”)
导入spark.implicits_
事实1
.加入(
dimDf3,
$“countrycode_2”$“countrycode”,
“内部”
)
.writeStream
.格式(“控制台”)
.outputMode(“追加”)
.start()
.等待终止
}
}
谢谢
Sri
Cache
类使用更新的mutable.HashMap
。如果数据帧缓存在spark中,则不会再次读取(更新的)文件。