Spark结构化流 - 将静态数据集与流数据集连接起来

时间:2017-10-02 22:04:33

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

我使用Spark structured streaming来处理从Kafka读取的记录。这就是我想要实现的目标:

(a)每条记录都是Tuple2类型的(Timestamp, DeviceId)

(b)我创建了一个静态Dataset[DeviceId],其中包含预期在DeviceId中看到的所有有效设备ID(类型为Kafka)的集合流。

(c)我需要写一个Spark structured streaming查询

 (i) Groups records by their timestamp into 5-minute windows
 (ii) For each window, get the list of valid device IDs that were **not** seen in that window

例如,假设所有有效设备ID的列表为[A,B,C,D,E],并且某个5分钟窗口中的kafka记录包含设备ID [A,B,E]。然后,对于该窗口,我正在查找的看不见的设备ID列表为[C,D]

问题

  1. 如何在Spark结构化流媒体中编写此查询?我尝试使用except()公开的join()Dataset方法。但是,他们都抛出了一个运行时异常,抱怨streaming Dataset上都不支持这些操作。
  2. 这是我的代码片段:

    val validDeviceIds: Dataset[(DeviceId, Long)] = spark.createDataset[DeviceId](listOfAllDeviceIds.map(id => (id, 0L))) 
    
    case class KafkaRecord(timestamp: TimestampType, deviceId: DeviceId)
    
    // kafkaRecs is the data stream from Kafka - type is Dataset[KafkaRecord]
    val deviceIdsSeen = kafkaRecs
         .withWatermark("timestamp", "5 minutes")
         .groupBy(window($"timestamp", "5 minutes", "5 minutes"), $"deviceId")
         .count()
         .map(row => (row.getLong(0), 1L))
         .as[(Long, Long)]
    
    val unseenIds = deviceIdsSeen.join(validDeviceIds, Seq("_1"), "right_outer")
         .filter(row => row.isNullAt(1))
         .map(row => row.getLong(0))
    

    最后一个语句抛出以下异常:

    Caused by: org.apache.spark.sql.AnalysisException: Right outer join with a streaming DataFrame/Dataset on the left is not supported;;
    

    提前致谢。

2 个答案:

答案 0 :(得分:4)

火花结构化流媒体中join operations的情况如下所示:可以将DataFrames 加入static DataFrames,以便进一步创建新的{{1} }。但streaming DataFramesouter joins之间的streaming支持有条件,而支持static Datasets right/left joins不支持streaming Dataset 一般来说是结构化流媒体。结果,您遇到了AnalysisException,当您尝试使用流数据集创建连接静态数据集时抛出了line。作为我的话语的证明,你可以查看spark的源代码,在这个{{3}}异常抛出,表示你试过的操作不受支持。

我尝试使用静态stream of DataFramesDataFrames上进行联接操作。

val streamingDf = sparkSession
    .readStream
    .format("kafka")
    .option("kafka.bootstrap.servers", "127.0.0.1:9092")
    .option("subscribe", "structured_topic")
    .load()

val lines = spark.readStream
      .format("socket")
      .option("host", "localhost")
      .option("port", 9999)
      .load()

val staticDf = Seq((1507831462 , 100)).toDF("Timestamp", "DeviceId")

//Inner Join
streamingDf.join(staticDf, "Timestamp")
line.join(staticDf, "Timestamp")

//Left Join
streamingDf.join(staticDf, "Timestamp", "left_join")
line.join(staticDf, "Timestamp", "left_join")

如您所见,除了从Kafka使用数据之外,我还从通过nc(netcat)启动的套接字读取数据,它可以在您对流应用进行测试时显着简化生活。 这种方法适合我,Kafkasocket作为数据来源。

希望有所帮助。

答案 1 :(得分:0)

与对侧are just not supported的流数据集进行外连接:

  
      
  • 有条件地支持流式传输和静态数据集之间的外连接。      
        
    • 不支持使用流式数据集的完全外部联接
    •   
    • 不支持右侧带有流式数据集的外部联接
    •   
    • 不支持左侧带有流数据集的右外连接
    •   
  •   

如果其他Dataset很小,您可以使用Map或类似结构broadcast,并在UserDefinedFunction内引用它。

val map: Broadcast[Map[T, U]] = ???
val lookup = udf((x: T) => map.value.get(x))

df.withColumn("foo", lookup($"_1"))