使用spark-sql 2.4.1和kafka进行实时流式传输。 我有以下用例
- 需要从hdfs加载元数据以与kafka的流数据帧结合。
应在元数据数据帧特定列(col-X)数据中查找- 流数据记录的特定列。 如果找到,请选择元数据列(col-Y)数据 否则,将流式记录/列数据插入元数据数据帧,即HDFS。即如果应该检查 流数据帧再次包含相同的数据。
作为在Spark作业开始时加载的元数据,如何在Streaming-Job中再次刷新其内容以查找并加入另一个Streaming数据帧?
答案 0 :(得分:0)
编辑:该解决方案更加精细,可以解决所有用例。
对于更简单的情况,将数据附加到现有文件而不更改文件或从数据库中读取数据,可以使用更简单的解决方案,如in the other answer所示。
这是因为数据帧(和基础RDD)分区仅创建一次,并且每次使用datafframe时都会读取数据。 (除非它被spark缓存)
如果负担得起,您可以尝试在每个微型存储区中(重新)读取此元数据数据帧。
更好的方法是将元数据数据帧放入缓存中(不要与火花缓存数据帧混淆)。缓存与映射相似,不同之处在于缓存不会提供插入的条目数超过配置的生存时间。
在您的代码中,您将尝试为每个微型批次从缓存中获取一次此元数据数据帧。如果缓存返回null。您将再次读取数据框,放入缓存中,然后使用该数据框。
Cache
类将是
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
}
}
}
}
答案 1 :(得分:0)
我可能误解了这个问题,但是刷新元数据数据框应该是开箱即用的功能。
您根本无需执行任何操作。
让我们看一下示例:
// 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
,只有新文件中的世界匹配。
答案 2 :(得分:0)
下面是我在 spark 2.4.5 中使用流连接进行左外连接的场景。下面的过程是推动 spark 读取最新的维度数据更改。
流程用于批量维度的流连接(始终更新)
第 1 步:-
在开始 Spark 流作业之前:- 确保维度批处理数据文件夹只有一个文件,并且该文件应该至少有一条记录(由于某种原因放置空文件不起作用)。
第 2 步:- 启动您的流媒体作业并在 kafka 流中添加流记录
第 3 步:- 用值覆盖昏暗数据(文件应该同名不要改变,维度文件夹应该只有一个文件) 注意:-不要使用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
}
}
谢谢 斯里