消息中间件 Kafka

Kafka

1.windows下安装运行

安装

http://kafka.apache.org/downloads 进入官网下载,下载2.0版本的,3.0版本对windows支持不友好

下载完之后,解压,并创建 两个文件夹 dataKafka-logs
在这里插入图片描述
并进入 config 文件夹,修改配置文件,更改信息输出地址

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

运行

在根目录下进入命令行
在这里插入图片描述

  • 先启动zookeeper,

.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties

在这里插入图片描述
在这里插入图片描述
启动成功标志 binging to port

  • 启动Kafka

.\bin\windows\kafka-server-start.bat .\config\server.properties
在这里插入图片描述

测试

创建topic测试主题kafka

# 创建  create        topic 名 chenchong   的主题
.\bin\windows\kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic chenchong

在这里插入图片描述
创建消费者,并连接主题

.\bin\windows\kafka-console-producer.bat --broker-list localhost:9092 --topic chenchong
在这里插入图片描述
创建消费者,并订阅主题

.\bin\windows\kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic chenchong --from-beginning

生产者发布消息到主题 chenchong 订阅了主题的 消费者 就会收到消息。
在这里插入图片描述
安装Kafka成功

2.常用命令

在这里插入图片描述

=主题操作=

主题操作命令都以 .\bin\windows\kafka-topics.bat开头
因为需要zookeeper支持,所以需要连接zookeeper,以–zookeeper localhost:2181结尾

.\bin\windows\kafka-topics.bat 查看都有那些命令

在这里插入图片描述

1.查看都有那些主题 (主题操作)

主题操作命令都以 .\bin\windows\kafka-topics.bat开头

# 命令都以 .\bin\windows\kafka-topics.bat开头
# 中间是具体的命令
  --list   查看都有那些主题
# 后边是连接的zookeeper 地址

.\bin\windows\kafka-topics.bat --list --zookeeper localhost:2181

在这里插入图片描述

2.创建主题 (主题操作)

主题操作命令都以 .\bin\windows\kafka-topics.bat开头

# 命令都以 .\bin\windows\kafka-topics.bat开头
# 中间是具体的命令
  -- create                        表明是创建主题
  -- replication-factor 1          设置分区副本
  -- partitions 1                  设置分区数
# 后边是连接的zookeeper 地址

.\bin\windows\kafka-topics.bat --create  --replication-factor 1 --partitions 1 --topic test123 --zookeeper localhost:2181

在这里插入图片描述

3.查看主题详细描述 (主题操作)

主题操作命令都以 .\bin\windows\kafka-topics.bat开头

# 命令都以 .\bin\windows\kafka-topics.bat开头
# 中间是具体的命令
  -- topic       指明要操作那个主题
  -- describe    表明该操作是查看详细描述信息
# 后边是连接的zookeeper 地址

.\bin\windows\kafka-topics.bat --topic chenchong --describe  --zookeeper localhost:2181

在这里插入图片描述
详细描述的参数解释:
Topic : 主题名
PartitionCount:分区数量
ReplicationFactor:副本数量
Configs:底层存储配置
下面是分区一的信息
Topic:所属主题
Partition:当前所属分区序号
Leader: 证明副本 0 是leader副本
Replicas:
Isr:

4.修改主题信息 (主题操作)

主题操作命令都以 .\bin\windows\kafka-topics.bat开头

# 命令都以 .\bin\windows\kafka-topics.bat开头
# 中间是具体的命令
  -- topic       指明要操作那个主题
  -- alter       表明该操作是修改主题信息	
  -- partitions 2 表面是修改分区数量为2  (分区数量只能增加,不能减少)
# 后边是连接的zookeeper 地址

.\bin\windows\kafka-topics.bat --topic chenchong --alter --partitions 2  --zookeeper localhost:2181

在这里插入图片描述

4.删除主题 (主题操作)

主题操作命令都以 .\bin\windows\kafka-topics.bat开头

# 命令都以 .\bin\windows\kafka-topics.bat开头
# 中间是具体的命令
  -- topic       指明要操作那个主题
  -- delete      表明该操作是删除主题	
 # 后边是连接的zookeeper 地址
 
.\bin\windows\kafka-topics.bat --topic test23 --delete  --zookeeper localhost:2181

在这里插入图片描述

=生产者操作=

主题操作命令都以 .\bin\windows\kafka-console-producer.bat开头
因为需要连接到kafka,所以需要 --broker-list localhost:9092结尾

kafka端口 9092 可以在配置文件 中修改
在这里插入图片描述

.\bin\windows\kafka-console-producer.bat查看都有那些命令

在这里插入图片描述

1.创建生产者并与主题产生连接 (生产者操作)

主题操作命令都以.\bin\windows\kafka-console-producer.bat 开头

#命令都以 .\bin\windows\kafka-console-producer.bat开头
# 中间是具体的命令
  -- topic       指明要操作那个主题

 # 后边是连接的kafka 地址

.\bin\windows\kafka-console-producer.bat  --topic chenchong --broker-list localhost:9092

在这里插入图片描述
向队列中发布消息
在这里插入图片描述

=消费者操作=

主题操作命令都以 .\bin\windows\kafka-console-consumer.bat开头
因为需要连接到kafka,所以需要–bootstrap-server localhost:9092 结尾

.\bin\windows\kafka-console-consumer.bat查看都有那些命令

在这里插入图片描述

1.创建消费者,并订阅主题

#命令都以 .\bin\windows\kafka-console-consumer.bat开头
# 中间是具体的命令
  -- topic       指明要操作那个主题
  -- from-beginning 加上之后,会把主题中历史消息也都读取出来
 # 后边是连接的kafka 地址
 
.\bin\windows\kafka-console-consumer.bat  --topic chenchong --from-beginning --bootstrap-server localhost:9092

在这里插入图片描述

3.使用

3.1 生产者

=异步发送消息=

定义:一批数据发送之后,不用等处理完就可以处理下一批数据

1.生产者异步发送消息

public class Producer {
    public static void main(String[] args) {
        //0.配置
        Properties properties = new Properties();
        //连接kafka
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //指定对应的key value的序列化类型
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        //1.创建Kafka生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);

        //2.发送数据
        for (int i = 0; i < 5; i++) {
            //调用send方法发送数据
            kafkaProducer.send(new ProducerRecord<>("chenchong", "hello,world  " + i));
        }
        //3.关闭资源
        kafkaProducer.close();
    }
}

消费者可以正常接受消息

在这里插入图片描述

2.生产者异步发送消息(带回调信息)


public class Producer {
    public static void main(String[] args) {
        //0.配置
        Properties properties = new Properties();
        //连接kafka
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //指定对应的key value的序列化类型
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        //1.创建Kafka生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);

        //2.发送数据
        for (int i = 0; i < 5; i++) {
            //调用send方法发送数据,带回调信息
            kafkaProducer.send(new ProducerRecord<>("chenchong", "hello,world  " + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (e==null){
                        //没有报错信息,就是正常处理返回
                        System.out.println("主题:"+recordMetadata.topic()+"分区:"+recordMetadata.partition());
                    }
                }
            });
        }
        //3.关闭资源
        kafkaProducer.close();
    }
}

打印结果
在这里插入图片描述

=同步发送消息=

定义:一批数据发送之后,必须等处理完才可以处理下一批数据

1.生产者同步发送消息

// 只需在正常的异步方法后面加上 .get()方法,并抛出异常即可 同步发送  
      kafkaProducer.send(new ProducerRecord<>("chenchong", "hello,world  " + i)).get();

在这里插入图片描述
表面上接受效果是一样的,底层会有 同步 异步的区别

=生产者分区=

生产者如果发送消息不指定分区的话,消费者有可能接受不到,因为消费者也没有指定分区

1.消息分区的好处

在这里插入图片描述

2.消息分区规则

在这里插入图片描述

①.指定分区

在这里插入图片描述
返回情况:
在这里插入图片描述

②不指定分区,但指定key

会根据Key 的hashCode值来计算。(常用这种,把数据库表名作为key,这样就保证每个表的数据都发到同一个分区)
在这里插入图片描述

在这里插入图片描述

③ 什么都不指定,粘性分区

为了演示,使用线程睡眠,并多发几条数据
在这里插入图片描述
打印结果:
在这里插入图片描述

3.自定义分区策略器

自定义分区器之后,kafka原生的就失效了,会按照自定义的进行;

  • 1.实现接口,重写方法,创建分区器类
/**
 * 自定义 kafka 分区器
 *  * @author
 */
public class MyPartitioner implements Partitioner {
    /**
     * 重写分区策略 (根据自己需要做逻辑判断)
     *
     * @param topic      主题
     * @param key        key
     * @param keyBytes   序列化之后的 key
     * @param value      value
     * @param valueBytes 序列化之后的 value
     * @param cluster
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        //获取value
        String strValue = value.toString();

        int partition;
        if (strValue.contains("chenchong")) {
            partition = 0;
        } else {
            partition = 1;
        }
        //返回分区
        return partition;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}
  • 2.发送消息时,指定分区策略为使用自定义分区器

在这里插入图片描述

  • 3.测试
    在这里插入图片描述
4.提高生产者吞吐量

提高生产者吞吐量,也就是处理生产者产生的消息的速率,有以下几个方面入手:

  • 设置批次大小
  • 设置等待时间
  • 设置缓冲区大小
  • 设置压缩

以上配置可能导致消息不及时,根据需要进行配置

/**
 * 提高生产者吞吐量
 */
public class ProducerInOut {
    public static void main(String[] args) throws InterruptedException {
        //0.配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 指定对应的key value的序列化类型
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        //(以下开始设置提高吞吐量的配置)
        //0.3 提高吞吐量-设置批次大小,默认16k,生产环境用 32k
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        //0.4 提高吞吐量-设置等待时间,默认0,这里改大一点,设为1
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        //0.5 提高吞吐量-设置缓冲区大小,默认32M
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        //0.6 提高吞吐量-设置压缩,默认none 可配置值 gzip,snappy,lz4,zstd
        properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");


        //1.创建Kafka生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
        //2.发送数据
        for (int i = 0; i < 2; i++) {
            //调用send方法发送数据
            kafkaProducer.send(new ProducerRecord<>("chenchong", "hello,world  " + i));
        }
        //3.关闭资源
        kafkaProducer.close();
    }
}
4.数据可靠

kafka接收到生产者的信息之后,会有一个应答,应答有三种级别:
在这里插入图片描述
保证的时数据可靠性,牺牲的是效率

各种级别的风险:
在这里插入图片描述
思考:
在这里插入图片描述
可靠性总结:
在这里插入图片描述
代码演示

/**
 * 提高生产者数据可靠
 */
public class ProducerAcks {
    public static void main(String[] args) throws InterruptedException {
        //0.配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 指定对应的key value的序列化类型
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        //(以下开始设置 数据可靠 的配置)
        //0.3 设置应答级别 
        properties.put(ProducerConfig.ACKS_CONFIG, "1");
        //0.4应答失败重试次数 默认是int 的范围最大值
        properties.put(ProducerConfig.RETRIES_CONFIG,3);
        
        //1.创建Kafka生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
        //2.发送数据
        for (int i = 0; i < 2; i++) {
            //调用send方法发送数据
            kafkaProducer.send(new ProducerRecord<>("chenchong", "hello,world  " + i));
        }
        //3.关闭资源
        kafkaProducer.close();
    }
}
5.数据重复

在这里插入图片描述

①幂等性

在这里插入图片描述
使用幂等性
在这里插入图片描述

②事务

在这里插入图片描述
总结:

幂等性,能够保证kafka启动期间数据不重复,但是重启后的数据与之前的有重复的可能性;加上使用事务,能够获取到唯一值的PID,这样重启之后,因为PID是唯一的,因此也不会出现重复数据的情况。
因为幂等性是默认开启的,因此解决数据重复问题,只需要开启事务即可。

代码如下:

/**
 * 生产者 消息事务(幂等性+事务)
 * 解决数据重复的问题
 */
public class ProducerTranactions {
    public static void main(String[] args) throws InterruptedException {
        //0.配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 指定对应的key value的序列化类型
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        //0.3 事务需要指定 事务id (全局唯一)
        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"id"+ UUID.randomUUID().toString());

        //1.创建Kafka生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
        //(事务是针对生产者来说的,因此需要生产者来进行初始化启动)
        //1.1初始化事务
        kafkaProducer.initTransactions();
        //1.2启动事务
        kafkaProducer.beginTransaction();
        try {
            //2.发送数据
            for (int i = 0; i < 2; i++) {
                //调用send方法发送数据
                kafkaProducer.send(new ProducerRecord<>("chenchong", "hello,tranactions  " + i));
            }
            //1.3 提交事务
            kafkaProducer.commitTransaction();
        } catch (Exception e) {
            //发生异常,终止事务,并回滚
            kafkaProducer.abortTransaction();
        } finally {
            //3.关闭资源
            kafkaProducer.close();
        }

    }
}

在这里插入图片描述
如果逻辑中报错,则不会收到消息

6.数据有序

在这里插入图片描述

3.2 Broker

broker 是kafka 存储数据相关的部分

1.Zookeeper存储

在zookeeper的服务器存储的Kafka相关信息
在这里插入图片描述
下面是详细解释:

①/kafka/brokers/ids 记录有哪些服务器

在这里插入图片描述

②/kafka/brokers/topics/主题名/partitions/0/state 记录谁是leader,有哪些服务器可用

在这里插入图片描述

③/kafka/controller 辅助选取leader

在这里插入图片描述

3.3 消费者

1.kafka消费者消费方式

在这里插入图片描述

2.工作流程

消费者分为 单独消费者消费者组
单独消费者,可以选择消费单独的一个分区的数据,也可以选择消费多个分区的数据。
消费者组,是由多个消费者组成的组,组内每个消费者选择的消费分区不允许重复。

①消费者组工作流程

每个单独的消费者其实也是一个组,但是由于没有其他的消费者与其争资源,因此它可以消费所有分区。
在这里插入图片描述
如果组内消费者超过分区数,那么会有一部分消费者闲置;
在这里插入图片描述
组内消费者消费分区不允许重复,但是组与组之间不受影响
在这里插入图片描述

消费者组 初始化

1.消费者组会利用自己的组id,来根据策略计算出选用那个分区的协调器来作为基准;选出基准后,每个消费者都向该协调器发请求,竞争出来消费者leader,由该消费者leader来进行每个消费者的分区选择
在这里插入图片描述

消费者组 消费

消费过程原理:
在这里插入图片描述
接下来,进行代码实操

3.消费者API

①订阅主题,监听消息
/**
 * 消费者
 */
public class Consumer {
    public static void main(String[] args) {

        //0 配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 因为生产者进行了序列化,所以消费者需要定义反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        //0.3 配置消费者组id
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");


        //1.创建消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);

        //2.订阅想要消费的主题
        List<String> topics = new ArrayList<>();
        topics.add("chenchong");
        kafkaConsumer.subscribe(topics);

        //3.消费数据 (业务中会选取一个逻辑进行终止拉取的判断,这里简单的设为一直拉取)
        while (true) {
            //poll 参数为 每批次拉取之间的间隔时间,这里设为 每一秒拉取一次
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            //打印
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

利用生产者向主题中发送消息:
在这里插入图片描述

控制台打印:
在这里插入图片描述

ConsumerRecord(
topic = chenchong, partition = 0, leaderEpoch = 0, offset = 36, 
CreateTime = 1659360500809, serialized key size = -1, serialized value size = 6, 
headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = nihaoa
)

②订阅主题,并指定分区拉取
/**
 * 消费者,指定分区消费
 */
public class ConsumerPattition {
    public static void main(String[] args) {

        //0 配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 因为生产者进行了序列化,所以消费者需要定义反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        //0.3 配置消费者组id
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");


        //1.创建消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);

        //2.订阅想要消费的主题,以及主题的指定分区
        List<TopicPartition> topicPartitions = new ArrayList<>();
        topicPartitions.add(new TopicPartition("chenchong",1));
        kafkaConsumer.assign(topicPartitions);

        //3.消费数据 (业务中会选取一个逻辑进行终止拉取的判断,这里简单的设为一直拉取)
        while (true) {
            //poll 参数为 每批次拉取之间的间隔时间,这里设为 每一秒拉取一次
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            //打印
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

利用生产者向指定分区发送数据:
在这里插入图片描述
控制台打印:
在这里插入图片描述

不向订阅指定的分区发送的话,消费者就拉取不到

4.分区分配策略

① Range + CooperativeSticky 分配 (默认)

在这里插入图片描述

② RoundRobin 分配

在这里插入图片描述
代码演示:

在这里插入图片描述
同一组内每个消费者的分区策略应当保持一致,这样不管那个消费者为主导,都会是相同的分区策略;

③ Sticky 粘性分配

在这里插入图片描述
同Range分配一样,先取模,多余的往前几个分配,不同的是,Range是分区有序的,Sticky是分区无序的。

代码只需要改为 粘性策略 即可:
在这里插入图片描述

5.offset

默认存放位置

kafka有一个默认系统主题 __consumer_offsets 用来存放offset
在这里插入图片描述
在这里插入图片描述

①offset 自动提交

在这里插入图片描述
代码:

        //0.4 设置自动提交,及自动提交间隙  (默认就是 true 这里不写也可以)
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        //设置自动提交频率为一秒一次
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);

在这里插入图片描述

②offset 手动提交

因为自动提交,导致无法准确的把控提交时机,因此,kafka还提供手动提交API

在这里插入图片描述
代码如下:

/**
 * 消费者 offset 手动提交
 */
public class ConsumerOffsetByHand {
    public static void main(String[] args) {

        //0 配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 因为生产者进行了序列化,所以消费者需要定义反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        //0.3 配置消费者组id
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
        //0.4 关闭自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);

        //1.创建消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);

        //2.订阅想要消费的主题
        List<String> topics = new ArrayList<>();
        topics.add("chenchong");
        kafkaConsumer.subscribe(topics);

        //3.消费数据 (业务中会选取一个逻辑进行终止拉取的判断,这里简单的设为一直拉取)
        while (true) {
            //poll 参数为 每批次拉取之间的间隔时间,这里设为 每一秒拉取一次
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            //打印
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
            //拉取完后,手动提交offset
            kafkaConsumer.commitSync();//同步提交
            kafkaConsumer.commitAsync();//异步提交
        }
    }
}

在这里插入图片描述

6.消费方式

①指定offset消费

在这里插入图片描述
上面三种策略,都没有指定位置,下面来进行指定位置

代码如下:

/**
 * 消费者 指定offset消费
 */
public class ConsumerSeek {
    public static void main(String[] args) {

        //0 配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 因为生产者进行了序列化,所以消费者需要定义反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        //0.3 配置消费者组id
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");


        //1.创建消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);

        //2.订阅想要消费的主题
        List<String> topics = new ArrayList<>();
        topics.add("chenchong");
        kafkaConsumer.subscribe(topics);
        //3 指定位置进行消费
        //3.1先获取全部分区,
        Set<TopicPartition> assignment = kafkaConsumer.assignment();
        //3.2因为有可能分区还没有分配完成,因此需要先保证分区分配完成
        while (assignment.size() == 0) {
            //主动拉取消息,加快分区完成
            kafkaConsumer.poll(Duration.ofSeconds(1));
            //再次获取分区,更新值
            assignment = kafkaConsumer.assignment();
        }
        //3.2 获取到分区信息,才可以进行遍历分区
        for (TopicPartition topicPartition : assignment) {
            //遍历分区,指定offset位置为100 (遍历的意义是保证无论消费那个分区,都是从这个offset位置开始拉取)
            kafkaConsumer.seek(topicPartition, 100);
        }

        //3.消费数据 (业务中会选取一个逻辑进行终止拉取的判断,这里简单的设为一直拉取)
        while (true) {
            //poll 参数为 每批次拉取之间的间隔时间,这里设为 每一秒拉取一次
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            //打印
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

②指定时间消费
/**
 * 消费者 指定时间进行消费
 */
public class ConsumerSeekTime {
    public static void main(String[] args) {

        //0 配置
        Properties properties = new Properties();
        //0.1 连接kafka
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        //0.2 因为生产者进行了序列化,所以消费者需要定义反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        //0.3 配置消费者组id
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");


        //1.创建消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);

        //2.订阅想要消费的主题
        List<String> topics = new ArrayList<>();
        topics.add("chenchong");
        kafkaConsumer.subscribe(topics);

        //3 指定时间进行消费
        //3.1先获取全部分区,
        Set<TopicPartition> assignment = kafkaConsumer.assignment();
        //3.2因为有可能分区还没有分配完成,因此需要先保证分区方案分配完成
        while (assignment.size() == 0) {
            //主动拉取消息,加快分区完成
            kafkaConsumer.poll(Duration.ofSeconds(1));
            //再次获取分区,更新值
            assignment = kafkaConsumer.assignment();
        }
        //3.3 因为没有直接指定时间的api,因此需要把时间换算成对应的offset
        Map<TopicPartition, Long> topicPartitionLongMap = new HashMap<>();
        for (TopicPartition topicPartition : assignment) {
            //放入 分区-指定时间的时间戳
            topicPartitionLongMap.put(topicPartition, System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
        }
        Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongMap);

        //3.2 根据 分区-时间戳转化的offset Map,进行消费
        for (TopicPartition topicPartition : assignment) {
            OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(topicPartition);
            kafkaConsumer.seek(topicPartition, offsetAndTimestamp.offset());
        }

        //4.消费数据 (业务中会选取一个逻辑进行终止拉取的判断,这里简单的设为一直拉取)
        while (true) {
            //poll 参数为 每批次拉取之间的间隔时间,这里设为 每一秒拉取一次
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            //打印
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}
③漏消费与重复消费分析

在这里插入图片描述
如果想要规避掉上面两种情况,我们引入事务

④ 事务

在这里插入图片描述

⑤ 数据积压

在这里插入图片描述

完结

参考视频:尚硅谷kafka

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值