it-roy-ru.com

Spark структурированная потоковая передача - объедините статический набор данных с потоковым набором данных

Я использую Spark structured streaming для обработки записей, прочитанных из Kafka. Вот чего я пытаюсь достичь:

(a) Каждая запись является Tuple2 типа (Timestamp, DeviceId).

(b) Я создал статический Dataset[DeviceId], который содержит набор всех допустимых идентификаторов устройств (типа DeviceId), которые, как ожидается, будут отображаться в потоке 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

Например, предположим, что список всех допустимых идентификаторов устройств - [A,B,C,D,E], а записи kafka в определенном 5-минутном окне содержат идентификаторы устройств [A,B,E]. Затем, для этого окна, список невидимых идентификаторов устройств, которые я ищу, это [C,D].

Вопрос

  1. Как этот запрос может быть записан в структурированном потоке Spark? Я попытался использовать методы except() и join(), которые предоставляет Dataset. Тем не менее, они оба выдали исключение времени выполнения, жалуясь, что ни одна из этих операций не поддерживается в streaming Dataset.

Вот фрагмент моего кода:

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;;

Заранее спасибо.

14
jithinpt

Ситуация с join operations в искровой структурированной потоковой передаче выглядит следующим образом: потоковую DataFramesможно объединить с static DataFrames, поэтому в дальнейшем создайте новый streaming DataFrames. Но outer joins между streaming и static Datasetsусловно поддерживается, в то время как right/left joins с streaming Datasetвообще не поддерживается при структурированной потоковой передаче. В результате вы столкнулись с AnalysisException , который был выдан, когда вы пытались создать объединенный статический набор данных с потоковым набором данных. Как доказательство моих слов, вы можете посмотреть на исходный код spark, в этом line исключение, которое обозначает, что опробованная вами операция не поддерживается.

Я попытался сделать операцию соединения на stream of DataFrames со статической DataFrames

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), это значительно упрощает жизнь, пока вы выполняете тестирование потокового приложения . Этот подход отлично работает для меня как с Kafka, так и socket как источник данных.

Надеюсь, что поможет.

4
Artem Rukavytsia

Внешние объединения с потоковым набором данных на противоположной стороне просто не поддерживаются :

  • Внешние объединения между потоковым и статическим наборами данных условно поддерживаются .
    • Полное внешнее соединение с потоковым набором данных не поддерживается
    • Левое внешнее соединение с потоковым набором данных справа не поддерживается
    • Правое внешнее объединение с потоковым набором данных слева не поддерживается

Если другая Dataset мала, вы можете использовать Map или аналогичную структуру, broadcast, и ссылаться на нее внутри UserDefinedFunction

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

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