kafkaのドキュメントは、以下の説明でアプローチを提供します:
スレッドごとに1つのコンシューマー:単純なオプションは、各スレッドに独自のコンシューマー>インスタンスを与えることです。
私のコード:
public class KafkaConsumerRunner implements Runnable {
private final AtomicBoolean closed = new AtomicBoolean(false);
private final CloudKafkaConsumer consumer;
private final String topicName;
public KafkaConsumerRunner(CloudKafkaConsumer consumer, String topicName) {
this.consumer = consumer;
this.topicName = topicName;
}
@Override
public void run() {
try {
this.consumer.subscribe(topicName);
ConsumerRecords<String, String> records;
while (!closed.get()) {
synchronized (consumer) {
records = consumer.poll(100);
}
for (ConsumerRecord<String, String> tmp : records) {
System.out.println(tmp.value());
}
}
} catch (WakeupException e) {
// Ignore exception if closing
System.out.println(e);
//if (!closed.get()) throw e;
}
}
// Shutdown hook which can be called from a separate thread
public void shutdown() {
closed.set(true);
consumer.wakeup();
}
public static void main(String[] args) {
CloudKafkaConsumer kafkaConsumer = KafkaConsumerBuilder.builder()
.withBootstrapServers("172.31.1.159:9092")
.withGroupId("test")
.build();
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(new KafkaConsumerRunner(kafkaConsumer, "log"));
executorService.execute(new KafkaConsumerRunner(kafkaConsumer, "log.info"));
executorService.shutdown();
}
}
しかし、それは機能せず、例外をスローします:
Java.util.ConcurrentModificationException:KafkaConsumerはマルチスレッドアクセスに対して安全ではありません
さらに、Flink(分散ストリームおよびバッチデータ処理用のオープンソースプラットフォーム)のソースを読みました。マルチスレッドコンシューマーを使用したFlinkは、私のものに似ています。
long pollTimeout = Long.parseLong(flinkKafkaConsumer.properties.getProperty(KEY_POLL_TIMEOUT, Long.toString(DEFAULT_POLL_TIMEOUT)));
pollLoop: while (running) {
ConsumerRecords<byte[], byte[]> records;
//noinspection SynchronizeOnNonFinalField
synchronized (flinkKafkaConsumer.consumer) {
try {
records = flinkKafkaConsumer.consumer.poll(pollTimeout);
} catch (WakeupException we) {
if (running) {
throw we;
}
// leave loop
continue;
}
}
どうしましたか?
Kafkaの消費者はスレッドセーフではありませんです。あなたがあなたの質問で指摘したように、文書はこう述べています
簡単なオプションは、各スレッドに独自のコンシューマインスタンスを与えることです
しかし、コードでは、異なるKafkaConsumerRunnerインスタンスによってラップされた同じコンシューマーインスタンスがあります。したがって、複数のスレッドが同じコンシューマインスタンスにアクセスしています。 kafkaドキュメントは明確に述べられています
Kafkaコンシューマはスレッドセーフではありません。すべてのネットワークI/Oは、呼び出しを行うアプリケーションのスレッドで発生します。マルチスレッドアクセスが適切であることを確認するのはユーザーの責任です同期されます。同期されていないアクセスでは、ConcurrentModificationExceptionが発生します。
それはまさにあなたが受け取った例外です。
サブスクライブの呼び出しで例外がスローされています。 this.consumer.subscribe(topicName);
そのブロックを次のように同期されたブロックに移動します。
@Override
public void run() {
try {
synchronized (consumer) {
this.consumer.subscribe(topicName);
}
ConsumerRecords<String, String> records;
while (!closed.get()) {
synchronized (consumer) {
records = consumer.poll(100);
}
for (ConsumerRecord<String, String> tmp : records) {
System.out.println(tmp.value());
}
}
} catch (WakeupException e) {
// Ignore exception if closing
System.out.println(e);
//if (!closed.get()) throw e;
}
}
多分あなたのケースではないかもしれませんが、あなたがサーバートピックのデータをマージ処理しているなら、あなたは同じコンシューマーで複数のトピックからデータを読むことができます。そうでない場合は、各トピックを使用する個別のジョブを作成することをお勧めします。