如何刷新Spark Streaming中已加载的数据帧内容?

时间:2019-08-16 06:26:00

标签: apache-spark apache-spark-sql spark-structured-streaming

使用spark-sql 2.4.1和kafka进行实时流式传输。 我有以下用例

  
      
  1. 需要从hdfs加载元数据以与kafka的流数据帧结合。
  2.   应在元数据数据帧特定列(col-X)数据中查找
  3. 流数据记录的特定列。   如果找到,请选择元数据列(col-Y)数据   否则,将流式记录/列数据插入元数据数据帧,即HDFS。即如果应该检查   流数据帧再次包含相同的数据。
  4.   

作为在Spark作业开始时加载的元数据,如何在Streaming-Job中再次刷新其内容以查找并加入另一个Streaming数据帧?

3 个答案:

答案 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

  }
}

谢谢 斯里