web-dev-qa-db-ja.com

Zeppelinのコンソールストリーミングシンクから出力を取得する方法

Zeppelinから実行すると、consoleシンクが PySpark Structured Streaming で動作するようになるのに苦労しています。基本的に、画面に出力された結果や、見つけたログファイルには出力されません。

私の質問:Apache Zeppelinで表示される出力を生成するシンクでPySpark構造化ストリーミングを使用する実用的な例はありますか?テストが簡単なため、理想的にはソケットソースも使用します。

私が使用しています:

  • Ubuntu 16.04
  • spark-2.2.0-bin-hadoop2.7
  • zeppelin-0.7.3-bin-all
  • Python3

structured_network_wordcount.pyの例 に基づいてコードを作成しました。 PySparkシェル(_./bin/pyspark --master local[2]_);から実行すると機能します。バッチごとのテーブルが表示されます。

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

各バッチの結果のプリントアウトが表示されると思いますが、代わりに_Starting..._が表示され、次にFalsequery.awaitTermination(20)の戻り値が表示されます。

別の端末で、上記の実行中に_nc -lk 9999_ netcatセッションにデータを入力しています。

9
m01

コンソールシンクは、インタラクティブなノートブックベースのワークフローには適していません。出力をキャプチャできるScalaの場合でも、同じ段落にawaitTermination呼び出し(または同等のもの)が必要であり、メモを効果的にブロックします。

%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

チェーンされたawaitTerminationは、同じ段落内のスタンドアロン呼び出しで置き換えることができますも機能します。

%spark

val query = df
  .writeStream
  ...
  .start()

query.awaitTermination()

それがなければ、ツェッペリンは出力を待つ理由がありません。 PySparkは、それに加えて、間接実行という別の問題を追加するだけです。そのため、クエリをブロックしても、ここでは役に立ちません。

さらに、ストリームからの連続出力は、ノートを参照するときにレンダリングの問題とメモリの問題を引き起こす可能性があります(InterpreterContextまたはREST APIを介してZeppelin表示システムを使用して、出力が上書きされるか、定期的にクリアされる、もう少し賢明な動作)。

Zeppelinでテストするためのより良い選択は memory sink です。これにより、ブロックせずにクエリを開始できます。

%pyspark

query = (windowedCounts
  .writeStream
  .outputMode("complete")
  .format("memory")
  .queryName("some_name")
  .start())

別の段落でオンデマンドで結果をクエリします。

%pyspark

spark.table("some_name").show()

リアクティブストリーム または同様のソリューションと組み合わせて、間隔ベースの更新を提供できます。

Py4parkコールバックでStreamingQueryListenerを使用してrxonQueryProgressイベントと結合することもできますが、クエリリスナーはPySparkではサポートされていないため、少しのコードが必要です物事を一緒に。 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 = {}
}

jarをビルドし、必要なScalaおよびSparkバージョンを反映するようにビルド定義を調整します。

scalaVersion := "2.11.8"  

val sparkVersion = "2.2.0"

libraryDependencies ++= Seq(
  "org.Apache.spark" %% "spark-sql" % sparkVersion,
  "org.Apache.spark" %% "spark-streaming" % sparkVersion
)

Spark classpath、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

コールバックサーバーを起動します。

%pyspark

sc._gateway.start_callback_server()

そしてリスナーを追加します:

%pyspark

from rx.subjects import Subject

class StreamingObserver(Subject):
    class Java:
        implements = ["com.example.spark.observer.PythonObserver"]

observer = StreamingObserver()
spark.streams.addListener(observer)

最後に、subscribeを使用して実行をブロックできます。

%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 

最後のステップは、クエリのストリーミングを開始した後に実行する必要があります。

rxをスキップして、次のような最小限のオブザーバーを使用することもできます。

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

Subjectよりも少し制御が少なくなります(1つの注意点は、これが他のコードのstdoutへの出力を妨げることがあり、 リスナーの削除 によってのみ停止できることです。Subjectdisposesubscribedオブザーバーは簡単に実行できますが、それ以外はほぼ同じように機能します。

リスナーからの出力をキャプチャするには、ブロッキングアクションがあれば十分であり、同じセルで実行する必要がないことに注意してください。例えば

%pyspark

observer = StreamingObserver()
spark.streams.addListener(observer)

そして

%pyspark

import time

time.sleep(42)

同様の方法で、定義された時間間隔でテーブルを印刷します。

完全を期すために、StreamingQueryManager.removeListener

11
user6910411

zeppelin-0.7.3-bin-allはSpark 2.1.0を使用します(残念ながら、構造化ストリーミングをテストするrate形式はありません)。

enter image description here


start _socket source nc -lk 9999を使用したスト​​リーミングクエリが既に開始されていることを確認してください(クエリは単に停止するため)。

また、クエリが実際に実行されていることを確認してください。

val lines = spark
  .readStream
  .format("socket")
  .option("Host", "localhost")
  .option("port", 9999)
  .load
val q = lines.writeStream.format("console").start

enter image description here

次の理由により、Zeppelinノートブックで出力を見ることができないことは確かに事実です。

  1. ストリーミングクエリは独自のスレッドで開始されます(これはZeppelinの範囲外のようです)

  2. consoleシンク 標準出力に書き込み (別のスレッドでDataset.show演算子を使用)。

これにより、ツェッペリンでは出力を「傍受」できなくなります。

だから私たちは本当の質問に答えるようになります:

ツェッペリン語の標準出力はどこに書き込まれますか?

まあ、ツェッペリンの内部についての理解は非常に限られているので、それはlogs/zeppelin-interpreter-spark-[hostname].logかもしれないと思いましたが、残念ながらconsoleシンクからの出力を見つけることができませんでした。ここで、log = 4を使用し、consoleシンクは使用しないSpark(および構造化ストリーミング)からのログを見つけることができます。

長期的な解決策は、独自のconsoleのようなカスタムシンクを作成し、log4jロガーを使用することでした。正直なところ、それは思ったほど難しくはありません。 コンソールシンクのソース に従います。

0
Jacek Laskowski