kafka基础知识,以及为什么异步和同步组合提交可以避免偏移量出问题,生产者最佳实践异步发送,消费者的最佳实践异步拉取异步加同步提交

kafka介绍

Kafka 是一个分布式流媒体平台,类似于消息队列或企业消息传递系统。kafka官网:Apache Kafka

  • producer:发布消息的对象称之为主题生产者(Kafka topic producer)

  • topic:Kafka将消息分门别类,每一类的消息称之为一个主题(Topic)

  • consumer:订阅消息并处理发布的消息的对象称之为主题消费者(consumers)

  • broker:已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。

kafka安装配置

Kafka对于zookeeper是强依赖,保存kafka相关的节点数据,所以安装Kafka之前必须先安装zookeeper

kafka移除容器重新开后,想要zookeeper也重新开

Docker安装zookeeper

下载镜像:

docker pull zookeeper:3.4.14

创建容器

docker run -d --name zookeeper -p 2181:2181 zookeeper:3.4.14

Docker安装kafka:下载镜像:

docker pull wurstmeister/kafka:2.12-2.3.1

创建容器

docker run -d --name kafka \
--env KAFKA_ADVERTISED_HOST_NAME=192.168.200.130 \
--env KAFKA_ZOOKEEPER_CONNECT=192.168.200.130:2181 \
--env KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.200.130:9092 \
--env KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
--env KAFKA_HEAP_OPTS="-Xmx256M -Xms256M" \
--net=host wurstmeister/kafka:2.12-2.3.1

kafka入门

生产者发送消息

(1)创建kafka-demo项目,导入依赖

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
</dependency>

(2)编写消息生产者类ProducerQuickstart

①.设置kafka的配置信息

properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.200.130:9092");//消息key的序列化器  连接kafka,只当端口ip


//消息要在网络上运行,使用要对消息的key和value进行序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");//消息value的序列化器

properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");

②.创建生产者对象

KafkaProducer<String,String> producer = new KafkaProducer<String, String>(properties);

③.发送消息

ProducerRecord<String,String> record = new ProducerRecord<String,String>(“topic","key",“value");producer.send(record);

④.关闭消息通道

producer.close();

代码实现

public class ProducerQuickStart {
    public static void main(String[] args) {
//        1.kafka连接的配置信息
        Properties prop = new Properties();
//        kafka连接地址
//        ProducerConfig.BOOTSTRAP_SERVERS_CONFIG相当于"bootstrap.servers",所以这里也可以用这个字符串
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.200.130:9092");
//        key和value指定序列化器,使用的是kafka的序列化器,在kafka的common包下,全类名为
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");

//        2.创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(prop);
//        发送消息
        /**
         * 第一个参数:消息的topic(主题)
         * 第二个参数:消息的key
         * 第三个参数:消息的value
         */
        ProducerRecord<String, String> kvProducerRecord = new ProducerRecord<>("topic-first", "key-001", "hello kafka");
        producer.send(kvProducerRecord);
//        4.关闭消息通道,必须要关闭,否则消息发送不成功
        producer.close();

    }
}

创建ConsumerQuickStart消费者类

①:设置kafka的配置信息

//连接信息
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.200.130:9092");
//指定消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"group2");
//反序列化的key和value
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");

②:创建消费者对象

KafkaConsumer<String,String> consumer = new KafkaConsumer<String,String>(properties);

③:订阅主题

consumer.subscribe(Collections.singletonList("itcast-001"));

④:获取消息

ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));

代码实现

public class  ConsumerQuickStart {
    public static void main(String[] args) {
//        1.kafka的配置信息
        Properties prop = new Properties();
//        连接地址
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.200.130:9092");
//        消费者的key和value的反序列化器
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
//        设置消费者组
        prop.put(ConsumerConfig.GROUP_ID_CONFIG, "group_1");
//          2.创建消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(prop);
//          3.订阅主题topic
        consumer.subscribe(Collections.singletonList("topic-first"));

//        4.拉取消息,为了让他一直执行,这里用死循环实现
        while (true){
            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key());
                System.out.println(consumerRecord.value());
            }
        }
    }
}

kafka分区机制

分区,可以理解成是一个存储topic的文件夹,同时发送消息时可以指定topic和分区,也就是让不同数据存储到不同分区下以及不同机器上,增强kafka的伸缩性(p是一个分区,broker一个节点,也叫代理,也叫服务器,t是topic,主题)

每个分区下都维护了一个连续自增的数值来标记消息在什么位置,也叫做偏移量

发消息时,分区策略有三种,默认是轮询,消费者接收消息时,拿到这条消息所在分区

while (true){
            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key());
                System.out.println(consumerRecord.value());
                System.out.println(consumerRecord.partition());
            }
        }

生产者发送消息时,也可以指定分区,也可以不指定分区,不指定默认是轮询,不写key也是轮询,写了key就是按键保存(计算hashcode值)

//        发送消息
        /**
         * 第一个参数:消息的topic(主题)
         * 第二个参数:分区
         * 第三个参数:消息的key
         * 第三个参数:消息的value
         */
        ProducerRecord<String, String> kvProducerRecord = new ProducerRecord<>("topic-first", 0, "key-001", "hello kafka");
        producer.send(kvProducerRecord);
//        4.关闭消息通道,必须要关闭,否则消息发送不成功
        producer.close();

kafka面试:

kafka高可用设计:

kafka备份机制

kafka备份机制-同步方式

追随者副本(Follower Replica也有两种,一种是ISR,采用的是同步备份的机制,一种是普通,采用异步备份的机制,都是从Leader Replica中获取)

如果所有副本都失效了,如何解决

如果要保证一致性,选择ISR中第一个活过来的副本做Leader(CP)

如果要保证系统的可用性,选择第一个活过来的副本族谱Leader(AP)

kafka生产者详解

发送类型:同步发送和异步发送

同步发送:

缺点:数据量越大,发送时间就越长,并且还会有阻塞风险,可能会导致服务雪崩

使用send()方法发送,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功

        try {
            RecordMetadata recordMetadata = producer.send(kvProducerRecord).get();
            System.out.println(recordMetadata.offset());   //偏移量,在分区小自增的数值,用于编号消息
        } catch (Exception e) {
            e.printStackTrace();
        }


另一种写法
ProducerRecord<String, String> record = 
    new ProducerRecord<>("my-topic", "key", "value");

// 调用 .get() 就变成了“同步发送”
Future<RecordMetadata> future = producer.send(record);
RecordMetadata metadata = future.get(); // ⛔ 阻塞等待!

异步发送:

优点:如果消息发送失败,会抛出异常,此时可以保存这个异常到日志表中 ,方便后续分析业务,或者做一些补偿

最好采用异步的方式进行消息的发送,因为不仅可以拿到返回值,还可以记录错误信息,还不容易阻塞

调用send()方法,并指定一个回调函数,服务器在返回响应时调用函数

        //消息发送失败,会抛出异常

producer.send(kvProducerRecord, new Callback() {
            @Override
            public void onCompletion(RecordMetadata metadata, Exception exception) {
                if (exception != null){
                    System.out.println("记录错误信息到日志");
                }
                System.out.println(metadata.offset());
            }
        });


另一种写法
ProducerRecord<String, String> record = 
    new ProducerRecord<>("topic-name", "key", "value");

producer.send(record, new Callback() {
    @Override
    public void onCompletion(RecordMetadata metadata, Exception exception) {
        if (exception == null) {
            System.out.println("✅ 发送成功,分区:" + metadata.partition() + ", offset:" + metadata.offset());
        } else {
            System.err.println("❌ 发送失败:" + exception.getMessage());
            // 在这里可以做重试、记录日志、告警等
        }
    }
});

kafka生产者参数

响应参数

acks=0 的配置意味着生产者不会等待任何来自服务器的响应,只要消息被发送出去就认为发送成功了。让我们详细解释一下 acks 参数的不同设置及其影响。

acks=1表示leader返回成功的确认信息生产者就认为发送成功了

一般采用acks = 1

acks=2代表leader要将所有消息同步到所有foller,同步成功leader才会给producer返回一个确认

重试次数

producer发送消息给leader,若leader一直不返回确认消息,重复10次后,若都没有成功,消息发送直接失败

消息压缩

根据实际情况进行选择,在开发者用的最多的事snappy

kafka消费者详解

消费者组

代码:

public class ProducerQuickStart {
    public static void main(String[] args) {
//        1.kafka连接的配置信息
        Properties prop = new Properties();
//        kafka连接地址
//        ProducerConfig.BOOTSTRAP_SERVERS_CONFIG相当于"bootstrap.servers",所以这里也可以用这个字符串
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.200.130:9092");
//        key和value指定序列化器,使用的是kafka的序列化器,在kafka的common包下,全类名为
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");

//        确认机制
        prop.put(ProducerConfig.ACKS_CONFIG, "1");
//        重试次数
        prop.put(ProducerConfig.RETRIES_CONFIG ,10);
//        数据压缩
        prop.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");

//        2.创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(prop);
//        发送消息
        /**
         * 第一个参数:消息的topic(主题)
         * 第二个参数:分区
         * 第三个参数:消息的key
         * 第三个参数:消息的value
         */
        ProducerRecord<String, String> kvProducerRecord = new ProducerRecord<>("topic-first", 0, "key-001", "hello kafka");
        try {
            RecordMetadata recordMetadata = producer.send(kvProducerRecord).get();
            System.out.println(recordMetadata.offset());   //偏移量,在分区小自增的数值,用于编号消息
        } catch (Exception e) {
            e.printStackTrace();
        }

        producer.send(kvProducerRecord, new Callback() {
            @Override
            public void onCompletion(RecordMetadata metadata, Exception exception) {
                if (exception != null){
                    System.out.println("记录错误信息到日志");
                }
                System.out.println(metadata.offset());
            }
        });

//        4.关闭消息通道,必须要关闭,否则消息发送不成功
        producer.close();

    }
}

消费者组决定了消息传递的模型

queue模型:消息队列,组内只能有一个消费者可以接收到消息(消费者在同一个组)

发布订阅模型:所有消费者都可以拿到消息(消费者在不同组)

消息的有序性

topic分区中消息只能由消费者组中的唯一一个消费者处理,所以消息肯定是按照先后顺序进行处理的。但是它也仅仅是保证Topic的一个分区顺序处理,不能保证跨分区的消息先后处理顺序。

结论

如果你想要顺序的处理Topic的所有消息,那就只提供一个分区。

kafka消费者详解

提交和偏移量

kafka不会像其他JMS队列那样需要得到消费者的确认,消费者可以使用kafka来追踪消息在分区的位置(偏移量) 消费者会往一个叫做_consumer_offset的特殊主题发送消息,消息里包含了每个分区的偏移量。如果消费者发生崩溃或有新的消费者加入群组,就会触发再均衡

再均衡

当消费者组中的消费者实例发生变化(如新增、退出消费者),Kafka 会触发再均衡过程,重新分配分区给消费者。在再均衡期间,消费者可能会丢失未提交偏移量的消息。

如图所示,如果上一次提交的偏移量为 11,但在再均衡过程中,消费者还在处理偏移量为 5 的消息,那么偏移量 611 之间的消息将会丢失。

因为实际提交时,都是批量提交,不可能处理一次提交一个

因为频繁提交偏移量 = 性能瓶颈

  • 提交偏移量(commit)是一个网络请求,需要和 Kafka 的 Group Coordinator 节点通信。
  • 如果每处理一条消息就提交一次,会产生大量网络开销,吞吐量急剧下降
  • 例如:每秒处理 1 万条消息,就要提交 1 万次 offset → 系统几乎瘫痪。

 所以实际中是批量处理 + 批量提交

批量提交又会出现下面问题:

多线程下,偏移量提交顺序可能错乱

  • 比如:offset=10 的消息处理快,先提交;offset=5 的还没处理完 → 导致 offset=5~9 的消息“被跳过”。

采用的解决方式

偏移量提交方式

提交偏移量的方式有两种,分别是自动提交偏移量和手动提交

提交当前偏移量(同步提交)

优点:如果提交失败会重试,直到成功或者最后抛出异常

缺点:可能会造成方法的阻塞

        while (true){
            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key());
                System.out.println(consumerRecord.value());
                System.out.println(consumerRecord.partition());
//                同步提交
                try {
                    consumer.commitSync();
                } catch (CommitFailedException e) {
                    System.out.println("记录错误信息:"+e);
                }
            }
        }

异步提交

优点:效率高,不需要实时等待

缺点:如果服务器返回提交失败,不会进行重试,如果存在多个异步提交,可能会导致偏移量覆盖

//                异步提交
//                如果提交失败,会执行后面的的逻辑,offsets里面装的就是偏移量
                consumer.commitAsync(new OffsetCommitCallback() {
                    @Override
                    public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
                        if (exception != null){
                            System.out.println("记录错误的提交偏移量:"+ offsets+ ",异常信息" + exception);
                        }
                    }
                });

            }

同步和异步组合提交

优点:异步提交失败后,会先去记录日志,最后进行同步提交

        try {
            while (true) {
                ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                    System.out.println(consumerRecord.key());
                    System.out.println(consumerRecord.value());
                    System.out.println(consumerRecord.partition());
                }
//                异步提交

                //同步异步组合提交
                consumer.commitAsync();


            }
        } catch (Exception e){
            System.out.println("记录错误信息:" + e);
        }finally {
            try {
                consumer.commitSync();
            } finally {
                consumer.close();
            }
        }

在写 Kafka 消费者时,我们有三个核心目标:

目标说明
✅ 不丢消息即使程序崩溃,重启后也不能跳过未处理的消息
✅ 不重复消费太多已处理的消息不要反复处理(影响性能)
✅ 性能要好不能因为“提交偏移量”拖慢整个消费速度

“平时异步,最后同步”

这就是你代码的精髓:

📌 平时:commitAsync() → 要快

  • 正常运行时,用异步提交,保证消费速度。
  • 就像“批量发快递,不等签收回执”。

📌 最后:finally 里 commitSync() → 要稳

  • 程序关闭前,必须确保最后一次提交成功
  • 就像“关门之前,一定要确认所有重要文件都寄出去了”。

“偏移量覆盖”不是异步提交(commitAsync)的 bug,而是 Kafka 的正常行为 —— 后提交的 offset 会覆盖先提交的,无论它是同步还是异步。

技术上:Kafka 怎么存 offset?

Kafka 把 offset 存在一个叫 __consumer_offsets 的内部 topic 中,它是一个 key-value 存储

Key(消费者组 + topic + partition)Value(offset 值)
group1-topicA-0100

当你调用:

consumer.commitAsync(); // 提交 offset=100

Kafka 会:

  1. 找到 key:group1-topicA-0
  2. 把 value 更新为 100

下一次你提交 offset=120:

consumer.commitAsync(); // 提交 offset=120

Kafka 会:

  1. 找到同一个 key
  2. 把 value 更新为 120(覆盖 100

✅ 所以“覆盖”是正常更新,不是重复或错误。


为什么“异步”更容易让人觉得“覆盖有问题”?

因为 commitAsync()异步的、不保证顺序的,可能会出现“乱序提交”问题。

❌ 举例:异步提交的“乱序”风险

// 第一批消息:offset=0~99
consumer.commitAsync(); // 提交 offset=100(请求已发出,但还没到 Kafka)

// 第二批消息:offset=100~119
consumer.commitAsync(); // 提交 offset=120(这个请求可能先到 Kafka)

可能出现:

  1. offset=120 的提交先到达 Kafka → 记录为 120
  2. offset=100 的提交后到达 → 覆盖成 100 ❌

👉 结果:Kafka 记录的 offset 反而变小了
如果此时消费者重启,会从 offset=100 开始消费 → offset=100~119 的消息被重复消费

⚠️ 这才是“异步提交导致覆盖”的真正风险!


如何避免“乱序覆盖”?

方法 1:不要频繁异步提交

  • 批量处理一批消息后提交一次,减少提交次数
  • 避免在多线程中随意提交

方法 2:使用回调控制顺序(不推荐)

consumer.commitAsync((offsets, exception) -> {
    // 只有当前提交成功后,才允许下一次提交
});

但这样会降低性能,失去异步意义。

方法 3:靠 finally 中的 commitSync() 来兜底

  • 即使中间 commitAsync() 乱序了,只要最后 commitSync() 成功提交最新 offset,就不会丢数据

总结:为什么“异步会产生偏移量覆盖”?

问题回答
异步提交会导致 offset 被覆盖吗?✅ 会,但这是正常行为 —— 新值覆盖旧值
这种覆盖有问题吗?一般没问题,但如果后提交的先到、先提交的后到(乱序),可能导致 offset 回退 → 重复消费
如何避免风险?减少异步提交次数 + 最后用 commitSync() 确保最终进度
“覆盖”是 bug 吗?❌ 不是!是 Kafka 的正常更新机制

最后一句话:

“偏移量覆盖”不是异步的问题,而是“状态更新”的本质。
异步提交的风险不是“覆盖”,而是“可能乱序提交,导致状态回退”。
所以我们用 commitSync() 在最后“一锤定音”,确保最终进度正确。

完整版代码:

生产者


package com.heima.kafka.simple;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

/**
 * @author NoFear
 * @version 1.0
 * @description: TODO
 * @date 2025/8/21 2:26
 */
public class ProducerQuickStart {
    public static void main(String[] args) {
//        1.kafka连接的配置信息
        Properties prop = new Properties();
//        kafka连接地址
//        ProducerConfig.BOOTSTRAP_SERVERS_CONFIG相当于"bootstrap.servers",所以这里也可以用这个字符串
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.200.130:9092");
//        key和value指定序列化器,使用的是kafka的序列化器,在kafka的common包下,全类名为
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");

//        确认机制
        prop.put(ProducerConfig.ACKS_CONFIG, "1");
//        重试次数
        prop.put(ProducerConfig.RETRIES_CONFIG ,10);
//        数据压缩
        prop.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");

//        2.创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(prop);
//        发送消息
        /**
         * 第一个参数:消息的topic(主题)
         * 第二个参数:分区
         * 第三个参数:消息的key
         * 第三个参数:消息的value
         */
        ProducerRecord<String, String> kvProducerRecord = new ProducerRecord<>("topic-first", 0, "key-001", "hello kafka");
        try {
            RecordMetadata recordMetadata = producer.send(kvProducerRecord).get();
            System.out.println(recordMetadata.offset());   //偏移量,在分区小自增的数值,用于编号消息
        } catch (Exception e) {
            e.printStackTrace();
        }
//异步发送
        producer.send(kvProducerRecord, new Callback() {
            @Override
            public void onCompletion(RecordMetadata metadata, Exception exception) {
                if (exception != null){
                    System.out.println("记录错误信息到日志");
                }
                System.out.println(metadata.offset());
            }
        });

//        4.关闭消息通道,必须要关闭,否则消息发送不成功
        producer.close();

    }
}

消费者

package com.heima.kafka.simple;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.TopicPartition;

import java.time.Duration;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;

/**
 * @author NoFear
 * @version 1.0
 * @description: TODO
 * @date 2025/8/21 2:25
 */
public class ConsumerQuickStart {
    public static void main(String[] args) {
//        1.kafka的配置信息
        Properties prop = new Properties();
//        连接地址
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.200.130:9092");
//        消费者的key和value的反序列化器
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
//        设置消费者组
        prop.put(ConsumerConfig.GROUP_ID_CONFIG, "group_1");
//          2.创建消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(prop);
//          3.订阅主题topic
        consumer.subscribe(Collections.singletonList("topic-first"));

//        4.拉取消息,为了让他一直执行,这里用死循环实现
        try {
            while (true) {
                ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                    System.out.println(consumerRecord.key());
                    System.out.println(consumerRecord.value());
                    System.out.println(consumerRecord.partition());
                }
//                异步提交
//                如果提交失败,会执行后面的的逻辑,offsets里面装的就是偏移量

                //同步异步组合提交
                consumer.commitAsync();


            }
        } catch (Exception e){
            System.out.println("记录错误信息:" + e);
        }finally {
            try {
                consumer.commitSync();
            } finally {
                consumer.close();
            }
        }



/*                consumer.commitAsync(new OffsetCommitCallback() {
                    @Override
                    public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
                        if (exception != null){
                            System.out.println("记录错误的提交偏移量:"+ offsets+ ",异常信息" + exception);
                        }
                    }
                });

            }
        }*/
//        while (true){
//            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
//            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
//                System.out.println(consumerRecord.key());
//                System.out.println(consumerRecord.value());
//                System.out.println(consumerRecord.partition());
////                同步提交
//                try {
//                    consumer.commitSync();
//                } catch (CommitFailedException e) {
//                    System.out.println("记录错误信息:"+e);
//                }
//            }
//        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值