J'utilise Spark Le streaming pour traiter des données entre deux Kafka files d'attente, mais je n'arrive pas à trouver un bon moyen d'écrire Kafka de Spark, j'ai essayé ceci:
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)
}
}
)
)
et cela fonctionne comme prévu, mais instiller un nouveau KafkaProducer pour chaque message est clairement irréalisable dans un contexte réel et j'essaie de le contourner.
Je souhaite conserver une référence à une seule instance pour chaque processus et y accéder lorsque j'ai besoin d'envoyer un message. Comment puis-je écrire à Kafka à partir de Spark Streaming?
Mon premier conseil serait d'essayer de créer une nouvelle instance dans foreachPartition et de déterminer si elle est assez rapide pour vos besoins (instancier des objets lourds dans foreachPartition est ce que la documentation officielle suggère).
Une autre option consiste à utiliser un pool d'objets comme illustré dans cet exemple:
J'ai toutefois trouvé difficile à mettre en œuvre lors de l'utilisation de points de contrôle.
Une autre version qui fonctionne bien pour moi est une usine telle que décrite dans l'article suivant, il vous suffit de vérifier si elle fournit suffisamment de parallélisme pour vos besoins (consultez la section commentaires):
Oui, malheureusement Spark (1.x, 2.x) ne permet pas de savoir comment écrire Kafka de manière efficace.
Je suggérerais l'approche suivante:
KafkaProducer
par processus exécuteur/JVM.Voici la configuration de haut niveau pour cette approche:
KafkaProducer
de Kafka car, comme vous l'avez dit, il n'est pas sérialisable. L'emballer vous permet de "l'expédier" aux exécuteurs. L'idée clé ici est d'utiliser un lazy val
afin que vous retardiez l’instanciation du producteur jusqu’à sa première utilisation, ce qui est en fait une solution de contournement afin que vous n’ayez pas à vous soucier de KafkaProducer
qui n’est pas sérialisable.Les extraits de code ci-dessous fonctionnent avec Spark en streaming à partir de Spark 2.0.
Étape 1: Habillage KafkaProducer
import Java.util.concurrent.Future
import org.Apache.kafka.clients.producer.{KafkaProducer, ProducerRecord, RecordMetadata}
class MySparkKafkaProducer[K, V](createProducer: () => KafkaProducer[K, V]) extends Serializable {
/* This is the key idea that allows us to work around running into
NotSerializableExceptions. */
lazy val producer = createProducer()
def send(topic: String, key: K, value: V): Future[RecordMetadata] =
producer.send(new ProducerRecord[K, V](topic, key, value))
def send(topic: String, value: V): Future[RecordMetadata] =
producer.send(new ProducerRecord[K, V](topic, value))
}
object MySparkKafkaProducer {
import scala.collection.JavaConversions._
def apply[K, V](config: Map[String, Object]): MySparkKafkaProducer[K, V] = {
val createProducerFunc = () => {
val producer = new KafkaProducer[K, V](config)
sys.addShutdownHook {
// Ensure that, on executor JVM shutdown, the Kafka producer sends
// any buffered messages to Kafka before shutting down.
producer.close()
}
producer
}
new MySparkKafkaProducer(createProducerFunc)
}
def apply[K, V](config: Java.util.Properties): MySparkKafkaProducer[K, V] = apply(config.toMap)
}
Étape 2: Utilisez une variable de diffusion pour donner à chaque exécuteur sa propre instance KafkaProducer
enveloppée
import org.Apache.kafka.clients.producer.ProducerConfig
val ssc: StreamingContext = {
val sparkConf = new SparkConf().setAppName("spark-streaming-kafka-example").setMaster("local[2]")
new StreamingContext(sparkConf, Seconds(1))
}
ssc.checkpoint("checkpoint-directory")
val kafkaProducer: Broadcast[MySparkKafkaProducer[Array[Byte], String]] = {
val kafkaProducerConfig = {
val p = new Properties()
p.setProperty("bootstrap.servers", "broker1:9092")
p.setProperty("key.serializer", classOf[ByteArraySerializer].getName)
p.setProperty("value.serializer", classOf[StringSerializer].getName)
p
}
ssc.sparkContext.broadcast(MySparkKafkaProducer[Array[Byte], String](kafkaProducerConfig))
}
Étape 3: écrivez à partir de Spark Diffusion en continu sur Kafka, réutilisation du même exemple KafkaProducer
enveloppé (pour chaque exécuteur)
import Java.util.concurrent.Future
import org.Apache.kafka.clients.producer.RecordMetadata
val stream: DStream[String] = ???
stream.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
val metadata: Stream[Future[RecordMetadata]] = partitionOfRecords.map { record =>
kafkaProducer.value.send("my-output-topic", record)
}.toStream
metadata.foreach { metadata => metadata.get() }
}
}
J'espère que cela t'aides.
Il y a un Streaming Kafka Writer maintenu par Cloudera (en réalité dérivé d'un Spark JIRA [1] )). Il crée fondamentalement un producteur par partition, qui amortit le temps consacré à la création d'objets "lourds" par rapport à une collection d'éléments (espérons-le volumineux).
Le rédacteur peut être trouvé ici: https://github.com/cloudera/spark-kafka-writer
Avec Spark> = 2.2
Les deux opérations de lecture et d'écriture sont possibles sur Kafka à l'aide de l'API Structured Streaming
// 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()
Lisez la clé et la valeur et appliquez le schéma aux deux. Pour simplifier, nous convertissons les deux en String
type.
val dsStruc = ds.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
.as[(String, String)]
Puisque dsStruc
a le schéma, il accepte toutes les opérations de type SQL telles que filter
, agg
, select
..etc dessus.
dsStruc
.writeStream // use `write` for batch, like DataFrame
.format("kafka")
.option("kafka.bootstrap.servers", "brokerhost1:port1,brokerhost2:port2")
.option("topic", "target-topic1")
.start()
Plus configuration pour Kafka en lecture ou en écriture
"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,
J'avais le même problème et j'ai trouvé ce post .
L'auteur résout le problème en créant un producteur par exécuteur. Au lieu d’envoyer le producteur lui-même, il n’envoie qu’une "recette" pour créer un producteur dans un exécuteur en le diffusant.
val kafkaSink = sparkContext.broadcast(KafkaSink(conf))
Il utilise un wrapper qui crée paresseusement le producteur:
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)
}
}
Le wrapper est sérialisable car le producteur Kafka est initialisé juste avant la première utilisation sur un exécuteur. Le pilote garde la référence au wrapper et celui-ci envoie les messages à l'aide du producteur de chaque exécuteur:
dstream.foreachRDD { rdd =>
rdd.foreach { message =>
kafkaSink.value.send("topicName", message)
}
}
Pourquoi est-ce infaisable? Fondamentalement, chaque partition de chaque RDD s'exécutera indépendamment (et peut s'exécuter sur un nœud de cluster différent), de sorte que vous devez pour rétablir la connexion (et toute synchronisation) au début de la tâche de chaque partition. Si les frais généraux sont trop élevés, augmentez la taille du lot dans votre StreamingContext
jusqu'à ce que cela devienne acceptable (dans ce cas, il y a un coût en latence).
(Si vous ne gérez pas des milliers de messages dans chaque partition, êtes-vous sûr de vouloir diffuser en continu des étincelles? Feriez-vous mieux avec une application autonome?)
Cela pourrait être ce que vous voulez faire. Vous créez essentiellement un producteur pour chaque partition d’enregistrements.
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)
}
}
})
)
J'espère que ça t'as aidé