如果我的Kafka主题收到
之类的记录CHANNEL | VIEWERS | .....
ABC | 100 | .....
CBS | 200 | .....
我有Spark结构化流代码来读取和处理Kafka记录,如下所示:
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
目前,我使用foreachwriter处理记录如下:
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()块内部执行与该通道相关的内存计算。那可能吗 ?如果是,我该怎么做?
答案 0 :(得分:1)
代码在记录级别应用 handle
方法并且独立于记录的分区。
我看到有两个选项可以确保在同一个执行器上处理同一频道的所有消息:
如果您可以控制 KafkaProducer 将数据生成到主题“partition_test”中,则可以将 channel
的值设置为 Kafka 消息的键。默认情况下,KafkaProducer 使用 key 来定义数据写入的分区。这将确保具有相同键的所有消息都将落在同一个 Kafka 主题分区中。由于使用 Kafka 主题的 Spark Structured Streaming 作业将匹配 Kafka 分区,因此您生成的 dataFrame
将具有与 Kafka 主题相同数量的分区,并且同一通道的所有消息都在同一分区中。
正如评论中已经写的那样,您可以通过执行 dataFrame
根据列 channel
的值简单地重新分区 dataFrame.repartition(n, col("columnName"))
,其中 n
是分区数。这样,所有具有相同通道的记录都会落在同一个分区中,因此会在同一个执行器上进行处理。
两个重要的注意事项:
获得分区(数据帧或 Kafka 主题)的所有权需要一些额外的关注,因为您最终可能会遇到所谓的“数据倾斜”。与只有几条消息的分区相比,当您有包含大量消息的分区时,就会发生数据倾斜。这会对您的整体表现产生负面影响。
只要您使用的是 foreach
输出接收器,在记录级别处理数据时,您的数据如何分区都无关紧要。如果您正在寻找更多控制权,您可能宁愿使用 foreachBatch
接收器(在 Spark 2.4+ 中可用)。 foreachBatch 输出接收器使您可以控制每个微批次的批次数据帧,并且您可以使用 foreachPartitions
或 mapPartitions
执行基于分区的逻辑。