web-dev-qa-db-fra.com

Spark structurée en continu - joignez un ensemble de données statiques à un ensemble de données en continu

J'utilise Spark structured streaming pour traiter les enregistrements lus dans Kafka. Voici ce que j'essaie de réaliser:

(a) Chaque enregistrement est un Tuple2 de type (Timestamp, DeviceId).

(b) J'ai créé un Dataset[DeviceId] statique qui contient l'ensemble de tous les ID de périphérique valides (de type DeviceId) qui devraient être vus dans le flux Kafka.

(c) Je dois écrire une requête Spark structured streaming qui 

 (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

Par exemple, supposons que la liste de tous les ID de périphérique valides soit [A,B,C,D,E] et que les enregistrements kafka dans une fenêtre donnée de 5 minutes contiennent les ID de périphérique [A,B,E]. Ensuite, pour cette fenêtre, la liste des ID de périphérique non vus que je recherche est [C,D].

Question

  1. Comment cette requête peut-elle être écrite en streaming structuré Spark? J'ai essayé d'utiliser les méthodes except() et join() que Dataset expose. Cependant, ils ont tous deux émis une exception d'exécution se plaignant qu'aucune de ces opérations n'est prise en charge sur un streaming Dataset.

Voici un extrait de mon code:

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

La dernière instruction lève l'exception suivante:

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

Merci d'avance.

14
jithinpt

La situation avec join operations dans la diffusion structurée par étincelle ressemble à ceci: la transmission DataFramespeut être jointe à static DataFrames afin de créer ultérieurement un nouveau streaming DataFrames. Mais outer joins entre une streaming et un static Datasets sont pris en charge conditionnellement, alors que right/left joins avec un streaming Datasetne sont pas pris en charge en général par le flux structuré. En conséquence, vous avez été confronté à AnalysisException , qui a été émise pendant que vous tentiez de créer une jointure de données statiques avec des données en continu. Comme preuve de mes paroles, vous pouvez regarder le code source de l'étincelle. Cette exception ligne renvoie ce qui indique que l'opération que vous avez essayée n'est pas prise en charge.

J'ai essayé de faire une opération de jointure sur stream of DataFrames avec une DataFrames statique. 

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")

Comme vous le voyez, en plus de consommer des données de Kafka, je lis les données du socket lancé via nc (netcat), cela simplifie considérablement la vie lorsque vous testez une application de flux . Cette approche me convient très bien avec Kafka et socket comme source de données.

J'espère que cette aide.

4
Artem Rukavytsia

Les jointures externes avec le jeu de données en continu du côté opposé ne sont tout simplement pas prises en charge :

  • Les jointures externes entre une diffusion en continu et une statique Les jeux de données sont conditionnellement pris en charge .
    • La jointure externe complète avec un jeu de données en continu n'est pas prise en charge.
    • La jointure externe gauche avec un jeu de données en continu à droite n'est pas prise en charge.
    • La jointure externe droite avec un jeu de données en continu sur la gauche n'est pas prise en charge

Si l'autre Dataset est petit, vous pouvez utiliser Map ou une structure similaire, broadcast, et le référencer à l'intérieur de UserDefinedFunction

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

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