Spark结构化流MemoryStream +行+编码器问题

时间:2018-09-08 18:33:42

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

我正在尝试使用Spark结构化流在本地计算机上运行一些测试。

在批处理模式下,这是我要处理的行:

val recordSchema = StructType(List(StructField("Record", MapType(StringType, StringType), false)))
val rows         = List(
    Row(
      Map("ID" -> "1",
        "STRUCTUREID" -> "MFCD00869853",
        "MOLFILE" -> "The MOL Data",
        "MOLWEIGHT" -> "803.482",
        "FORMULA" -> "C44H69NO12",
        "NAME" -> "Tacrolimus",
        "HASH" -> "52b966c551cfe0fa7d526bac16abcb7be8b8867d",
        "SMILES" -> """[H][C@]12O[C@](O)([C@H](C)C[C@@H]1OC)""",
        "METABOLISM" -> "The metabolism 500"
       )),
    Row(
      Map("ID" -> "2",
        "STRUCTUREID" -> "MFCD00869854",
        "MOLFILE" -> "The MOL Data",
        "MOLWEIGHT" -> "603.482",
        "FORMULA" -> "",
        "NAME" -> "Tacrolimus2",
        "HASH" -> "52b966c551cfe0fa7d526bac16abcb7be8b8867d",
        "SMILES" -> """[H][C@]12O[C@](O)([C@H](C)C[C@@H]1OC)""",
        "METABOLISM" -> "The metabolism 500"
      ))
  )
val df  = spark.createDataFrame(spark.sparkContext.parallelize(rows), recordSchema)

在Batch中更多地使用它是一种魅力,没有问题。

现在,我尝试使用MemoryStream进行流式测试。我添加了以下内容:

implicit val ctx = spark.sqlContext
val intsInput = MemoryStream[Row]

但是编译器抱怨如下:

  

未找到参数证据$ 1的隐式值:Encoder [Row]

因此,我的问题:我应该怎么做才能使它正常工作

我还看到,如果添加以下导入,错误将消失:

  

导入spark.implicits ._

实际上,我现在收到以下警告而不是错误

  

参数证据$ 1的含糊不清的隐式:编码器[行]

我不太了解编码器机制,如果有人可以向我解释如何不使用这些隐式函数,我将不胜感激。原因是在从Rows创建DataFrame时,我在书中补充了以下内容。

推荐方法:

val myManualSchema = new StructType(Array(
  new StructField("some", StringType, true),
  new StructField("col", StringType, true),
  new StructField("names", LongType, false)))
val myRows = Seq(Row("Hello", null, 1L))
val myRDD = spark.sparkContext.parallelize(myRows)
val myDf = spark.createDataFrame(myRDD, myManualSchema)
myDf.show()

然后作者继续:

  

在Scala中,我们还可以利用Spark在   控制台(如果您将其导入到JAR代码中),请在   序列类型。这不适用于null类型,因此不适合   对于生产用例必不可少。

val myDF = Seq(("Hello", 2, 1L)).toDF("col1", "col2", "col3")

如果有人在我使用隐式函数时花时间解释我的情况,并且这样做比较安全,或者有办法在不导入隐式函数的情况下更明确地做到这一点。

最后,如果有人能给我介绍有关Encoder和Spark Type映射的好文档,那将会很棒。

EDIT1

我终于可以使用它了

  implicit val ctx = spark.sqlContext
  import spark.implicits._
  val rows = MemoryStream[Map[String,String]]
  val df = rows.toDF()

尽管我的问题是我对自己在做什么并不自信。在我看来,就像在某些情况下,我需要创建一个DataSet才能使用toDF转换将其转换为DF [ROW]。我知道使用DS是typeSafe,但比使用DF慢。那么,为什么要使用DataSet这种中介呢?这不是我第一次在Spark结构化流媒体中看到它。再说一次,如果有人可以帮助我,那将很棒。

1 个答案:

答案 0 :(得分:0)

我建议您使用Scala的case classes进行数据建模。

final case class Product(name: String, catalogNumber: String, cas: String, formula: String, weight: Double, mld: String)

现在您可以在存储器中存储List中的Product

  val inMemoryRecords: List[Product] = List(
    Product("Cyclohexanecarboxylic acid", " D19706", "1148027-03-5", "C(11)H(13)Cl(2)NO(5)", 310.131, "MFCD11226417"),
    Product("Tacrolimus", "G51159", "104987-11-3", "C(44)H(69)NO(12)", 804.018, "MFCD00869853"),
    Product("Methanol", "T57494", "173310-45-7", "C(8)H(8)Cl(2)O", 191.055, "MFCD27756662")
  )

通过使用广为人知的Dataset[T]抽象,structured streaming API使得推理流处理变得容易。粗略地说,您只需要担心三件事:

  • Source:源可以生成输入数据流,我们可以将其表示为Dataset[Input]。到达的每个新数据项Input将被追加到此无边界数据集中。您可以根据需要操纵数据(例如Dataset[Input] => Dataset[Output])。
  • StreamingQueriesSink:查询生成一个结果表,该表在每个触发间隔从Source进行更新。更改被写入到称为接收器的外部存储中。
  • Output modes:可以将数据写入接收器的模式有多种:完成模式,追加模式和更新模式。

让我们假设您想知道分子量大于200个单位的产品。

正如您所说,使用批处理API相当简单明了:

// Create an static dataset using the in-memory data
val staticData: Dataset[Product] = spark.createDataset(inMemoryRecords)

// Processing...
val result: Dataset[Product] = staticData.filter(_.weight > 200)

// Print results!
result.show()

使用Streaming API时,您仅需要定义sourcesink作为额外的步骤。在此示例中,我们可以使用MemoryStreamconsole接收器来打印出结果。

// Create an streaming dataset using the in-memory data (memory source)
val productSource = MemoryStream[Product]
productSource.addData(inMemoryRecords)

val streamingData: Dataset[Product] = productSource.toDS()

// Processing...
val result: Dataset[Product] = streamingData.filter(_.weight > 200)

// Print results by using the console sink. 
val query: StreamingQuery = result.writeStream.format("console").start()

// Stop streaming
query.awaitTermination(timeoutMs=5000)
query.stop()

请注意,staticDatastreamingData具有确切的类型签名(即Dataset[Product])。这样,无论使用Batch还是Streaming API,我们都可以应用相同的处理步骤。您也可以考虑实现通用方法def processing[In, Out](inputData: Dataset[In]): Dataset[Out] = ???,以避免在两种方法中都重复您自己的内容。

完整的代码示例:

object ExMemoryStream extends App {

  // Boilerplate code...
  val spark: SparkSession = SparkSession.builder
    .appName("ExMemoryStreaming")
    .master("local[*]")
    .getOrCreate()

  spark.sparkContext.setLogLevel("ERROR")

  import spark.implicits._
  implicit val sqlContext: SQLContext = spark.sqlContext

  // Define your data models 
  final case class Product(name: String, catalogNumber: String, cas: String, formula: String, weight: Double, mld: String)

  // Create some in-memory instances
  val inMemoryRecords: List[Product] = List(
    Product("Cyclohexanecarboxylic acid", " D19706", "1148027-03-5", "C(11)H(13)Cl(2)NO(5)", 310.131, "MFCD11226417"),
    Product("Tacrolimus", "G51159", "104987-11-3", "C(44)H(69)NO(12)", 804.018, "MFCD00869853"),
    Product("Methanol", "T57494", "173310-45-7", "C(8)H(8)Cl(2)O", 191.055, "MFCD27756662")
  )

  // Defining processing step
  def processing(inputData: Dataset[Product]): Dataset[Product] =
    inputData.filter(_.weight > 200)

  // STATIC DATASET
  val datasetStatic: Dataset[Product] = spark.createDataset(inMemoryRecords)

  println("This is the static dataset:")
  processing(datasetStatic).show()

  // STREAMING DATASET
  val productSource = MemoryStream[Product]
  productSource.addData(inMemoryRecords)

  val datasetStreaming: Dataset[Product] = productSource.toDS()

  println("This is the streaming dataset:")
  val query: StreamingQuery = processing(datasetStreaming).writeStream.format("console").start()
  query.awaitTermination(timeoutMs=5000)

  // Stop query and close Spark
  query.stop()
  spark.close()

}