KafkaコンシューマーコンシューマーAPI操作の詳細な説明

1.準備

  • IDEでmavenプロジェクトを作成し、pomファイルに依存関係を追加します
<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>1.1.1</version>
</dependency>
  • zookeeperクラスターを開始します
bin/zkServer.sh start
  • kafkaクラスターを開始します
bin/kafka-server-start.sh -daemon config/server.properties
  • カフカクラスターがプロデューサーを開く
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic mydata 

2つ目は、通常の消費者を作成する(オフセットを自動的に送信する)

Kafkaではデータが永続的であるため、消費者のデータ消費の信頼性は簡単に保証され、データの損失を心配する必要はありません。

消費者は消費プロセス中に停電やダウンタイムなどの障害を経験する可能性があるため、消費者が回復した後、障害の前の場所から消費を継続する必要があります。したがって、消費者は、障害回復後も消費を継続できるように、消費したオフセットをリアルタイムで記録する必要があります。したがって、オフセットの維持は、消費者がデータを消費するときに考慮しなければならない問題です。

以下のコードでは、この消費は最大オフセットのみを記録します。これは、コマンドラインで--from-beginningを追加しないことと同等であり、前のデータは消費できません。

import java.util.Collections;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

public class MyConsumer {
    
    
    public static void main(String[] args) {
    
    
        Properties properties = new Properties();

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启自动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(true));

        /* 自动提交的延时时间 */
        properties.put("auto.commit.interval.ms", "1000");

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));


        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);


            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }
        }
    }
}

スクリーンショットを実行します。
ここに写真の説明を挿入

第三に、特定のトピックのデータを再消費する方法(オフセットを自動的に送信する)

コンシューマーグループを変更してオフセットをリセットできます

/* 指定消费者组 */
properties.put("group.id", "mygroup");

/* 重置消费者的offset */
properties.put("auto.offset.reset","earliest");

auto.offset.resetのソースコードは次のとおりです。2つの条件のいずれかが満たされます。コンシューマーグループが変更されるか、保存されたデータが無効になると(オフセットが無効な場合)、有効になり、オフセットがリセットされます。有効なタイプは2つあります。デフォルトは最新のオフセットであり、最も早いオフセット、最も早いオフセットに変更できます。

    public static final String AUTO_OFFSET_RESET_CONFIG = "auto.offset.reset";
    public static final String AUTO_OFFSET_RESET_DOC = "What to do when there is no 
    initial offset in Kafka or if the current offset does not exist any more on the 
    server (e.g. because that data has been deleted): <ul><li>earliest: automatically 
    reset the offset to the earliest offset<li>latest: automatically reset the offset to 
    the latest offset</li><li>none: throw exception to the consumer if no previous offset 
    is found for the consumer's group</li><li>anything else: throw exception to the 
    consumer.</li></ul>";

詳細なコードは次のとおりです。

import java.util.Collections;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

public class MyConsumer {
    
    
    public static void main(String[] args) {
    
    
        Properties properties = new Properties();

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启自动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(true));

        /* 自动提交的延时时间 */
        properties.put("auto.commit.interval.ms", "1000");

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup1");

        /* 重置消费者的offset */
        properties.put("auto.offset.reset","earliest");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));


        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }
        }
    }
}

4、手動でオフセットを送信します

  • オフセットの自動送信は非常に簡潔ですが、時間に基づいて送信されるため、開発者がオフセット送信のタイミング(遅延時間を把握することは困難ですしたがって、Kafkaはオフセットを手動で送信するためのAPIも提供します。

  • オフセットを手動で送信するには、commitSync(同期送信)とcommitAsync(非同期送信)の2つの方法があります。

    2つの類似点は、ポーリングデータのバッチの最大オフセットを送信することです。

    違いは、commitSyncは送信が成功するまで現在のスレッドをブロックし、自動的に失敗して再試行することです(制御できない要因により、送信の失敗もあります)。commitAsyncには失敗の再試行メカニズムがないため、送信が失敗する可能性があります。

  • 同期して送信するか非同期で送信するかにかかわらず、データの漏洩や繰り返しの消費を引き起こす可能性があります。オフセットを最初に送信してから消費すると、データ漏洩が発生する可能性があります。オフセットを消費後に送信すると、データが繰り返し消費される可能性があります。

同期送信

import java.util.Collections;
import java.util.Properties;
import org.apache.kafka.clients.consumer.*;

public class MyConsumer {
    
    
    public static void main(String[] args) {
    
    
        Properties properties = new Properties();

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启手动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(false));

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));

        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }

            /* 同步提交,当前线程会阻塞直到 offset 提交成功 */
            consumer.commitSync();

        }
    }
}

非同期送信:

import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;

public class MyConsumer {
    
    
    public static void main(String[] args) {
    
    
        Properties properties = new Properties();

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启手动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(false));

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));


        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }

            /* 异步提交 */
            consumer.commitAsync(new OffsetCommitCallback() {
    
    
                @Override
                public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
    
    
                    if (exception != null){
    
    
                        System.err.println("提交失败" + offsets);
                    }else {
    
    
                        System.err.println("提交成功" + offsets);
                    }
                }
            });
        }
    }
}

5、カスタムストレージオフセット

  • Kafkaバージョン0.9より前は、オフセットはzookeeperに保存されていましたが、バージョン0.9以降、オフセットはデフォルトでKafkaの組み込みトピックに保存されていました。さらに、Kafkaはストレージオフセットをカスタマイズすることもできます。消費者のリバランスを考慮する必要があるため、オフセットの維持は非常に面倒です

  • 新しいコンシューマーがコンシューマーグループに参加するか、既存のコンシューマーがコンシューマーグループを起動するか、サブスクライブされたトピックのパーティションが変更されると、パーティションの再割り当てがトリガーされます。再割り当てのプロセスは、リバランスと呼ばれます。

  • 消費者のリバランスが発生すると、各消費者の消費パーティションが変更されます。したがって、消費者は最初に再割り当て先のパーティションを取得し、消費を継続するために各パーティションによって最近送信されたオフセット位置を見つける必要があります。

  • カスタムストレージオフセットを実装するには、ConsumerRebalanceListenerクラスを使用する必要があります。オフセットを送信および取得する方法は、選択したオフセットストレージシステムによって実装される必要があります。

import java.util.*;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;

public class MyConsumer {
    
    
    private static Map<TopicPartition, Long> currentOffset = new  HashMap<>();

    public static void main(String[] args) {
    
    
        Properties properties = new Properties();

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启手动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(false));

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 消费者订阅的主题 */
        consumer.subscribe(Arrays.asList("bigdata"), new ConsumerRebalanceListener() {
    
    

            /* 该方法会在 Rebalance 之前调用 */
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
    
    
                /* 调用自己写的提交所有分区的 offset 方法 */
                commitOffset(currentOffset);
            }

            /* 该方法会在 Rebalance 之后调用 */
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
    
    
                currentOffset.clear();
                /* 遍历 */
                for (TopicPartition partition : partitions) {
    
    
                    /* 定位到最近提交的 offset 位置继续消费 */
                    consumer.seek(partition, getOffset(partition));
                }
            }
        });

        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());

                currentOffset.put(new TopicPartition(consumerRecord.topic(), consumerRecord.partition()),consumerRecord.offset());
            }

            /* 异步提交 */
            commitOffset(currentOffset);
        }
    }

    /* 获取某分区的最新 offset */
    private static long getOffset(TopicPartition partition) {
    
    
        return 0;
    }

    /* 提交该消费者所有分区的 offset */
    private static void commitOffset(Map<TopicPartition, Long> currentOffset) {
    
    
        /* 自己定义的方法,根据业务逻辑,也可以提交到mysql上,写一个jdbc,创建消费者组、主题、分区字段 */
    }
}

おすすめ

転載: blog.csdn.net/weixin_46122692/article/details/109280040