Kafka知识点

备注:这是我的个人学习笔记,仅供个人学习。

Kafka消息队列应用场景

传统的消息队列主要应用场景包括:缓冲/消峰,异步通信,解耦
缓冲/消峰:解决生产消息和消费消息的处理速度不一致问题
在这里插入图片描述

解耦:允许独立扩展或者修改两边的处理过程
在这里插入图片描述

异步通信:把消息放入一个消息队列中,并不立刻处理,再需要的时候再去处理
在这里插入图片描述

消息队列的两种模式

点对点模式:

在这里插入图片描述

发布、订阅模式:

在这里插入图片描述

Kafka基础架构

在这里插入图片描述
我有100T的数据,一个服务器存不下,我就可以分开几个分区,分别存储。broker代表服务器,也就是hadoop01,Hadoop02。每个服务器中会有不同的主题,一个主题会有多个分区,每个分区的数据只能有一个消费者进行消费。Topic可以理解为一个队列,消息在排着队等待消费。同时每个分区也有副本,当Leader挂了以后,Follower会成为新的Leader。无论是生产者发送消息的对象,或者消费者消费消息的对象都是Leader。

Kafka启动

先开Zookeeper,再开Kafka。关闭也是先关Zookeeper,再关Kafka。

[root@hadoop01 kafka_2.12-3.0.0]# bin/kafka-server-start.sh -daemon config/server.properties

在这里插入图片描述

Kafka命令的使用

在这里插入图片描述

创建主题

bin/kafka-topics.sh --bootstrap-server hadoop01:9092 --topic first --create --partitions 1 -replication-factor 3
# --bootstrap-server 连接kafka的主机名和端口号,默认9092
# --topic 操作topic的名称
# --create 创建
# --partitions 分区数量
# --replication-factor 副本数量

在这里插入图片描述

查看主题

bin/kafka-topics.sh --bootstrap-server hadoop01:9092 --topic first --describe

在这里插入图片描述

修改分区数

bin/kafka-topics.sh --bootstrap-server hadoop01:9092 --topic first --alter --partitions 3

查看分区
在这里插入图片描述
需要注意的是!
分区数只能增加不能减少

修改副本

kafka不支持在命令行中修改副本的数量

生产者

启动生产者

bin/kafka-console-producer.sh --bootstrap-server hadoop01:9092 --topic first

启动消费者

bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic first

在生产者中发送消息,消费者消费消息
在这里插入图片描述
让消费者重新消费消息,从开始处

bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic first --from-beginning

生产者发送原理

在这里插入图片描述
首先,生产者创建一个main线程,通过send方法,发送数据,经过拦截器,然后序列化数据,因为要发到不同的节点上所以需要序列化,再到分区器,分区器中有一个缓冲队列,默认是32M,每一个批次(batch)默认16k。当batch.size或者linger.ms达到设定的时候就会发送数据。发送数据之后kafka集群会进行一个acks的应答。
0:消息发送完成,不用等落盘,就应答
1:消息发送给Leader,Leader落盘再应答
-1/all :等Leader和所有副本都落盘再应答

linger.ms 默认 0ms
batch.size 默认 16k

KafkaAPI

添加依赖

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

生产者

异步发送

在这里插入代码片public class Producer {
    public static void main(String[] args) {
//        配置
        Properties properties = new Properties();
//        往配置中put信息(配置)
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        //        创建kafka生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);
//        发送数据
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<String,String>("first","atguigu"+i));
        }
//        关闭资源
        kafkaProducer.close();
    }
}

运行程序,并且运行消费者

bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic first

在这里插入图片描述

回调 异步发送

其实就是在发送数据的地方new 一个 callback
callback返回主题,分区等信息

public class Producer {
    public static void main(String[] args) {
//        配置
        Properties properties = new Properties();
//        往配置中put信息(配置)
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        //        创建kafka生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);
//        发送数据
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<String, String>("first", "atguigu" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if(e == null){
                        System.out.println("主题:"+recordMetadata.topic()+" 分区:"+recordMetadata.partition());
                    }
                }
            });
        }
//        关闭资源
        kafkaProducer.close();
    }
}

运行结果
在这里插入图片描述

同步发送

其实就是在发送数据的地方,加一个 .get()

   发送数据
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<String, String>("first", "atguigu" + i)).get();
        }

异步发送和同步发送的区别:
异步发送就是可以不用等缓冲队列中的数据都发送完成再发送。
同步发送,要等缓冲队列中的数据都发送完成后再发送。

分区

kafka默认分区

  1. 如果指名partition的情况下,直接将值作为partition值
  2. 如果没有指明partition,但是又key,会对key的hash值与分区数取余,得到partition值

例如key1的hash值为5,topic有2个分区,5%2,余1,所以存入1号分区

  1. 既没有partition值又没有key的值,kafka采用粘性分区器,随机选择一个分区,当该分区满了,或者达到batch值或者linger.ms设定的值的时候,kafka会再随机选择除了上一个之外的分区

演示:
指定partition为1
在这里插入图片描述
在这里插入图片描述
根据key的hash值进行partition分区
在这里插入图片描述
在这里插入图片描述
企业中,会有很多张表,我希望将其中的 订单表 发送到kafka的某一个分区,我们只需要将 订单表的表名作为key然后取hash值,这个时候同一张表的数据就会发到同一个分区中.
不指定分区,随机
在这里插入图片描述
在这里插入图片描述

自定义分区器

继承接口Partitioner,重写方法

@param topic 主题
@param key 消息的 key
@param keyBytes 消息的 key 序列化后的字节数组
@param value 消息的 value
@param valueBytes 消息的 value 序列化后的字节数组
@param cluster 集群元数据可以查看分区信息

public class PartitionTest implements Partitioner {
    @Override
    public int partition(String s, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
    //对value进行分区
        String msgValue = value.toString();
        int partition;
        if (msgValue.contains("atguigu")){
            partition = 0;
        }else {
            partition = 1;
        }
        return partition;
    }

    @Override
    public void close() {

    }

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

    }
}

在生产者中加入自定义分区器配置

在这里插入图片描述
运行,当值为hello的时候,消息发送到1号分区
在这里插入图片描述
当值为atguigu的时候,消息发送到0号分区
在这里插入图片描述

生产者提高吞吐量

public class Producer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
//        缓冲区大小,默认32M
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
//        批次大小,默认16k
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
//        时间延迟,默认0ms
        properties.put(ProducerConfig.LINGER_MS_CONFIG,1);
//        压缩类型,默认none,可配置值gzip,snappy,lz4,zstd
        properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");

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

        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<>("first","atguigu"+i));
        }

        kafkaProducer.close();

    }
}

数据的可靠性

在这里插入图片描述
0 :数据从Producer发送给Leader后,还没有来得及消费和同步副本,Leader挂了,此时会造成数据的丢失

1 :数据从Producer发送给Leader后,落盘成功,并且给Producer应答,但是还没来得及同步副本,Leader挂了,此时也造成了数据的丢失

-1 :数据从Producer发送给Leader后,Leader同步完所有的副本,给Producer应答,这个比较可靠,也不是十分可靠,因为分区副本设置为1个,或者ISR里应答的最小副本数量(min.insync.replicas默认为1)设置为1,和ack=1的效果是一样的,仍然有丢数据的风险。

(Leader维护了一个动态的in-sync replica set(ISR),意为和
Leader保持同步的Follower+Leader集合(leader:0,isr:0,1,2)。
如果Follower长时间未向Leader发送通信请求或同步数据,则
该Follower将被踢出ISR。该时间阈值由replica.lag.time.max.ms参
数设定,默认30s。例如2超时,(leader:0, isr:0,1)。

数据完全可靠条件 = ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2

可靠性总结:
acks=0,生产者发送过来数据就不管了,可靠性差,效率高;
acks=1,生产者发送过来数据Leader应答,可靠性中等,效率中等;
acks=-1,生产者发送过来数据Leader和ISR队列里面所有Follwer应答,可靠性高,效率低;
在生产环境中,acks=0很少使用;acks=1,一般用于传输普通日志,允许丢个别数据;acks=-1,一般用于传输和钱相关的数据,
对可靠性要求比较高的场景。

//        acks
        properties.put(ProducerConfig.ACKS_CONFIG,"1");
//        重试次数
        properties.put(ProducerConfig.RETRIES_CONFIG,3);

如果acks = -1 ,这里的数据还是有可能出现问题,那就是数据重复的问题
Producer发送数据给Leader,Leader写入完成,并把数据同步到副本,就在给Producer应答前,Leader挂了,Follower会被选为Leader,Producer会因为没有得到应答会将数据再重新发送,此时的数据就会造成重复。
在这里插入图片描述

数据重复

幂等性原理

重复数据的判断标准:
具有<PID, Partition, SeqNumber>相同主键的消息提交时,Broker只会持久化一条。其
中PID是Kafka每次重启都会分配一个新的;Partition 表示分区号;Sequence Number是单调自增的。

开启参数 enable.idempotence 默认为 truefalse 关闭。

事务

public class Translation {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());

        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//        设置事务id(必须),事务id保证唯一就行
        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"translation01");
//        初始化事务
        kafkaProducer.initTransactions();
//        开启事务
        kafkaProducer.beginTransaction();
        try {
            for (int i = 0; i < 5; i++) {
                kafkaProducer.send(new ProducerRecord<>("first","atguigu"+i));
            }
//            提交事务
            kafkaProducer.commitTransaction();
        }catch (Exception e){
//            终止事务
            kafkaProducer.abortTransaction();
        }finally {
            kafkaProducer.close();   
        }
    }
}

数据有序

在这里插入图片描述
怎么保证数据有序,有两种

  1. 未开启幂等性
max.in.flight.requests.per.connection

需要设置为1。

  1. 开启幂等性
max.in.flight.requests.per.connection

需要设置小于等于5。
因为,启用幂等性后,kafka服务端会缓存producer发来的最近5个request的元数据,
会根据<pid,partition,SeqNumber>来确定数据是否重复,那传过来的数据,根据序列化号进行一个排序,无论如何最近5个数据都是有序的。

Broker

zookeeper存储的Kafka信息

在这里插入图片描述

Broker的工作流程

在这里插入图片描述

  1. 启动kafka服务后,每个broker中都会有Controller,然后再zookeeper中进行注册,启动完成就去注册

  2. 看controller谁先注册的,就谁说了算,谁就辅助选举Leader

  3. 选举处理的Controller监听Brokers的节点变化

  4. controller决定Leader选举,选举规则就是保证isr存活为前提,按照AR中的顺序

  5. controller将节点信息上传到zookeeper

  6. 其他的controller从zookeeper中同步相关信息,为了防止controller Leader挂了,挂了后其他controller能够上位

  7. 生产者往Leader中发送数据,然后同步数据到Follower

  8. 当Leader挂了,controller Leader就会监听到节点的变化

  9. 获取ISR

  10. 选取新的leader(参照第4点)

  11. 更新Leader和ISR

Kafka副本

副本的作用:提高可靠性
Kafka副本默认1个
Kafka中副本分为Leader和Follow,Kafka生产者只会把数据发往Leader,然后Follower找Leader进行同步数据。

副本的统称AR
AR=ISR+OSR
ISR存活的副本,OSR延迟过多的副本

Leader的选举流程

在这里插入图片描述

  1. 启动Kafka服务后,broker向ZK进行注册
  2. 看那个broker先注册,那个controller就是controller Leader,辅助Leader的选举
  3. controller监听brokers的节点变化
  4. controller进行Leader的选举,选举规则就是按照AR顺序进行上位
  5. controller将节点信息上传到ZK
  6. 其他的controller从ZK同步相关的信息

Leader和Follower故障处理细节

LEO:每个副本最新的offset+1
HW:所有副本最小的LEO

  1. Follower故障
    Follower发生了故障,首先将Follower从ISR中移除
    这个期间Leader和其他Follower继续接受数据
    等Follower恢复后,Follower会读取本地的磁盘记录,上次的HW,并将log文件高于HW的部分删除,从HW处向Leader进行同步
    等Folower的LEO大于等于该Partition的HW,即Follower追上Leader之后,重新加入ISR
    在这里插入图片描述

  2. Leader故障
    Leader发生了故障,会从ISR中选出一个新的Leader
    为保证多个副本之间的数据一致性,其余的Follower会先将各自的log文件高于HW的部分截掉,然后从新的Leader同步数据
    在这里插入图片描述
    这里只能保证数据的一致性,并不能保证数据不丢失或者不重复。
    因为上一个Leader已经处理了数据的5,6,7了,新的Leader还没有处理,所以同步过后,会造成5,6,7的丢失

手动调整分区副本

创建新主题,名称为three(4个分区,2个副本)

bin/kafka-topics.sh --bootstrap-server 
hadoop102:9092 --create --partitions 4 --replication-factor 2 --
topic three

查看分区情况
在这里插入图片描述
创建副本存储计划

vim increase-replication-factor.json

输入以下内容

{
"version":1,
"partitions":[{"topic":"three","partition":0,"replicas":[0,1]},
{"topic":"three","partition":1,"replicas":[0,1]},
{"topic":"three","partition":2,"replicas":[1,0]},
{"topic":"three","partition":3,"replicas":[1,0]}]
}

执行副本计划

bin/kafka-reassign-partitions.sh --
bootstrap-server hadoop102:9092 --reassignment-json-file 
increase-replication-factor.json --execute

验证副本计划

bin/kafka-reassign-partitions.sh --
bootstrap-server hadoop102:9092 --reassignment-json-file 
increase-replication-factor.json --verify

在这里插入图片描述

增加副本

创建topic(3个分区,一个副本)

bin/kafka-topics.sh --bootstrap-server 
hadoop102:9092 --create --partitions 3 --replication-factor 1 --
topic four

只能手动增加副本,不能通过命令行的方式进行操作
手动增加副本

vim increase-replication-factor.json
{"version":1,
"partitions":[
{"topic":"four","partition":0,"replicas":[0,1,2]},
{"topic":"four","partition":1,"replicas":[0,1,2]},
{"topic":"four","partition":2,"replicas":[0,1,2]}
]}

文件存储

在这里插入图片描述
一个Topic下有多个Partitions,一个partition有一个log文件,这个只是一个虚拟的,log文件下又存储着Segment,每个Segment 有1G的数据。

index和log文件以当前segment的第一条消息的offset命名。所以他的有序的

index文件和log文件

注意:

  1. index为稀疏索引,大约每往log文件写入4kb数据,就会往index文件写入一条索引。(参数log.index.interval.bytes默认4kb。

  2. Index文件中保存的offset为相对offset,这样能确保offset的值所占空间不会过大,
    因此能将offset的值控制在固定大小

在这里插入图片描述

文件清理策略

Kafka 中默认的日志保存时间为 7 天,可以通过调整如下参数修改保存时间。
⚫ log.retention.hours,最低优先级小时,默认 7 天。
⚫ log.retention.minutes,分钟。
⚫ log.retention.ms,最高优先级毫秒。
⚫ log.retention.check.interval.ms,负责设置检查周期,默认 5 分钟。

超过了时间,提供了两种日志清理策略 delete 和 compact

  1. delte
    ⚫ log.cleanup.policy = delete 所有数据启用删除策略
    基于时间,默认打开,以segment中最大时间戳作为该文件的时间戳
    基于大小,默认关闭,超过设置的大小,删除最早的(默认是-1,表示无穷大)

  2. compact

对于相同key的不同value值,只保留最后一个版本。
⚫ log.cleanup.policy = compact 所有数据启用压缩策略
在这里插入图片描述
压缩后的数据offset是不连续的。
适合使用在消息的key是用户ID,value是用户的资料,这样就保留了最新的用户资料了。

高效读写数据(重要!!!)

1)Kafka 本身是分布式集群,可以采用分区技术,并行度高
2)读数据采用稀疏索引,可以快速定位要消费的数据
3)顺序写磁盘,Kafka的生产者生产数据,要写入log文件中,写的过程一直都是追加到文件末端,魏顺序写(能达到100M/S)
4)页缓冲+零拷贝技术,Kafka重度依赖底层操作系统提供的PageCache功能。当上层有写操作时,操作系统只是将数据写入PageCache。当读操作发生时,先从PageCache中查找,如果找不到,再去磁盘中读取。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用

Kafka消费者

Kakfa消费方式

kafka采用的消费方式是pull(拉取)方式。consumer从broker中主动拉取数据。
这个模式的不足之处在于,如果Kafka中没有数据,消费者可能会陷入循环中,一直返回空数据。

消费者工作流程

在这里插入图片描述
生产者向broker发送数据,一个消费者可以消费多个分区的数据,但是每个分区只能由一个消费者组里的一个消费者消费。每个消费者的offset有消费者提交到系统主题保存

消费者组原理

一个消费者组中的每个消费者只能消费一个分区的数据,最多一个分区一个消费者,多出的消费者就会闲置。
消费者组之间互不影响。

消费者组初始化流程(重要!!!)

在这里插入图片描述

coordinator:辅助实现消费者组的初始化和分区的分配。
coordinator节点选择 = groupid的hashcode值 % 50( __consumer_offsets的分区数量)
例如: groupid的hashcode值 = 1,1% 50 = 1,那么__consumer_offsets 主题的1号分区,在哪个broker上,就选择这个节点的coordinator
作为这个消费者组的老大。消费者组下的所有的消费者提交offset的时候就往这个分区去提交offset。

重要:

面试题:
什么时候会触发再平衡机制?

答:
每个消费者都会和coordinator保持心跳(默认3s),一旦超时(session.timeout.ms=45s),该消费者会被移除,并触发再平衡;
或者消费者处理消息的时间过长(max.poll.interval.ms5分钟),也会触发再平衡

消费者API操作

消费者消费某一个主题

public class Consumer {
    public static void main(String[] args) {
        Properties properties = new Properties();
//        连接集群
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
//        对Key Value进行反序列化操作
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
//        重要!!!,一定要设置一个groupID
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");

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

        ArrayList<String> topic = new ArrayList<>();
        topic.add("first");
        kafkaConsumer.subscribe(topic);

        while (true){
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));

            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

启动生产者,往里面发送数据,运行效果

在这里插入图片描述

消费某一个分区
public class Consumer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02: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,"test");


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

        ArrayList<Object> partitions = new ArrayList<>();
//        创建一个TopicPartition
        partitions.add(new TopicPartition("first",0));
//        分区就使用assign
        kafkaConsumer.assign(partitions);

        while (true){
            ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(3));

            for (Object consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

往分区0中发送数据

//生产者的代码省略,这里是重要代码
for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<>("first",0,"","atguigu"+i));
        }

消费者运行截图
在这里插入图片描述

Range以及再平衡

创建一个7个分区的topic

在这里插入图片描述

启动消费者
确保他们在同一个消费者组里

properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");

启动生产者,让他们往不同的分区发送数据

    for (int i = 0; i < 500; i++) {
            kafkaProducer.send(new ProducerRecord<>("first","atguigu"+i));
            Thread.sleep(1);
        }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当我挂了一个消费者后(45 S )
在这里插入图片描述
可以看到5,6号分区的数据,分在了一个消费者中,
所以
消费者1 消费3,4,5,6
消费者2 消费0,1,2
所以容易造成数据倾斜

以下就是Range的策略

在这里插入图片描述

RoundRobin分区策略

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

也就是轮询的办法
在这里插入图片描述

Sticky分区策略

粘性分区定义:可以理解为分配的结果带有“粘性的”。即在执行一次新的分配之前,
考虑上一次分配的结果,尽量少的调整分配的变动,可以节省大量的开销

首先会尽量均衡随机的放置分区
到消费者上面,在出现同一消费者组内消费者出现问题的时候,会尽量保持原有分配的分
区不变化。

// 修改分区分配策略
ArrayList<String> startegys = new ArrayList<>();
startegys.add("org.apache.kafka.clients.consumer.StickyAssignor");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, startegys);

offset位移

offset的默认维护位置

0.9版本之前offset维护在Zookeeper之前,0.9版本之后维护在系统主题当中(__consumer__offsets)

自动提交offset

⚫ enable.auto.commit:是否开启自动提交offset功能,默认是true
⚫ auto.commit.interval.ms:自动提交offset的时间间隔,默认是5s

// 是否自动提交 offset
 properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
 // 提交 offset 的时间周期 1000ms,默认 5s
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);

手动提交offset

  1. 同步提交:等待offset提交完毕后,再去消费下一批数据
  2. 异步提交:发送完提交offset请求后,不用等提交完毕,就开始消费下一批数据
// 是否自动提交 offset
 properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
// 同步提交 offset
 consumer.commitSync();
 // 异步提交 offset
 consumer.commitAsync();

指定offset消费

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

(1)earliest:自动将偏移量重置为最早的偏移量,–from-beginning。
(2)latest(默认值):自动将偏移量重置为最新偏移量。
(3)none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。

public class Consumer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02: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,"test");

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

        ArrayList<String> topic = new ArrayList<>();
        topic.add("first");
        kafkaConsumer.subscribe(topic);

        Set<TopicPartition> assignment = kafkaConsumer.assignment();

        while (assignment.size() == 0) {
            kafkaConsumer.poll(Duration.ofSeconds(1));
            // 获取消费者分区分配信息(有了分区分配信息才能开始消费)
            assignment = kafkaConsumer.assignment();
        }
//        指定消费的offset
        for (TopicPartition partition : assignment) {
            kafkaConsumer.seek(partition,100);
        }


        while (true){
            ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(3));

            for (Object consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

指定时间消费


public class Consumer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02: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,"test");

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

        ArrayList<String> topic = new ArrayList<>();
        topic.add("first");
        kafkaConsumer.subscribe(topic);
        Set<TopicPartition> assignment = kafkaConsumer.assignment();

        while (assignment.size() ==0){
            kafkaConsumer.poll(Duration.ofSeconds(1));
            // 获取消费者分区分配信息(有了分区分配信息才能开始消费)
            assignment = kafkaConsumer.assignment();
        }
//        把时间转换为offset
        HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();

// 封装集合存储,每个分区对应一天前的数据
        for (TopicPartition topicPartition : assignment) {
            topicPartitionLongHashMap.put(topicPartition,System.currentTimeMillis()-1*24*3600*1000);
        }
// 获取从 1 天前开始消费的每个分区的 offset
        Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);
        // 遍历每个分区,对每个分区设置消费时间。
        for (TopicPartition partition : assignment) {
            OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(partition);
            kafkaConsumer.seek(partition,offsetAndTimestamp.offset());
        }

        while (true){
            ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(3));

            for (Object consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

数据积压问题

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值