Sparkストリーミングを使用して2つのKafkaキュー間でデータを処理していますが、SparkからKafkaに書き込む良い方法が見つからないようです。私はこれを試しました:
input.foreachRDD(rdd =>
rdd.foreachPartition(partition =>
partition.foreach {
case x: String => {
val props = new HashMap[String, Object]()
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers)
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
"org.Apache.kafka.common.serialization.StringSerializer")
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
"org.Apache.kafka.common.serialization.StringSerializer")
println(x)
val producer = new KafkaProducer[String, String](props)
val message = new ProducerRecord[String, String]("output", null, x)
producer.send(message)
}
}
)
)
それは意図したとおりに機能しますが、すべてのメッセージに対して新しいKafkaProducerをインスタンス化することは実際の状況では明らかに実行不可能であり、回避しようとしています。
プロセスごとに単一のインスタンスへの参照を保持し、メッセージを送信する必要があるときにアクセスしたいと思います。 KafkaストリーミングからSparkに書き込むにはどうすればよいですか?
私の最初のアドバイスは、foreachPartitionで新しいインスタンスを作成し、それがあなたのニーズに十分に速いかどうかを測定することです(foreachPartitionで重いオブジェクトをインスタンス化することは公式ドキュメントが示唆するものです)。
別のオプションは、この例に示すようにオブジェクトプールを使用することです。
しかし、チェックポイントを使用する場合、実装が難しいと感じました。
私にとってうまく機能している別のバージョンは、次のブログ投稿で説明されている工場です。あなたのニーズに十分な並列性を提供するかどうかを確認するだけです(コメントセクションを確認してください):
Clouderaが管理するストリーミングKafka Writerがあります(実際にはSpark JIRA [1] からスピンオフされます)。パーティションごとのプロデューサー。これは、要素の(できれば大きい)コレクション上で「重い」オブジェクトを作成するために費やした時間を償却します。
ライターはここにあります: https://github.com/cloudera/spark-kafka-writer
Spark> = 2.2
読み取りと書き込みの両方の操作が可能ですKafka Structured Streaming APIを使用して
// Subscribe to a topic and read messages from the earliest to latest offsets
val ds= spark
.readStream // use `read` for batch, like DataFrame
.format("kafka")
.option("kafka.bootstrap.servers", "brokerhost1:port1,brokerhost2:port2")
.option("subscribe", "source-topic1")
.option("startingOffsets", "earliest")
.option("endingOffsets", "latest")
.load()
キーと値を読み取り、両方にスキーマを適用します。簡単にするために、両方をString
型に変換しています。
val dsStruc = ds.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
.as[(String, String)]
dsStruc
にはスキーマがあるため、filter
、agg
、select
..etcなどのすべてのSQL種類の操作を受け入れます。
dsStruc
.writeStream // use `write` for batch, like DataFrame
.format("kafka")
.option("kafka.bootstrap.servers", "brokerhost1:port1,brokerhost2:port2")
.option("topic", "target-topic1")
.start()
詳細 Kafka読み取りまたは書き込みの統合 の設定)==
"org.Apache.spark" % "spark-core_2.11" % 2.2.0,
"org.Apache.spark" % "spark-streaming_2.11" % 2.2.0,
"org.Apache.spark" % "spark-sql-kafka-0-10_2.11" % 2.2.0,
私は同じ問題を抱えていて、 この投稿 を見つけました。
著者は、エグゼキューターごとに1つのプロデューサーを作成することで問題を解決します。プロデューサー自体を送信する代わりに、彼はそれをブロードキャストすることによってエグゼキューターでプロデューサーを作成する方法を「レシピ」のみを送信します。
val kafkaSink = sparkContext.broadcast(KafkaSink(conf))
彼は、プロデューサーを遅延的に作成するラッパーを使用します。
class KafkaSink(createProducer: () => KafkaProducer[String, String]) extends Serializable {
lazy val producer = createProducer()
def send(topic: String, value: String): Unit = producer.send(new ProducerRecord(topic, value))
}
object KafkaSink {
def apply(config: Map[String, Object]): KafkaSink = {
val f = () => {
val producer = new KafkaProducer[String, String](config)
sys.addShutdownHook {
producer.close()
}
producer
}
new KafkaSink(f)
}
}
Kafkaプロデューサーはエグゼキューターで最初に使用する直前に初期化されるため、ラッパーはシリアル化可能です。ドライバーはラッパーへの参照を保持し、ラッパーは各エグゼキューターのプロデューサーを使用してメッセージを送信します。
dstream.foreachRDD { rdd =>
rdd.foreach { message =>
kafkaSink.value.send("topicName", message)
}
}
なぜ実行不可能なのですか?基本的に、各RDDの各パーティションは独立して実行されます(また、異なるクラスターノードで実行される可能性があります)haveを使用して、各パーティションのタスクの開始時に接続(および同期)をやり直します。そのオーバーヘッドが大きすぎる場合は、StreamingContext
のバッチサイズを許容範囲内になるまで増やす必要があります(これを行うにはレイテンシコストがかかります)。
(各パーティションで数千のメッセージを処理していない場合、スパークストリーミングが必要ですか?スタンドアロンアプリケーションを使用する方が良いでしょうか?)
これはあなたがやりたいことかもしれません。基本的に、レコードのパーティションごとに1つのプロデューサーを作成します。
input.foreachRDD(rdd =>
rdd.foreachPartition(
partitionOfRecords =>
{
val props = new HashMap[String, Object]()
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers)
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
"org.Apache.kafka.common.serialization.StringSerializer")
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
"org.Apache.kafka.common.serialization.StringSerializer")
val producer = new KafkaProducer[String,String](props)
partitionOfRecords.foreach
{
case x:String=>{
println(x)
val message=new ProducerRecord[String, String]("output",null,x)
producer.send(message)
}
}
})
)
役立つことを願っています