kafka学习笔记二

目录

第一二章 kafka概述&快速入门

1. kafka定义

2. 常见MQ有哪些?

3. 传统消息队列的应用场景

4. kafka基础架构

5. kafka的下载、安装、启动/停止

6. kafka命令行操作

6.1 主题命令行操作

6.2 生产者命令行操作

6.3 消费者命令行操作

第三章 生产者

3.1 生产者消息发送流程

3.1.1 发送原理

3.1.2 生产者重要参数

3.2 异步发送API

3.2.1 普通异步发送

3.2.2 带回调函数的异步发送

3.3 同步发送API

3.4 生产者分区

分区的好处:

默认的分区策略:

自定义分区器:

3.5 生产经验——生产者如何提高吞吐量

3.6 生产经验——数据可靠性(不丢失)

3.7 生产经验——数据去重(幂等性&事务)

生产者事务

3.8 生产经验——数据有序

3.9 生产经验——数据乱序

第四章 kafka Broker

zookeeper存储kafka的哪些信息?

Broker kafka总体工作流程:

节点的服役与退役

kafka副本:

Leader选举流程:

Follower故障处理细节:

Leader故障处理细节:

文件存储机制

文件清理策略

高效读写数据(高频面试)

第五章 kafka消费者

5.1 Kafka 消费方式

5.2 Kafka 消费者 工作流程

5.2.1 消费者总体工作流程

5.2.2 消费者组原理

5.2.3 消费者重要参数

5.3 消费者API

5.3.1 独立消费者案例(订阅主题)

5.3.2 独立消费者案例(订阅分区)

5.3.3 消费者组案例

5.4 生产经验——分区的分配以及再平衡

5.4.1 Range以及再平衡

5.4.2 RoundRobin以及再平衡

5.4.3 Sticky以及再平衡

5.5 offset位移

5.6 生产经验——消费者事务

5.7 生产经验——数据积压( 消费者如何提高吞吐量)

第六章 Kafka-Eagle监控

第七章 Kafka-Kraft模式

--------------------------------------------------------------------------------------------------------

课程地址: 【尚硅谷】Kafka3.x教程(从入门到调优,深入全面)_哔哩哔哩_bilibili

讲解版本:kafka3.x ;时长13小时

官网:Apache Kafka

Kafka2.8.0以后也可以配置不采用zookeeper,之前是必须采用zk的。(启动kafka前须启动zk)

说明:资料已下载,非常全!很赞!

第一二章 kafka概述&快速入门

1. kafka定义

传统上用作分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域; 同时,Kafka是一个开源的分布式事件流平台(Event Streaming Platform),被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用。

2. 常见MQ有哪些?

目前企业中比较常见的消息队列产品主要有 Kafka、ActiveMQ、RabbitMQ、RocketMQ等。在大数据场景主要采用 Kafka 作为消息队列。在 JavaEE 开发中主要采用 ActiveMQ、RabbitMQ、RocketMQ。

3. 传统消息队列的应用场景

缓存/ 消峰、 解耦和 异步通信。

4. kafka基础架构

5. kafka的下载、安装、启动/停止

到官网下载:

第一步:将下载的压缩包拖到虚拟机centos102的/opt/software/目录下;

第二步:解压缩到指定目录/opt/module/下;

[root@centos102 software]# mkdir -p /opt/module
[root@centos102 software]# tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module

由于kafka压缩包是免安装包,故解压命令中参数为-zxvf 

给解压的kafka改个名字:

第三步:修改配置文件;

进入/opt/module/kafka/config目录修改server.properties文件,具体如下:

#broker 的全局唯一编号,不能重复,只能是数字。
broker.id=0

#kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/module/kafka/datas

#配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181/kafka

第四步:分发kafka并修改配置broker.id

正确的分发命令:rsync -av kafka root@192.168.6.103:/opt/module

而不是:rsync -av kafka/ root@192.168.6.103:/opt/module (注意有没有/的区别)

第五步:java -version检查JDK是否安装成功并配置了环境变量;配置kafka的环境变量;启动;

/opt/module/kafka/bin/kafka-server-start.sh -daemon /opt/module/kafka/config/server.properties
[root@centos104 kafka]# bin/kafka-server-start.sh -daemon config/server.properties

老师说,启动kafka前须启动zk,但是我没有启动zk也能启动成功kafka。

我亲自下载搭建了zk集群,发现:

[root@centos102 zookeeper]# bin/zkServer.sh start   这是启动zk命令,必须是在zookeeper这个目录下才能启动,否则就报错;还有,zookeeper的配置文件zookeeper/conf/zoo.cfg中这个必须写绝对路径dataDir=/opt/module/zookeeper/data,(data这个目录是新建的,需要在这个目录下创建名称为 myid 的文件)否则zookeeper/data目录下的my.id文件读取不到,zk不能启动,从zookeeper/logs/下的日志中看到报错信息为myid文件读取不到:

第六步:停止服务时,须先关闭kafka再关闭zookeeper。老师写了批量启动停止的shell脚本。

保存退出后,执行 chmod 777 kf.sh 命令进行赋权。

kafka关闭命令:

[root@centos104 kafka]# bin/kafka-server-stop.sh

6. kafka命令行操作

注意:任何操作首先须启动服务端,即通过jps检查启动的服务,如果没有启动kafka时,须对集群的每台服务器执行命令启动kafka:

开启服务(在kafka根目录下执行):

bin/kafka-server-start.sh -daemon config/server.properties

6.1 主题命令行操作

相关命令都保存在kafka/bin/kafka-topics.sh这个文件中。

对Topic进行操作时,首先得使用--bootstrap-server参数连接上kafka broker主机名称和端口号。

对Topic进行操作,一般指增删改查。

zk集群启动的前提下,执行kafka主题命令行启动命令时,报错:could not be established. Broker may not be available. 一直在尝试连接。百度原因为:

kafka/config/server.properties文件中,需要进行如下配置:

操作了几次都无法成功,过了好几天,从头到尾,从克隆虚拟机开始重新操作了一遍,可以了,没有那个错误了!但上图中这个配置并没有改哦!我猜想可能是zookeeper没有真正启动,因为我只是执行了启动命令后jps查看启动的进程,但并没有执行命令bin/zkServer.sh status检查其是否真正启动!

当然,还有一种情况,就是你没有启动kafka的服务而直接执行如下命令,也会是一直连不上的情况:

kafka服务启动命令:

bin/kafka-server-start.sh -daemon config/server.properties

而直接执行:

bin/kafka-topics.sh --bootstrap-server centos102:9092 --list

练习

查看集群中的所有主题:

bin/kafka-topics.sh --bootstrap-server centos102:9092,centos103:9092,centos104:9092 --list

创建Topic命令:

bin/kafka-topics.sh --bootstrap-server centos102:9092,centos103:9092,centos104:9092 --create --topic topic1 --partitions 1 --replication-factor 3  

6.2 生产者命令行操作

在6.1中我们创建了一个主题为topic1的主题,接下来创建一个生产者往topic1中发送数据。

生产者相关命令保存在bin/kafka-console-producer.sh中。

6.3 消费者命令行操作

消费者命令使用与生产者类似,可执行bin/kafka-console-consumer.sh回车进行求助。

第三章 生产者

3.1 生产者消息发送流程

3.1.1 发送原理

在消息发送的过程中,涉及到了 两个线程 ——main线程和Sender线程。在main线程
中创建了一个双端列队列RecordAccumulator。main线程将消息发送给RecordAccumulator,
Sender线程不断从RecordAccumulator中拉取消息发送到 Kafka Broker。 

3.1.2 生产者重要参数

略,详见讲义。

3.2 异步发送API

3.2.1 普通异步发送

需求:创建 Kafka生产者,采用异步的方式发送到 Kafka Broker。

java代码编写,可参考讲义。运行下面代码前,记得将Linux虚拟机集群开启消费消息,观察数据变化。

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.serialization.StringSerializer;

import java.util.Properties;

public class TestProducter {
    public static void main(String[] args) {

        // 1. 创建 kafka 生产者的配置对象
        Properties properties = new Properties();

        // 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092,centos104:9092");
        // key,value 序列化(必须):key.serializer,value.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());

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

        // 4. 调用 send 方法,发送消息
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<>("topic1","test_data"+i));
        }

        // 5. 关闭资源
        kafkaProducer.close();
    }
}

3.2.2 带回调函数的异步发送

回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是元数据信息(RecordMetadata)和异常信息(Exception),如果 Exception 为 null,说明消息发送成功,如果 Exception 不为 null,说明消息发送失败。

代码:

// 4. 调用 send 方法,发送消息(带回调函数)
for (int i = 0; i < 5; i++) {
    kafkaProducer.send(new ProducerRecord<>("topic1", "record_" + i), new Callback() {
        @Override
        public void onCompletion(RecordMetadata metadata, Exception e) {
            if (e == null) {
                // 没有异常,输出信息到控制台
                System.out.println(" 主 题 : " + metadata.topic() + "->" + "分区:" + metadata.partition());
            } else {
                // 出现异常打印
                e.printStackTrace();
            }
        }
    });

    // 延迟一会会看到数据发往不同分区
    Thread.sleep(2);
}

3.3 同步发送API

只需在异步发送的基础上,再调用一下 get()方法即可。

3.4 生产者分区

分区的好处:

默认的分区策略:
/**
 * The default partitioning strategy:
 * <ul>
 * <li>If a partition is specified in the record, use it
 * <li>If no partition is specified but a key is present choose a partition based on a hash of the key
 * <li>If no partition or key is present choose the sticky partition that changes when the batch is full.
 * 
 * See KIP-480 for details about sticky partitioning.
 */
public class DefaultPartitioner implements Partitioner {
  • 如果你指定了分区策略,那么使用你指定的分区策略;
  • 如果没有指定分区,但key是存在的,那么会对key进行hash计算来确定分区;
  • 如果分区和key都没有指定,选择在批处理已满时更改的粘性分区。

面试题:如果保证某张mysql表中的数据发送到某一分区?

答:指定key,且将表名设定为key。

自定义分区器:
  • 将数据发往指定分区;
  • 过滤脏数据;

第一步:自定义分区器MyPartitioner类实现Partitioner接口,重写其方法; 

package org.wuya;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;

import java.util.Map;

public class MyPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        String msgValue = value.toString();
        int partition;
        //如果消息中包含"record_"字符串,发送到0分区;否则发送到1分区
        if (msgValue.contains("record_")) {
            partition = 0;
        } else {
            partition = 1;
        }
        return partition;
    }

    //关闭资源
    @Override
    public void close() {

    }

    //配置方法
    @Override
    public void configure(Map<String, ?> configs) {

    }
}

第二步:在java代码中,生产者的配置中添加分区器参数;

properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,MyPartitioner.class);

第三步:测试。

3.5 生产经验——生产者如何提高吞吐量

package org.wuya;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

public class TestProductor2 {
    public static void main(String[] args) {

        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "centos102:9092,centos103:9092,centos104:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        //batch.size:批次大小,默认 16K
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16 * 1024);
        //linger.ms:等待时间,默认 0
        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        //RecordAccumulator:缓冲区大小,默认 32M:buffer.memory
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 32 * 1024 * 1024L);
        //compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4、zstd,通常使用snappy较多
        props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
        //1.创建生产者(将消息发布到Kafka集群的Kafka客户端)
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);

        //2.生产数据
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<>("topic1", "wuya_" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception e) {
                    if (e == null) {
                        System.out.println("主题:" + metadata.topic() + "分区:" + metadata.partition());
                    } else {
                        e.printStackTrace();
                    }
                }
            });
        }

        //3.关闭资源
        kafkaProducer.close();
    }
}

以上kafka生产者的默认配置信息在ProducerConfig.java类中的静态代码块中都可以看到。 

3.6 生产经验——数据可靠性(不丢失)

回顾发送流程:首先kafka producer生产数据消息,通过send方法发送到缓存队列,达到batch.size或者linger.ms后,sender线程读取队列中消息经由selector发送到kafka集群,kafka集群接收到消息后按照一定规则进行处理后应答(acks)。这其中有3中应答方式:

  • 0:生产者发送过来的数据,无需落盘(持久化)即进行应答;
  • 1:Leader收到数据后应答;
  • -1(或者all):Leader和isr队列的所有节点收齐数据后应答。默认值为-1。

数据可靠性分析:acks=0时,有丢失数据风险,可靠性差,效率高。acks=1时,Leader收到数据即进行应答,若应答完成后还没同步副本时,Leader挂了,会重新进行Leader选举,这种情况下新的Leader上面是没有刚刚的数据的,所以acks=1同样有丢失数据风险,可靠性中等,效率中等。acks=-1时,可靠性高,效率低,但其实它的可靠性高也是有条件的,如下:

ISR含义:

In-Sync Replicas (同步副本)的缩写;

Leader维护了一个动态的ISR,即和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。

如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。

Leader发生故障之后,就会从ISR中选举新的leader

//设置acks,默认-1(同all)
properties.put(ProducerConfig.ACKS_CONFIG, -1);
//重试次数 retries,默认是int最大值2147483647
properties.put(ProducerConfig.RETRIES_CONFIG, 3);

3.7 生产经验——数据去重(幂等性&事务)

acks=-1时,虽然可以保证消息不丢失,确保可靠性,但是却存在消息重复发送的可能性,即不能保证消息的唯一性。即便将参数enable.idempotence设置为true(默认值就是true哦),也不能保证消息的唯一性,因为如果broker重启后PID就会改变。因此,要想保证消息唯一性,须使用事务解决。

生产者事务

1)生产者事务原理

说明:开启事务时,必须保证幂等性也是开启状态哦!

2)事务API

package org.wuya;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.errors.ProducerFencedException;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

public class TestProductor3 {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "centos102:9092,centos103:9092,centos104:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        //开启幂等性enable.idempotence,默认就是开启的
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
        //设置acks,默认就是-1(同all)
        props.put(ProducerConfig.ACKS_CONFIG, "-1");
        //重试次数 retries,默认是int最大值2147483647
        props.put(ProducerConfig.RETRIES_CONFIG, 3);
        //TODO 使用kafka生产者时,必须指定事务id,否则运行程序会报错:Transactional method invoked on a non-transactional producer.
        props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transactional_id_001");//事务id须全局唯一

        //1.创建生产者(将消息发布到Kafka集群的Kafka客户端)
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);

        //TODO 事务操作
        //初始化事务
        kafkaProducer.initTransactions();
        //开启事务
        kafkaProducer.beginTransaction();
        try {
            //2.生产数据
            for (int i = 0; i < 5; i++) {
                kafkaProducer.send(new ProducerRecord<>("topic1", "wuy34a_" + i), new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata metadata, Exception e) {
                        if (e == null) {
                            System.out.println("主题:" + metadata.topic() + "分区:" + metadata.partition());
                        } else {
                            e.printStackTrace();
                        }
                    }
                });
            }
//            int i = 1/0;
            //提交事务
            kafkaProducer.commitTransaction();
        } catch (Exception e) {
            //回滚事务
            kafkaProducer.abortTransaction();
        } finally {
            //3.关闭资源
            kafkaProducer.close();
        }
    }
}

3.8 生产经验——数据有序

3.9 生产经验——数据乱序

第四章 kafka Broker

zookeeper存储kafka的哪些信息?

①集群中哪些机器(Broker)处于存活状态;

②每一个主题、每一个分区下面谁是Leader,谁是isr;

③Leader选举的controller信息。

主要记住这三个即可。

Broker kafka总体工作流程:

节点的服役与退役

略。详见视频25-27

kafka副本:

用于增加数据的可靠性;

默认副本为1个,生产环境通常设置为两个;

kafka副本分为Leader和follower,无论是生产者还是消费者,它们操作的数据只针对Leader;

Kafka 分区中的所有副本统称为 AR(Assigned Repllicas)。

AR = ISR + OSR

ISR,表示和 Leader 保持同步的 Follower 集合。如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出 ISR。该时间阈值由 replica.lag.time.max.ms参数设定,默认 30s。Leader 发生故障之后,就会从 ISR中选举新的 Leader。

OSR ,表示 Follower 与 Leader副本同步时,延迟过多的副本。

Leader选举流程:

选举原则是在isr中存活为前提,按照Replicas副本中排在前面的优先。

Follower故障处理细节:

Leader故障处理细节:

文件存储机制

2) 思考:Topic 数据到底存储 在什么位置?

略。

3)index 文件和 log 文件详解

面试题:kafka索引是按照什么方式存储的?

答:稀疏索引。默认每往log文件写入4kb数据,会往index文件写入一条索引。

文件清理策略

高效读写数据(高频面试)

第五章 kafka消费者

5.1 Kafka 消费方式

Kafka 消费方式为pull(拉)模式——consumer采用从broker中主动拉取数据。

5.2 Kafka 消费者 工作流程

5.2.1 消费者总体工作流程

5.2.2 消费者组原理

消费者组: 

消费者组初始化流程:

图片中的7)是高频面试题,即消费者与集群是如何保持通信的呢?

答:心跳机制,默认是3秒。超时或者消费者处理消息的时间过长,都会触发再平衡。

消费者组详细消费流程:

5.2.3 消费者重要参数

略。见讲义。

5.3 消费者API

5.3.1 独立消费者案例(订阅主题)

需求:创建一个独立消费者,消费 topic1主题中数据。

注意:在消费者 API 代码中必须配置消费者组 id。命令行启动消费者不填写消费者组id会被自动填写随机的消费者组 id。

否则会报错:Exception in thread "main" org.apache.kafka.common.errors.InvalidGroupIdException: To use the group management or offset commit APIs, you must provide a valid group.id in the consumer configuration.
 

代码:

package org.wuya;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class TestConsumer1 {
    public static void main(String[] args) {
        //0.配置信息
        Properties properties = new Properties();
        //连接bootstrap.servers
        //properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092");
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092,centos104:9092");
        //反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        //配置消费者组id(必须得配)
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");//消费者组id的名字随意
        //properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test01");//TODO 组id名字写成"test01",不知道为什么控制台打印不出任何信息!!!

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

        //2.订阅主题
        List<String> topicsList = new ArrayList<>();
        topicsList.add("topic1");
        kafkaConsumer.subscribe(topicsList);

        //3.消费数据
        while(true){
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            for (ConsumerRecord<String, String> record : consumerRecords) {
                System.out.println(record);
            }
        }
    }
}

5.3.2 独立消费者案例(订阅分区)

即:消费某主题的特定分区中的数据。

需求:创建一个独立消费者,消费 topic1 主题0号分区的数据。

消费端代码:(其他代码均与5.3.1中的相同)

生产者代码:

public class TestProductor2 {
    public static void main(String[] args) {

        Properties props = new Properties();
        System.out.println(ProducerConfig.configNames());
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "centos102:9092,centos103:9092,centos104:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        //batch.size:批次大小,默认 16K
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16 * 1024);
        //linger.ms:等待时间,默认 0
        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        //RecordAccumulator:缓冲区大小,默认 32M:buffer.memory
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 32 * 1024 * 1024L);
        //compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4、zstd,通常使用snappy较多
        props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
        //1.创建生产者(将消息发布到Kafka集群的Kafka客户端)
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);

        //2.生产数据(特定分区,如0分区)
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<>("topic1",0,"", "wuya_" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception e) {
                    if (e == null) {
                        System.out.println("主题:" + metadata.topic() + "分区:" + metadata.partition());
                    } else {
                        e.printStackTrace();
                    }
                }
            });
        }

        //3.关闭资源
        kafkaProducer.close();
    }
}

5.3.3 消费者组案例

需求:测试同一个主题的分区数据,只能由消费者组中的一个消费者消费,即不能由一个消费者组中的多个消费者消费。

代码:略。

问:如何指定多个消费者为一个消费者组?

答:通过在消费者端代码中配置相同的来指定。

properties.put(ConsumerConfig.GROUP_ID_CONFIG,"相同的组id的名称");

5.4 生产经验——分区的分配以及再平衡

具体可以在kafka官网搜索“partition.assignment.strategy” 便可以看到4中分区策略。

  • org.apache.kafka.clients.consumer.RangeAssignor: Assigns partitions on a per-topic basis.
  • org.apache.kafka.clients.consumer.RoundRobinAssignor: Assigns partitions to consumers in a round-robin fashion.
  • org.apache.kafka.clients.consumer.StickyAssignor: Guarantees an assignment that is maximally balanced while preserving as many existing partition assignments as possible.
  • org.apache.kafka.clients.consumer.CooperativeStickyAssignor: Follows the same StickyAssignor logic, but allows for cooperative rebalancing.

The default assignor is [RangeAssignor, CooperativeStickyAssignor], which will use the RangeAssignor by default, but allows upgrading to the CooperativeStickyAssignor with just a single rolling bounce that removes the RangeAssignor from the list.

Implementing the org.apache.kafka.clients.consumer.ConsumerPartitionAssignor interface allows you to plug in a custom assignment strategy.

5.4.1 Range以及再平衡

Range分区策略原理:略。

缺点:当分区特别多时,会造成数据倾斜。

5.4.2 RoundRobin以及再平衡

RoundRobin分区策略原理:略。

Range是针对一个主题而言的,RoundRobin是针对集群中所有的主题(topic)而言的,所以RoundRobin不存在数据倾斜的问题。

RoundRobin 轮询分区策略,是把所有的 partition 和所有的consumer 都列出来,然后按照hashcode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。

// 修改分区分配策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor");

5.4.3 Sticky以及再平衡

保证分配最大程度的平衡,同时保留尽可能多的现有分区分配。随机分配的。

5.5 offset位移

1. offset的默认维护位置

kafka中的三个重要角色:生产者、集群、消费者。生产者把数据发送到集群中的leader分区,消费者组中的消费者开始消费某一分区的数据。消费过程中,有个offset的概念,用来标记消费到哪了。

offset的默认维护位置:Kafka0.9版本之前,consumer默认将offset保存在Zookeeper中;从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets。

为什么0.9版本前维护在zk中,之后不在维护在zk了呢?

答:为了避免过多的网络交互。

__consumer_offsets 主题里面采用key和value 的方式存储数据。key是group.id+topic+分区号,value就是当前offset的值。每隔一段时间,kafka 内部会对这个 topic 进行压缩(compact),也就是每个group.id+topic+分区号就保留最新数据。 

2. 自动提交offset

//自动提交开关"enable.auto.commit",默认就是true
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
//自动提交间隔时间"auto.commit.interval.ms",默认为5000ms
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,1000);

3. 手动提交offset

具体应用:

首先代码中把自动提交关掉:

//手动提交
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,false);

然后,手动提交offset:

//3.消费数据
while(true){
    ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
    for (ConsumerRecord<String, String> record : consumerRecords) {
        System.out.println(record);
    }
    //同步提交offset
    //kafkaConsumer.commitSync();

    //异步提交 offset
    kafkaConsumer.commitAsync();
}

4. 指定offset消费

auto.offset.reset = earliest | latest | none 默认是 latest。

任意指定offset位移开始消费 

代码:

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

public class TestConsumerSeek {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"wuya4");

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

        List<String> topicsList = new ArrayList<>();
        topicsList.add("topic1");
        kafkaConsumer.subscribe(topicsList);

        //指定位置进行消费
        Set<TopicPartition> assignment = kafkaConsumer.assignment();

        //保证分区分配方案已经制定完毕(因为消费者组初始化需要一个过程)
        while(assignment.size()==0){
            System.out.println("=======================================");
            kafkaConsumer.poll(Duration.ofSeconds(1));
            //更新数据,重新分配
            assignment = kafkaConsumer.assignment();
        }

        //指定消费的offset
        for (TopicPartition partition : assignment) {
            kafkaConsumer.seek(partition,352);
        }

        while(true){
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

注意:每次执行完,需要修改消费者组名;(我实际操作时,不改也可以,只是第二次执行时需要的时间久一点,大概30秒左右) 

5. 指定时间消费

没有直接指定时间消费的API,需要通过kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap)这个API进行转换。

package org.wuya;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.*;

public class TestConsumerSeek {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"wuya53");

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

        List<String> topicsList = new ArrayList<>();
        topicsList.add("topic1");
        kafkaConsumer.subscribe(topicsList);

        //指定位置进行消费
        Set<TopicPartition> assignment = kafkaConsumer.assignment();

        //保证分区分配方案已经制定完毕(因为消费者组初始化需要一个过程)
        while(assignment.size()==0){
            System.out.println("=======================================");
            kafkaConsumer.poll(Duration.ofSeconds(1));
            //更新数据,重新分配
            assignment = kafkaConsumer.assignment();
        }

        HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();
        for (TopicPartition topicPartition : assignment) {
            topicPartitionLongHashMap.put(topicPartition, System.currentTimeMillis() - 1*24*3600*1000L);
        }

        Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);

        //指定消费的offset
        for (TopicPartition partition : assignment) {
            OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(partition);
            kafkaConsumer.seek(partition,offsetAndTimestamp.offset());
        }

        while(true){
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

6. 漏消费&重复消费问题

5.6 生产经验——消费者事务

5.7 生产经验——数据积压( 消费者如何提高吞吐量)

第六章 Kafka-Eagle监控

Kafka-Eagle框架可以监控 Kafka 集群的整体运行情况,在生产环境中经常使用。

Kafka-Eagle 的安装依赖于 MySQL,MySQL 主要用来存储可视化展示的数据。

官网:EFAK (kafka-eagle.org)

第七章 Kafka-Kraft模式

kafka2.8.0之后,不再必须依赖zookeeper了。

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值