Je ne parviens pas à utiliser l'évier console
avec PySpark Structured Streaming lorsque je l'utilise depuis Zeppelin. En gros, je ne vois aucun résultat imprimé à l'écran ni dans aucun fichier journal que j'ai trouvé.
Ma question: Quelqu'un at-il un exemple concret d'utilisation de PySpark Structured Streaming avec un récepteur produisant une sortie visible dans Apache Zeppelin? Idéalement, il utiliserait également la source de socket, ce qui est facile à tester.
J'utilise:
J'ai basé mon code sur l'exemple structuré-network_wordcount.py }. Cela fonctionne lorsqu'il est exécuté depuis le shell PySpark (./bin/pyspark --master local[2]
); Je vois des tableaux par lot.
%pyspark
# structured streaming
from pyspark.sql.functions import *
lines = spark\
.readStream\
.format('socket')\
.option('Host', 'localhost')\
.option('port', 9999)\
.option('includeTimestamp', 'true')\
.load()
# Split the lines into words, retaining timestamps
# split() splits each line into an array, and explode() turns the array into multiple rows
words = lines.select(
explode(split(lines.value, ' ')).alias('Word'),
lines.timestamp
)
# Group the data by window and Word and compute the count of each group
windowedCounts = words.groupBy(
window(words.timestamp, '10 seconds', '1 seconds'),
words.Word
).count().orderBy('window')
# Start running the query that prints the windowed Word counts to the console
query = windowedCounts\
.writeStream\
.outputMode('complete')\
.format('console')\
.option('truncate', 'false')\
.start()
print("Starting...")
query.awaitTermination(20)
Je m'attendrais à voir des impressions des résultats pour chaque lot, mais à la place, je ne vois que Starting...
, puis False
, la valeur de retour de query.awaitTermination(20)
.
Dans un terminal séparé, je suis en train d'entrer des données dans une session nc -lk 9999
netcat pendant l'exécution de la procédure ci-dessus.
L'évier de console n'est pas un bon choix pour le flux de travail basé sur un ordinateur portable interactif. Même en Scala, où la sortie peut être capturée, un appel awaitTermination
(ou équivalent) est requis dans le même paragraphe, ce qui bloque effectivement la note.
%spark
spark
.readStream
.format("socket")
.option("Host", "localhost")
.option("port", "9999")
.option("includeTimestamp", "true")
.load()
.writeStream
.outputMode("append")
.format("console")
.option("truncate", "false")
.start()
.awaitTermination() // Block execution, to force Zeppelin to capture the output
La chaîne awaitTermination
pourrait être remplacée par un appel autonome dans le même paragraphe fonctionnerait également:
%spark
val query = df
.writeStream
...
.start()
query.awaitTermination()
Sans cela, Zeppelin n’a aucune raison d’attendre une sortie. PySpark ajoute simplement un autre problème: l’exécution indirecte. Pour cette raison, même le blocage de la requête ne vous aidera pas ici.
De plus, une sortie continue du flux peut entraîner des problèmes de rendu et de mémoire lors de l'exploration de la note (il est possible d'utiliser le système d'affichage Zeppelin via l'API InterpreterContext
ou REST pour obtenir un comportement un peu plus judicieux, où la sortie est écrasé ou effacé périodiquement).
Un meilleur choix pour les tests avec Zeppelin est collecteur de mémoire . De cette façon, vous pouvez lancer une requête sans bloquer:
%pyspark
query = (windowedCounts
.writeStream
.outputMode("complete")
.format("memory")
.queryName("some_name")
.start())
et interroger le résultat à la demande dans un autre paragraphe:
%pyspark
spark.table("some_name").show()
Il peut être couplé avec des flux réactifs ou une solution similaire pour fournir des mises à jour par intervalles.
Il est également possible d'utiliser StreamingQueryListener
avec les rappels Py4j pour coupler les événements rx
à onQueryProgress
, bien que les écouteurs de requête ne soient pas pris en charge dans PySpark et nécessitent un peu de code pour coller des éléments. Interface Scala:
package com.example.spark.observer
import org.Apache.spark.sql.streaming.StreamingQueryListener
import org.Apache.spark.sql.streaming.StreamingQueryListener._
trait PythonObserver {
def on_next(o: Object): Unit
}
class PythonStreamingQueryListener(observer: PythonObserver)
extends StreamingQueryListener {
override def onQueryProgress(event: QueryProgressEvent): Unit = {
observer.on_next(event)
}
override def onQueryStarted(event: QueryStartedEvent): Unit = {}
override def onQueryTerminated(event: QueryTerminatedEvent): Unit = {}
}
construisez un bocal en ajustant la définition de génération pour refléter la version souhaitée de Scala et Spark:
scalaVersion := "2.11.8"
val sparkVersion = "2.2.0"
libraryDependencies ++= Seq(
"org.Apache.spark" %% "spark-sql" % sparkVersion,
"org.Apache.spark" %% "spark-streaming" % sparkVersion
)
placez-le sur le chemin de classe Spark, patch StreamingQueryManager
:
%pyspark
from pyspark.sql.streaming import StreamingQueryManager
from pyspark import SparkContext
def addListener(self, listener):
jvm = SparkContext._active_spark_context._jvm
jlistener = jvm.com.example.spark.observer.PythonStreamingQueryListener(
listener
)
self._jsqm.addListener(jlistener)
return jlistener
StreamingQueryManager.addListener = addListener
démarrer le serveur de rappel:
%pyspark
sc._gateway.start_callback_server()
et ajouter un auditeur:
%pyspark
from rx.subjects import Subject
class StreamingObserver(Subject):
class Java:
implements = ["com.example.spark.observer.PythonObserver"]
observer = StreamingObserver()
spark.streams.addListener(observer)
Enfin, vous pouvez utiliser subscribe
et bloquer l'exécution:
%pyspark
(observer
.map(lambda p: p.progress().name())
# .filter() can be used to print only for a specific query
.subscribe(lambda n: spark.table(n).show() if n else None))
input() # Block execution to capture the output
La dernière étape doit être exécutée après le démarrage de la requête en continu.
Il est également possible de sauter rx
et d'utiliser un observateur minimal comme ceci:
class StreamingObserver(object):
class Java:
implements = ["com.example.spark.observer.PythonObserver"]
def on_next(self, value):
try:
name = value.progress().name()
if name:
spark.table(name).show()
except: pass
Cela donne un peu moins de contrôle que Subject
(un inconvénient est que cela peut interférer avec l’impression de code sur stdout et ne peut être arrêté que par en supprimant l’auditeur . Avec Subject
, vous pouvez facilement dispose
subscribed
observateur, une fois que vous avez terminé ), mais devrait fonctionner plus ou moins de la même manière.
Notez que toute action de blocage sera suffisante pour capturer la sortie du programme d'écoute et qu'elle ne doit pas nécessairement être exécutée dans la même cellule. Par exemple
%pyspark
observer = StreamingObserver()
spark.streams.addListener(observer)
et
%pyspark
import time
time.sleep(42)
fonctionnerait de la même manière, en imprimant la table pour un intervalle de temps défini.
Pour être complet, vous pouvez implémenter StreamingQueryManager.removeListener
.
zeppelin-0.7.3-bin-all
utilise Spark 2.1.0 (donc pas de format rate
pour tester le streaming structuré avec, malheureusement).
Assurez-vous que lorsque vous start
, une requête en flux continu avec socket
source nc -lk 9999
a déjà été démarrée (sinon, la requête s'arrête tout simplement).
Assurez-vous également que la requête est bien opérationnelle.
val lines = spark
.readStream
.format("socket")
.option("Host", "localhost")
.option("port", 9999)
.load
val q = lines.writeStream.format("console").start
Il est en effet vrai que vous ne pourrez pas voir la sortie dans un cahier Zeppelin éventuellement car:
Les requêtes en streaming démarrent sur leurs propres threads (ce qui semble hors de portée de Zeppelin)
console
sink écrit sur la sortie standard (utilise l'opérateur Dataset.show
sur ce thread séparé).
Tout cela rend "intercepter" la sortie non disponible dans Zeppelin.
Nous arrivons donc à répondre à la vraie question:
Où est la sortie standard écrite dans Zeppelin?
Eh bien, avec une compréhension très limitée des composants internes de Zeppelin, j'ai pensé que cela pourrait être logs/zeppelin-interpreter-spark-[hostname].log
, mais malheureusement, je n'ai pas pu trouver la sortie de l'évier console
. C'est là que vous pouvez trouver les journaux de Spark (et du flux structuré en particulier) qui utilisent log4j mais que console
sink n'utilise pas.
Il semble que votre seule solution à long terme consiste à écrire votre propre évier personnalisé semblable à console
- et à utiliser un enregistreur log4j. Honnêtement, ce n'est pas si difficile que cela puisse paraître. Suivez les sources de la console évier .