Kafka同時実行問題からのスパーク読み取り
過去の大規模なデータメモリ過去のメモリビッグデータ
が頻繁に使用するApacheSparkは、Kafkaの学生からの読み取りでこのような問題が発生することは確実です。一部のSparkパーティションデータは処理され、パーティションの別の部分はまだデータを処理しているため、このバッチジョブが発生します。消費時間が長くなり、SparkジョブがKafkaのデータを時間内に消費できなくなることさえあります。簡単にするために、この記事で説明するSpark Directメソッドは、Kafkaのデータを読み取ります。この場合、Spark RDDのパーティションとKafkaパーティションは1対1で対応します。詳細については、公式ドキュメントを参照してください。ここでは紹介しません。
では、この問題を解決する方法はありますか?まず、コミュニティがこの問題をどのように解決するかを見てみましょう。
SPARK-22056の問題は、この問題を提起し、解決策を提供しました。
Spark、Hadoop、またはHbase関連の記事について時間内に知りたい場合は、WeChatパブリックアカウントに従ってください:iteblog_hadoop
つまり、KafkaRDDクラスのgetPartitionsメソッドが変更されます。
元の実装:
override def getPartitions: Array[Partition] = {
offsetRanges.zipWithIndex.map { case (o, i) =>
val (host, port) = leaders(TopicAndPartition(o.topic, o.partition))
new KafkaRDDPartition(i, o.topic, o.partition, o.fromOffset, o.untilOffset, host, port)
}.toArray
}
変更された実装:
override def getPartitions: Array[Partition] = {
val subconcurrency = if (kafkaParams.contains("topic.partition.subconcurrency"))
kafkaParams.getOrElse("topic.partition.subconcurrency","1").toInt
else 1
val numPartitions = offsetRanges.length
val subOffsetRanges: Array[OffsetRange] = new Array[OffsetRange](subconcurrency * numPartitions)
for (i <- 0 until numPartitions) {
val offsetRange = offsetRanges(i)
val step = (offsetRange.untilOffset - offsetRange.fromOffset) / subconcurrency
var from = -1L
var until = -1L
for (j <- 0 until subconcurrency) {
from = offsetRange.fromOffset + j * step
until = offsetRange.fromOffset + (j + 1) * step -1
if (j == subconcurrency) {
until = offsetRange.untilOffset
}
subOffsetRanges(i * subconcurrency + j) = OffsetRange.create(offsetRange.topic, offsetRange.partition, from, until)
}
}
subOffsetRanges.zipWithIndex.map{ case (o, i) =>
val (host, port) = leaders(TopicAndPartition(o.topic, o.partition))
new KafkaRDDPartition(i, o.topic, o.partition, o.fromOffset, o.untilOffset, host, port)
}.toArray
}
このメソッドの実現のアイデアはまだ非常に単純です。つまり、topic.partition.subconcurrencyパラメーターを設定することにより、このパラメーターが1に等しい場合、関数全体の実行効果は以前と同じです。ただし、このパラメーターが1より大きい場合、前のKafkaパーティションの1つのSparkパーティションによって消費されるデータはtopic.partition.subconcurrency Sparkパーティションによって消費され、各Sparkパーティションは同じ量のデータを消費します。これは間違いなくKafkaデータの消費をスピードアップしますが、この方法の問題も明らかです。
データの順序が重要な場合、この方法には無秩序の問題があります。
Sparkによって設計されたKafkaRDDの目的は、KafkaパーティションとSpark RDDパーティションを1対1で対応させて、同じパーティション内のデータの順序を確保することですが、この方法はKafka間の1対多の関係になっています。パーティションとSparkRDDパーティション。これは間違いなくKafkaパーティションとSparkRDDパーティションの関係を破壊します。公式のオリジナルデザイン。
これらの問題に基づいて、コミュニティのjerryshaoとkoeningerのリーダーはこれについて楽観的ではありません:(PR 19274)
jerryshao: Hi @loneknightpy , think a bit on your PR, I think this can also be done in the user side. User could create several threads in one task (RDD#mapPartitions) to consume the records concurrently, so such feature may not be so necessary to land in Spark's code. What do you think?
koeninger: Search Jira and the mailing list, this idea has been brought up multiple times. I don't think breaking fundamental assumptions of Kafka (one consumer thread per group per partition) is a good idea.
これまでのところ、上記のPRは終了しており、SPARK-22056は進行中の状態にありますが、これも最終的には終了する可能性があると思います。
上記の実装に加えて、他の実装はありますか?もちろん、データを処理する前に、再パーティション化または合体によってデータを再パーティション化することができます。
val kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder,
StringDecoder](streamingContext, kafkaParams, topics).repartition(xxx).mapPartition(xxx)
この方法の利点は、同じタイプのデータの場合、再パーティション化後に同じタイプのデータが最終的に同じパーティションに分散されるため、順序が乱雑にならないことです。
ただし、この方法の前提は、データの再パーティション化とその後の処理の時間が、再パーティション化せずにデータを直接処理する時間よりも短いことです。そうしないと、再パーティション化のコストが高くなり、合計処理時間が長すぎて意味がありません。
もちろん、RDD#mapPartitionsに複数のスレッドを作成することで、同じRDDパーティション内のデータを処理できます。
ただし、上記の2つの方法では、Kafka側のデータスキューによって引き起こされるデータ処理の速度低下の問題を解決できません(つまり、一部のパーティションのデータ量が他のパーティションよりもはるかに多く、これらのパーティションのデータを消費する時間は他のパーティションよりもはるかに長い)。この状況に対応して、Kafkaパーティション設定が妥当かどうかを検討する必要がありますか?Kafkaパーティショニングの実装を変更することで、データスキューの問題を解決できますか?
Kafkaデータスキューが原因でデータ処理が遅くなるのではなく、すべてのKafkaパーティションの全体的なデータ量が比較的大きい場合、この場合にKafkaパーティションの数を増やすことができるかどうかを検討できますか?Sparkの処理リソースを増やす必要がありますか?同じKafkaパーティション内のデータを処理するために複数のスレッドを使用しないことをお勧めします。