Scala Spark Structured Streaming with Kafka-如何重新划分数据并在工作节点之间分配处理

Scala Spark Structured Streaming with Kafka-如何重新划分数据并在工作节点之间分配处理,scala,apache-spark,apache-kafka,spark-structured-streaming,spark-kafka-integration,Scala,Apache Spark,Apache Kafka,Spark Structured Streaming,Spark Kafka Integration,如果我的卡夫卡主题收到如下记录 CHANNEL | VIEWERS | ..... ABC | 100 | ..... CBS | 200 | ..... 我使用Spark结构化流式代码读取和处理卡夫卡记录,如下所示: val spark = SparkSession .builder .appName("TestPartition") .master("local[*]") .getOrCreate()

如果我的卡夫卡主题收到如下记录

CHANNEL | VIEWERS | .....
ABC     |  100    | .....
CBS     |  200    | .....
我使用Spark结构化流式代码读取和处理卡夫卡记录,如下所示:

val spark = SparkSession 
      .builder 
      .appName("TestPartition") 
      .master("local[*]") 
      .getOrCreate() 

    import spark.implicits._ 

    val dataFrame = spark 
      .readStream 
      .format("kafka") 
      .option("kafka.bootstrap.servers", 
      "1.2.3.184:9092,1.2.3.185:9092,1.2.3.186:9092") 
      .option("subscribe", "partition_test") 
      .option("failOnDataLoss", "false") 
      .load() 
      .selectExpr("CAST(value AS STRING)") 
      // I will use a custom UDF to transform to a specific object
val writer = new ForeachWriter[testRec] {
    def open(partitionId: Long, version: Long): Boolean = {
      true
    }
    def process(record: testRec) = {
      handle(record)
    }
    def close(errorOrNull: Throwable): Unit = {
    }
  }

  val query = dataFrame.writeStream
    .format("console")
    .foreach(writer)
    .outputMode("append")
    .start()
目前,我使用foreachwriter处理记录,如下所示:

val spark = SparkSession 
      .builder 
      .appName("TestPartition") 
      .master("local[*]") 
      .getOrCreate() 

    import spark.implicits._ 

    val dataFrame = spark 
      .readStream 
      .format("kafka") 
      .option("kafka.bootstrap.servers", 
      "1.2.3.184:9092,1.2.3.185:9092,1.2.3.186:9092") 
      .option("subscribe", "partition_test") 
      .option("failOnDataLoss", "false") 
      .load() 
      .selectExpr("CAST(value AS STRING)") 
      // I will use a custom UDF to transform to a specific object
val writer = new ForeachWriter[testRec] {
    def open(partitionId: Long, version: Long): Boolean = {
      true
    }
    def process(record: testRec) = {
      handle(record)
    }
    def close(errorOrNull: Throwable): Unit = {
    }
  }

  val query = dataFrame.writeStream
    .format("console")
    .foreach(writer)
    .outputMode("append")
    .start()

代码运行得很好。但是,我想做的是将传入的数据按通道进行分区,这样每个工人都负责特定的通道,我在handle()块中进行与该通道相关的内存计算。可能吗?如果是,我如何做到这一点?

代码按原样在记录级别应用
handle
方法,并且与记录的分区无关

我看到两个选项可确保同一通道的所有消息将在同一执行器上处理:

  • 如果您可以控制KafkaProducer将数据生成到主题“partition_test”中,则可以将
    channel
    的值设置为Kafka消息的键。默认情况下,KafkaProducer使用键定义数据写入的分区。这将确保具有相同密钥的所有消息都将到达相同的Kafka主题分区。由于使用卡夫卡主题的Spark结构化流媒体作业将匹配卡夫卡分区,因此生成的
    dataFrame
    将具有与卡夫卡主题相同的分区数量,并且同一频道的所有消息都位于同一分区中

  • 如注释中所述,您可以通过执行
    dataFrame.repartition(n,col(“columnName”)
    ,根据列
    channel
    的值简单地重新划分
    dataFrame
    ,其中
    n
    是分区数。这样,具有相同通道的所有记录都将落在同一分区中,因此将在同一执行器上处理

  • 两个重要注意事项:

    • 拥有分区(数据帧或Kafka主题)的所有权需要额外注意,因为最终可能会出现所谓的“数据倾斜”。与只有少量消息的分区相比,当您有包含大量消息的分区时,就会发生数据倾斜。这将对您的整体绩效产生负面影响

    • 只要您使用的是
      foreach
      输出接收器,在记录级别处理数据时,数据的分区方式就无关紧要了。如果您正在寻找更多的控件,您可以使用
      foreachBatch
      sink(Spark 2.4+中提供)。foreachBatch输出接收器使您可以控制每个微批次的批次数据帧,并且您可以使用
      foreachPartitions
      mapPartitions
      执行基于分区的逻辑


    dataFrame.repartition($“columnName”)选项1是否也会出现数据倾斜?是,为真。谢谢,我已经在我的答复中详细说明了这一点。