【三】kafka体系架构之消费者客户端概述(分区分配策略、再均衡、偏移量)

基于kafka 2.12-2.0.0版本

kafka-clients 2.0.0

本文是《深入理解Kafka核心设计与实践原理》的读书笔记、再均衡部分有在网上找资料。

一、消费者代码demo讲解

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sid</groupId>
    <artifactId>test-kafka</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.0.0</version>
        </dependency>
    </dependencies>
</project>
package com.sid;

public class KafkaProperties {
    public static  final  String ZK = "node1:2181";
    public static  final String TOPIC="sid_topic";
    public static final  String BROKER_LIST ="node1:9092";
    public static final  String GROUP_ID ="test_group1";
}
package com.sid;

import java.time.Duration;
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.StringSerializer;

import java.util.Collections;
import java.util.Properties;

public class ConsumerTest {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("bootstrap.servers", KafkaProperties.BROKER_LIST);
        
        //消费者组ID
        properties.put("group.id", KafkaProperties.GROUP_ID);
        properties.put("key.deserializer", StringSerializer.class.getName());
        properties.put("value.deserializer", StringSerializer.class.getName());
        properties.put("client.id", "consumer-1");

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

        //订阅主题
        consumer.subscribe(Collections.singletonList(KafkaProperties.TOPIC));

        try {
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

                for (ConsumerRecord<String, String> record : records) {
                    System.out.println("topic = "+record.topic()+",partition = "+record.partition()+", offset = "+record.offset());
                    System.out.println("key = "+record.key()+",value = "+record.value());;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            consumer.close();
        }

    }
}

 1.如果前后两次订阅了不同的主题,那么消费者以最后一次为准

例子

consumer.subscribe(Collections.singletonList("topic1"));
consumer.subscribe(Collections.singletonList("topic2));

那么实际上订阅的是topic2这个主题

2.subscribe(Pattern)

如果消费者采用的是正则表达式(subscribe(Pattern))订阅,在之后的过程中,如果有人又创建了新的主题,并且主题名字与正则表达式匹配,那么这个消费者就可用消费到新添加的主题中的消息。

3.subscribe订阅方法还可以入参ConsumerRebalanceListener

设置相应的再均衡监听器

4.KafkaConsumer中assign()方法

消费者不仅可用通过KafkaConsumer中subscribe()方法订阅主题,还可以直接订阅某些主题的特定分区,在KafkaConsumer中还提供了一个assign()方法来实现这个功能

public void assign(Collection<TopicPartition> partitions)

 5.KafkaConsumer中的partitionsFor()方法

可以用来查询指定主题的元素数据

public List<PartitionInfo> partitionsFor(String topic)

其中PartitionInfo类型是主题的分区元数据信息 

6.取消订阅

可以使用KafkaConsumer中unsubscribe()可以取消订阅。

如果将subscribe(Collection)或者assign(Collection)中的集合参数设置为空集合,那么作用就等于unsubscribe()

7.几种订阅方式对应的状态

subscribe(Collection)代表的订阅状态是AUTO_TOPICES

subscribe(Pattern)代表的订阅状态是AUTO_PATTERN

assign(Collection)代表的订阅状态是USER_ASSIGNED

8.subscribe和assign的区别

1.subscribe是订阅主题,assign是直接订阅某些主题的特定分区

2.通过subscribe()方法订阅主题具有消费者自动再均衡的功能,在多个消费者的情况下可以根据分区分配策略来自动分配各个消费者与分区的关系。

当消费组内消费组增加或者减少时,分区分配关系会自动调整,以实现消费负载均衡及故障自动转移。

通过assign()方法订阅分区时,时不具备消费者自动均衡的功能的。

9.KafkaConsumer中的seek()方法指定位移消费

在Kafka中每当消费者查找不到所记录的消费位移或者位移越界时,就会根据消费者客户端参数auto.offset.reset的配置来决定从何处开始进行消费。

这个参数的默认值为"latest",表示从分区末尾开始消费。

这个参数配置为"earliest",那么消费者会从起始处也就是0开始消费。

这个参数配置为"none",那么抛出异常NoOffsetForPartitionException。

seek()方法可以让我们从特定的位移处开始拉取消息。

public void seek(TopicPartition partition, long offset);

seek只能选择消费者已经分配到的分区的消费位置,而分区的分配是在poll()方法的调用过程中实现的,也就是说在执行seek()方法之前需要先执行一次poll()方法,等到分配到分区之后才可以用seek方法选择消费位置。

注意:当poll方法中的参数为0时,此方法立刻返回,那么poll方法内部进行分区分配的逻辑就会来不及执行。 

10.消费者也有相应的拦截器的概念

消费者拦截器主要在消费到消息或在提交消息位移时进行一些定制化的操作。

消费者拦截器需要自定义实现org.apache.kafka.clients.consumer.ConsumerInterceptor

public interface ConsumerInterceptor<K, V> extends Configurable {

    public ConsumerRecords<K, V> onConsume(ConsumerRecords<K, V> records);

    public void onCommit(Map<TopicPartition, OffsetAndMetadata> offsets);

    public void close();
}

KafkaConsumer会在poll方法返回之前调用拦截器的onConsume()方法来对消息进行相应的定制化操作。

如果onConsumer()方法中抛出异常,那么会被捕获并记录到日志中,但是异常不会再向上传递。

 KafkaConsumer会在提交完消费位移之后调用拦截器的onCommit()方法,可以使用这个方法来记录跟踪所有提交的位移信息。

二、消费组

如下图可见
1.同一个topic中的每个分区partition默认情况下只会被同一个消费组中的一个消费者消费
比如p0在消费组B中,只会被c4消费,c5不会消费p0
p0在消费组A中只会被c0消费,c1 c2 c3 不会消费p0
2.可以粗暴的理解,对于同一个topic,不同消费组之间是发布订阅模式,相同消费组内是竞争消费模式
在这里插入图片描述
所以,基于特性1,如果topic有7个分区,而同一个消费组内有8个消费组,则有一个消费组是闲着的,并没有分区给它消费。

当然这是说的默认情况下。开发者可以继承AbstractPartitionAssignor实现自定义消费策略,从而实现同一消费组内的任意消费者都可以消费订阅主题的所有分区
在这里插入图片描述

三、消费端分区分配策略

Kafka 提供了消费者客户端参数 partition.assignment.strategy 来设置消费者与订阅主题之间的分区分配策略

RangeAssignor分配策略(默认)

默认情况下,采用 RangeAssignor 分配策略。

RangeAssignor 分配策略的原理是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。

对于每一个主题,RangeAssignor 策略会将消费组内所有订阅这个主题的消费者按照名称的字典序排序,然后为每个消费者划分固定的分区范围,如果不够平均分配,那么字典序靠前的消费者会被多分配一个分区。

假设消费组内有2个消费者 C0 和 C1,都订阅了主题 t0 和 t1,并且每个主题都有4个分区,那么订阅的所有分区可以标识为:t0p0、t0p1、t0p2、t0p3、t1p0、t1p1、t1p2、t1p3。最终的分配结果为:

消费者C0:t0p0、t0p1、t1p0、t1p1
消费者C1:t0p2、t0p3、t1p2、t1p3

假设上面例子中2个主题都只有3个分区,那么订阅的所有分区可以标识为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终的分配结果为:

消费者C0:t0p0、t0p1、t1p0、t1p1
消费者C1:t0p2、t1p2
可以明显地看到这样的分配并不均匀。

RoundRobinAssignor分配策略

RoundRobinAssignor 分配策略的原理是将消费组内所有消费者及消费者订阅的所有主题的分区按照字典序排序,然后通过轮询方式逐个将分区依次分配给每个消费者。

如果同一个消费组内所有的消费者的订阅信息都是相同的,那么 RoundRobinAssignor 分配策略的分区分配会是均匀的。

比如:

假设消费组中有2个消费者C0和C1,都订阅了主题t0和t1,并且每个主题都有三个分区,那么订阅的所有分区可以标识为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终分配结果为:

消费者C0:t0p0、t0p2、t1p1

消费者C1:t0p1、t1p0、t1p2

如果同一个消费组内的消费者订阅的信息是不相同的,那么在执行分区分配的时候就不是完全的轮询分配,有可能导致分区分配得不均匀。

假设消费组内有3个消费者(C0、C1 和 C2),t0、t0、t1、t2主题分别有1、2、3个分区,即整个消费组订阅了 t0p0、t1p0、t1p1、t2p0、t2p1、t2p2 这6个分区。

具体而言,消费者 C0 订阅的是主题 t0,消费者 C1 订阅的是主题 t0 和 t1,消费者 C2 订阅的是主题 t0、t1 和 t2,那么最终的分配结果为:

消费者C0:t0p0
消费者C1:t1p0
消费者C2:t1p1、t2p0、t2p1、t2p2

可以看 到 RoundRobinAssignor 策略也不是十分完美,这样分配其实并不是最优解,因为完全可以将分区 t1p1 分配给消费者 C1。

StickyAssignor分配策略

kafka从0.11.X版本开始引入这种分配策略,它主要有两个目的:

分区的分配要尽可能均匀。
分区的分配尽可能与上次分配的保持相同。

假设消费组内有3个消费者(C0、C1 和 C2),它们都订阅了4个主题(t0、t1、t2、t3),并且每个主题有2个分区。也就是说,整个消费组订阅了 t0p0、t0p1、t1p0、t1p1、t2p0、t2p1、t3p0、t3p1 这8个分区。最终的分配结果如下:

消费者C0:t0p0、t1p1、t3p0
消费者C1:t0p1、t2p0、t3p1
消费者C2:t1p0、t2p1

再假设此时消费者 C1 脱离了消费组,那么消费组就会进行再均衡操作,进而消费分区会重新分配,分配结果为:

消费者C0:t0p0、t1p1、t3p0、t2p0
消费者C2:t1p0、t2p1、t0p1、t3p1

StickyAssignor 分配策略如同其名称中的“sticky”一样,让分配策略具备一定的“黏性”,尽可能地让前后两次分配相同,进而减少系统资源的损耗及其他异常情况的发生。

四、消费再均衡Rebalance

1.前置知识掌握 

意义:

再均衡是指分区的所属权从一个消费者转移到另一消费者的行为,它为消费组具备高可用性和伸缩性提供保障,使我们可以既方便又安全地删除消费组内的消费者或往消费组内添加消费者。

重平衡过程是如何通知到其他消费者实例的:

靠消费者端的心跳线程(Heartbeat Thread)

当协调者决定开启新一轮重平衡后,它会将“REBALANCE_IN_PROGRESS”封装进心跳请求的响应中,发还给消费者实例。

当消费者实例发现心跳响应中包含了“REBALANCE_IN_PROGRESS”,就能立马知道重平衡又开始了,这就是重平衡的通知机制。

1.1.相关组件整体介绍 

__consumer_offset主题 

消费者提交的位移offset,就是保存在Kafka的内部主题__consumer_offset中的。

这个主题topic的副本因子受offsets.topic.replication.factor参数约束,这个参数的默认值为3。

这个主题的分区数可以通过offsets.topic.num.partitions参数设置,默认为50。

GroupCoordinator消费组协调器

是 Kafka 服务端中用于管理消费组的组件。

一个消费组对应一个GroupCoordinator

问:

GroupCoordinator到底在kafka server的哪台机器上呢?

答:

既然 __consumer_offset是个主题,有分区,有副本,那它每个分区的副本中就有一个是leader副本。

比如消费组1提交的的偏移量offset是保存到__consumer_offset的分区1上面的,那么__consumer_offset的分区1的leader副本所在的broker,就是消费组1对应的GroupCoordinator所在的broker

问:

消费组1提交的的偏移量offset是保存到__consumer_offset的哪个分区上面的是怎么决定的呢?

答:

Utils.abs(groupId.hashCode)%groupMetadataTopicPartitionCount

groupMetadataTopicPartitionCount是__consumer_offset主题的分区数,默认是50.

也就是说用groudId取哈希码,对__consumer_offset主题的分区数取余,就是这个消费组提交的偏移量offset保存的地方

ConsumerCoordinator 消费者协调器

是消费者客户端中的组件负责与 GroupCoordinator 进行交互。

主要作用:在再均衡的第三阶段SYNC_GROUP中,消费者leader根据已选举出来的分区分配策略来试试具体的分区分配,通过GroupCoordinator把分配方案同步给消费组内各个消费者

1.2.消费者组状态机

状态含义
Empty组内没有任何成员,但消费者组可能存在已提交的位移数据,且这些位移尚未过期
Deal

同样是组内没有任何成员,但组的元数据信息已经在协调者端被移除。

协调者组件保存着当前向它注册过的所有组信息,所谓的元数据信息就类似于这个注册信息

PreparingRebalance消费者组准备开启再均衡,此时所有成员都要重新请求加入消费者组
CompletingRebalance

消费者组下所有成员已经加入,各个成员正在等待分配方案。

该状态在老版本中被称为AwaitingSync,它和CompletingRebalance一个意思

Stable

消费者组的稳定状态。

该状态表示再均衡已完成,组内各个成员能够正常消费数据了。

1.3. 消费者组状态机中状态的扭转

1.4.再均衡的缺点:

1.在再均衡发生期间,消费组内的消费者是无法读取消息的。

2.Rebalance 很慢。如果一个消费者组里面有几百个 Consumer 实例,Rebalance 一次要几个小时。

3.在进行再均衡的时候消费者当前的状态也会丢失。比如消费者消费完某个分区中的一部分消息时还没有来得及提交消费位移就发生了再均衡操作,之后这个分区又被分配给了消费组内的另一个消费者,原来被消费完的那部分消息又被重新消费一遍,也就是发生了重复消费。

2.触发再均衡的场景Rebalance

2.1.消费者组内成员数量发生变化

当 Consumer Group 完成 Rebalance 之后,每个 Consumer 实例都会定期地向 Coordinator 发送心跳请求,表明它还存活着。如果某个 Consumer 实例不能及时地发送这些心跳请求,Coordinator 就会认为该 Consumer 已经“死”了,从而将其从 Group 中移除,然后开启新一轮 Rebalance。

Consumer端可以设置session.timeout.ms,默认是10s,表示如果 Coordinator 在 10 秒之内没有收到 Group 下某 Consumer 实例的心跳,它就会认为这个 Consumer 实例已经挂了。

Consumer端还可以设置heartbeat.interval.ms,表示发送心跳请求的频率。

以及max.poll.interval.ms 参数,它限定了 Consumer 端应用程序两次调用 poll 方法的最大时间间隔。它的默认值是 5 分钟,表示你的 Consumer 程序如果在 5 分钟之内无法消费完 poll 方法返回的消息,那么 Consumer 会主动发起“离开组”的请求,Coordinator 也会开启新一轮 Rebalance。

所以知道了上面几个参数后,我们就可以避免以下几个问题:

1.非必要 Rebalance 是因为未能及时发送心跳,导致 Consumer 被“踢出”Group 而引发的。

所以我们在生产环境中要合理配置这两个参数:
session.timeout.ms =
heartbeat.interval.ms = 

2.必要 Rebalance 是 Consumer 消费时间过长导致的。如何消费任务时间达到8分钟,而max.poll.interval.ms设置为5分钟,那么也会发生Rebalance,所以如果有比较重的任务的话,可以适当调整这个参数。

3.Consumer 端的频繁的 Full GC导致的长时间停顿,从而引发了 Rebalance。

2.2.订阅主题数量发生变化

2.3.订阅主题的分区数发生变化

3.再平衡全流程

第一阶段(FIND_COORDINATOR)寻找GroupCoordinator

目的:

1.消费者确定它所属的消费组对应的 GroupCoordinator 所在的 broker

2.创建与该 broker 相互通信的网络连接

过程:

1.如果消费者已经保存了与消费组对应的 GroupCoordinator 节点的信息,并且与它之间的网络连接是正常的,那么就可以进入第二阶段。

2.否则需要向kafka server集群中负载最小的节点的发送 FindCoordinatorRequest 请求来查找对应的 GroupCoordinator。

第二阶段(JOIN_GROUP)加入消费组

目的:

1.选举消费组的leader,即是ConsumerCoordinator

GroupCoordinator 需要为消费组内的消费组选举出一个消费组的leader

如果消费组内还没有 leader,那么第一个加入消费组的消费者即为消费组的 leader。

如果某一时刻 leader 消费者由于某些原因退出了消费组,那么会重新选举一个新的 leader,重新选择其实也很随机。

2.选举分区分配策略

1.收集各个消费者支持的所有分配策略,组成候选集 candidates。

2.每个消费者从候选集 candidates 中找出第一个自身支持的策略,为这个策略投上一票。

3.计算候选集中各个策略的选票数,选票数最多的策略即为当前消费组的分配策略。 

过程:

1. 消费者会向 GroupCoordinator 发送 JoinGroupRequest 请求,其中携带了各自提案的分区分配策略和订阅信息。

消费者在发送JoinGroupRequest请求之后会阻塞等待Kafka响应。

2. 服务端在收到JoinGroupRequest请求后会交由GroupCoordinator来进行处理

GroupCoordinator先对JoinGroupRequest请求合法性进行校验。

如果消费者是第一次请求加入消费组,那么JoinGroupRequest请求中的member_id为null,即没有消费者自身的唯一标识,此时GroupCoordinator负责为此消费者生成一个member_id

GroupCoordinator选举消费组leader、选举分区分配策略

3.GroupCoordinator发送JoinGroupResponse响应给各个消费者

leader消费者和普通消费者收到的响应不同

 

第三阶段(SYNC_GROUP)同步

目的:

leader 消费者根据在第二阶段中选举出来的分区分配策略来实施具体的分区分配,在此之后需要将分配的方案通过 GroupCoordinator 这个“中间人”来负责转发同步给组内其他消费者。

过程:

1.每个消费者发送SyncGroupRequest请求给GroupCoordinator

其中,只有Leader发送的SyncGroupRequest请求中的参数带有最后的分区分配方案

2.服务端在收到消费者发送的SyncGroupRequest请求后会交由GroupCoordinator来负责具体的逻辑处理。

GroupCoordinator会先对SyncGroupRequest请求做合法性校验

然后将从leader消费者发送过来的分配方案提取出来,连同整个消费组的元数据一起存入Kafka的__consumer_offsets主题

最后发送SyncGroupResponse响应给各个消费者以提供各个消费者所属的分配方案

3.当消费组收到SyncGroupResponse响应知道自己所属的分配方案后会调用PartitionAssignor中的onAssignment()方法

随后再调用ConsumerRebalanceListener中的OnPartitionAssigned()方法


在这里插入图片描述

第四阶段(HEARTBEAT)心跳

进入这个阶段之后,消费组中的所有消费者就会处于正常工作状态。

在正式消费之前,消费者还需要确定拉取消息的起始位置。

假设之前已经将最后的消费位移提交到了 GroupCoordinator,并且 GroupCoordinator 将其保存到了 Kafka 内部的 __consumer_offsets 主题中,此时消费者可以通过 OffsetFetchRequest 请求获取上次提交的消费位移并从此处继续消费。

消费者通过向 GroupCoordinator 发送心跳来维持它们与消费组的从属关系,以及它们对分区的所有权关系。

只要消费者以正常的时间间隔发送心跳,就被认为是活跃的,说明它还在读取分区中的消息。

心跳线程是一个独立的线程,可以在轮询消息的空档发送心跳。

如果消费者停止发送心跳的时间足够长,则整个会话就被判定为过期,GroupCoordinator 也会认为这个消费者已经死亡,就会触发一次再均衡行为。

4.再均衡监听器

再均衡执行的过程:

1.再均衡操作通知到消费者群组成员

2.消费者成员执行下一次poll()方法才会接收到再均衡操作通知信息

poll()不会再拉取数据而是触发再均衡监听器onPartitionsRevoked方法

(注意:如果上一次拉取1000条数据,消费到500条,有再均衡操作通知,此时当前这个消费者对这个通知是没有感知的,当剩下500条数据都执行结束,调用poll()拉取下一批数据时才会感知到再均衡通知,此时不会执行拉取动作,而是触发再均衡监听器)

先执行完onPartitionsRevoked方法先进入再均衡等待状态,此时不会接收消息,待所有消费者成员都进入再均衡等待(消费者间有一个互相等待机制)

3.Broker执行具体再均衡操作

4.Broker将再均衡分配结果发送到所有群组成员,触发onPartitionsAssigned方法

5.onPartitionsAssigned方法执行结束后,每个消费者对应的主题、分区信息可能会发生变化,再拉取数据就会以新分配的主题、分区信息拉取

(此时消费者群组成员没有互相等待机制,谁先执行完,谁先进入正常的拉取数据,消费数据操作)

在业务操作中就可以利用这两个方法来实现再均衡前做分区、偏移量持久化逻辑,以及再均衡后从指定偏移量开始读取消息数据操作。 

实现ConsumerRebalanceListener接口

public class SelfRebalance implements ConsumerRebalanceListener {

    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> collection) {
        //将最终缓存或者持久化的偏移量提交给Kafka服务
    }
    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> collection) {
        //通过collection可以获取再均衡后,此消费者被的主题和分区
        //根据分配到的主题和分区,到数据库或者缓存内拿取再均衡前对应的偏移量
        //根据获取的分区和偏移量信息,让消费者从该偏移量开始消费,使用Consumer的seek(……)方法
    }
}

1.onPartitionsRevoked可以做的事情远不止持久化偏移量、提交最终偏移量到Kafka这些,可以根据自身的业务做适当的调整。

2.再均衡结束后,会将新分配的主题、分区信息放到onPartitionsAssigned方法的参数上,可以根据被分配得的主题、分区信息到缓存或者持久化信息中找到对应的偏移量信息,然后让consumer从特定的位置开始消费

五、提交偏移量

比如消费到了offset,提交的时候是提交的offset+1

偏移量是存储到kafka的特殊主题__consumer_offsets

另外还有一个特殊主题_transaction_state用来存储事务日志消息

1.自动提交

自动提交配置参数enable.auto.commit,默认值是true,表示默认支持自动提交

如果需要指定提交间隔时间,就需要配置auto.commit.interval.ms参数,默认是5秒。

消费者使用poll()方法轮询去Kafka中拉取消息数据,同时消费者会有一个异步的线程在定时的向Kafka提交消费者的偏移量,提交的偏移量是poll()最后一次拉取的偏移量。

自动提交丢数据场景

消费者A,第一次poll了100条,刚好第一次提交偏移量也是100+1(5秒提交一次),但是拉取的这100条才处理了50条,A就挂了,相当于有50条已经拉取了的,已经提交了偏移量了的,还没处理。发生了消费再平衡,由B来接着消费这个分区,B从101开始消费,那么相当于51-101的数据丢失了

自动提交重复消费场景

消费者A,第一次poll了100条,刚好第一次提交偏移量也是100+1(5秒提交一次),在后面的3秒中,消费者A又poll了2次,每次100条,相当于此时消费者A已经消费到了偏移量300了,此时才过3秒,还没有到下一次触发自动提交的时间。这是消费者A挂了,发生了消费再平衡,由B来接着消费这个分区,那B就是从101偏移量开始消费,那么101-300都被重复消费了。

2.手动提交

使用手动提交的时候,需要将自动提交关闭,把enable.auto.commit值设备为false即可

同步提交commitSync()

public void commitSync();
public void commitSync(Duration timeout);
public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets);
public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets, final Duration timeout);

commitSync()方法会根据poll()方法拉取得最新位移来进行提交,只要没有发生不可恢复得错误(UnrecoverableError),它就会阻塞消费者线程直至位移提交完成(或者阻塞至timeout)

对于不可恢复错误,比如CommitFailedException、WakeupException、InterruptException、AuthenticationException、AuthorizationException等,我们可以将其捕获并坐针对性处理

如果每消费一条消息提交一次,可以防止重复消费问题,但是会对性能造成巨大的消耗,如果等消费一批(比如消费20条)提交一次,这样同样会造成重复消费问题。

异步提交commitAsync()

public void commitAsync();

public void commitAsync(OffsetCommitCallback callback);

public void commitAsync(final Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback);

异步提交调用commitAsync()方法,不阻塞,异步执行提交操作,但是还是存在问题。

如现在第一次提交偏移量是2000没有成功,会进行重新发送,在重新发送之前又发生了一次异步提交操作,此时是成功的,提交的偏移量是2100,此时上一个重新发送的提交也成功了,把最新的偏移量重置成了2000,如果在这个时刻发生了再均衡就可能导致2000到2100中间的100条消息被重复消费。

六、消费者客户端重要配置

1.fetch.min.bytes

该参数用来配置Consumer在一次拉取请求(调用poll()方法)中能从Kafka中拉取的最小数据量,默认值为1B。

Kafka在收到Consumer的拉取请求时,如果返回给Consumer的数据量小于这个参数所配置的值,那么它就需要进行等待,直到数据量满足这个参数的配置大小。

可以适当调大这个参数的值以提高一定的吞吐量,不过也会造成额外的延迟,对于延迟敏感的应用就不可取了。

2.fetch.max.bytes

该参数用来配置Consumer在一次拉取请求(调用poll()方法)中能从Kafka中拉取的最大数据量,默认值为52428800B,也就是50MB.

该参数设定的不是绝对的最大值,如果在第一个非空分区中拉取的第一条消息大于该值,那么该消息将仍然返回,以确保消费者继续工作。

Kafka中所能接收的最大消息的大小通过服务端参数message.max.bytes来设置(对应与主题端参数max.message.bytes)

3.fetch.max.wait.ms

这个参数也和fetch.min.bytes参数有关,如果kafka仅仅参考fetch.min.bytes参数的要求,那么可能会一致阻塞等待而无法发送响应给Consumer,显然这是不合理的。

fetch.max.wait.ms这个参数用于指定Kafka的等待时间,默认值为500ms。

如果kafka中没有足够多的消息而满足不了fetch.min.bytes参数的要求,那么最终会等待500ms.

这个参数的设定和Consumer与Kafka之间的延迟也有关系,如果业务应用对应延迟敏感,那么可以适当调小这个参数。

4.max.partition.fetch.bytes

这个参数用来配置从每个分区里返回给Consumer的最大数据,默认值为1048576B,即1MB.

这个参数与fetch.max.bytes参数类似,只不过前者用来限制一次拉去中每个分区的消息大小,而后者用来限制一次拉取中整体消息的大小。

同样这个参数设定的值比消息的大小要小并不会造成无法消费。

5.max.poll.records

这个参数用来配置Consumer在一次拉取请求中拉取的最大消息数,默认值为500条。

如果消息的大小都比较小,则可以适当调大这个参数值来提升一定的消费速度。

6.connections.max.idle.ms

这个参数用来指定在多久之后关闭限制的连接,默认值时540000ms,即9分钟。

7.exclude.internal.topics

Kafka中有两个内部的主题:__consumer_offsets和__transaction_state。

这个参数是用来指定Kafka中的内部主题是否可以向消费者公开,默认值为true。

如果设置为true那么只能使用subscribe(Collection)的方式而不能使用subscribe(Pattern)的方式来订阅内部主题,设置为false则没有这个限制。

8.receive.buffer.bytes

这个参数用来设置Socket接收消息缓冲区(SO_RECBUF)的大小。默认值是65536B,即64KB.

如果设置为-1,则使用操作系统的默认值。

如果Consumer与Kafka处于不同的机房则可以适当调大这个参数值

9.send.buffer.bytes

这个参数用来设置Socket发送消息缓冲区(SO_SNDBUF)的大小。默认值是131072B,即128KB.

如果设置为-1,则使用操作系统的默认值。

10.request.timeout.ms

这个参数用来配置Consumer等待请求响应的最长时间,默认值为30000ms

11.metadata.max.age.ms

这个参数用来配置元数据的过期时间,默认值是300000ms,即5分钟。

如果元数据在这个参数所限定的时间范围内没有进行更行,就会被强制更新。

12.reconnect.backoff.ms

这个参数用来配置尝试重新连接指定主机之前的等待时间(也称为退避时间),避免频繁的连接主机,默认为50ms。

这种机制适用于消费者向broker发送的所有请求。

13.retry.backoff.ms

这个参数用来配置尝试重新发送失败的请求到指定的主题分区之前的等待(退避)时间,避免在某些故障情况下频繁地重复发送,默认为100ms

14.isolation.level

这个参数用来配置消费者的事务隔离级别

字符串类型,有效值为”read_uncommitted“和"read_committed",表示消费者所消费到的位置。

如果设置为”read_committed“那么消费者就会忽略事务未提交的消息,即只能消费到LSO(LastStableOffset)的位置。

默认情况下为”read_uncommitted“,即可以消费到HW(High Watermark)处的位置。

15.其他重要配置参数

参数名称默认值参数意思
bootstrap.servers" "指定连接Kafka集群所需要的broker地址清单
key.deserializer 消息中key所对应的反序列化类,需要实现org.apache.kafka.common.serialization.Deserializer接口
value.deserializer 消息中value所对应的反序列化类,需要实现org.apache.kafka.common.serialization.Deserializer接口
group.id" "此消费者所隶属的消费组的唯一标识,即消费组的名称
client.id" "消费者客户端的id
hearbeat.interval.ms3000

当使用Kafka的分组管理功能时,心跳到消费者协调器之间的预计时间。

心跳用于确保消费者的会话保持活动状态,当有新消费者加入或离开组时方便重新平衡。

该值必须比session.timeout.ms小,通常不高于1/3.

它可以调整得更低,以控制正常重新平衡得预期时间

session.timeout.ms10000组管理协议中用来检测消费者是否失效的超时时间
max.poll.interval.ms300000通过消费组管理消费者时,该配置指定拉取消息线程最长空闲时间,若超过这个时间间隔还没有发起poll操作,则消费组认为该消费者已离开了消费组,将进行再均衡操作。
auto.offset.resetlatest参数值为字符串类型,有效值为"earliest""latest""none"
enable.auto.committrueboolean类型,配置是否开启自动提交消费位移的功能,默认开启。
auto.commit.interval.ms5000当enable.auto.commit参数设置为true时才生效,表示开启自动提交消费位移功能时自动提交消费位移的时间间隔
partition.assignment.strategyorg.apache.kafka.client.consumer.RangeAssignor消费者的分区分配策略
interceptor.class""用来配置消费者客户端的拦截器

七、kafka在zookeeper上写的节点

改图来自https://blog.csdn.net/tyh1579152915/article/details/109700493?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_v2~rank_aggregation-4-109700493.pc_agg_rank_aggregation&utm_term=kafka%E6%B6%88%E8%B4%B9%E8%80%85%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%9E%B6%E6%9E%84&spm=1000.2123.3001.4430

使用GroupCoorinator和ConsumerCoordinator的好处

其中/ consumers下的sonsumer_group_N

在旧的kafka版本中有这个,每个消费者启动的时候都要在这个节点路径上注册一个监听器,消费组中的消费者一旦发生变化(包括消费再均衡),ZK通过Watcher机制通知么给消费者

这样的缺点是

1.当触发再均衡操作时,一个消费组下的所有消费者都会同时进行再均衡,而消费者之间并不知道彼此操作的结果,这样可能导致kafka工作在一个不正确的状态

2.这种严重依赖ZK集群的做法会导致羊群效应(Herd Effect)和脑裂问题(Split Brain)

而在新版客户端没有/ consumers下的sonsumer_group_N这些节点,而是采取GroupCoorinator和ConsumerCoordinator来管理

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值