web-dev-qa-db-ja.com

kafka 0.9.0でマルチスレッドコンシューマを使用する方法

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;
        }
    }

マルチスレッドのflinkコード

どうしましたか?

10
Acceml

Kafkaの消費者はスレッドセーフではありませんです。あなたがあなたの質問で指摘したように、文書はこう述べています

簡単なオプションは、各スレッドに独自のコンシューマインスタンスを与えることです

しかし、コードでは、異なるKafkaConsumerRunnerインスタンスによってラップされた同じコンシューマーインスタンスがあります。したがって、複数のスレッドが同じコンシューマインスタンスにアクセスしています。 kafkaドキュメントは明確に述べられています

Kafkaコンシューマはスレッドセーフではありません。すべてのネットワークI/Oは、呼び出しを行うアプリケーションのスレッドで発生します。マルチスレッドアクセスが適切であることを確認するのはユーザーの責任です同期されます。同期されていないアクセスでは、ConcurrentModificationExceptionが発生します。

それはまさにあなたが受け取った例外です。

14
Lan

サブスクライブの呼び出しで例外がスローされています。 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;
    }
}
3
Steve

多分あなたのケースではないかもしれませんが、あなたがサーバートピックのデータをマージ処理しているなら、あなたは同じコンシューマーで複数のトピックからデータを読むことができます。そうでない場合は、各トピックを使用する個別のジョブを作成することをお勧めします。

2
demonodojo