大数据开发学习1.12-Kafka消费者

490 阅读8分钟

Kafka消费方式

  • pull(拉)模式:

    consumer采用从broker中主动拉取数据。

    Kafka采用这种方式。

  • push(推)模式:

    Kafka没有采用这种方式,因为由broker决定消息发送速率,很难适应所有消费者的消费速率。

    例如推送的速度是50m/s,Consumer1、Consumer2就来不及处理消息

pull模式不足之处是,如果Kafka没有数据,消费者可能会陷入循环中,一直返回空数据

Kafka消费者工作流程

Kafka消费者工作流程.png

消费者组原理

Consumer Group(CG):消费者组,由多个consumer组成。形成一个消费者组的条件,是所有消费者的groupid相同。

消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。

消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者

消费者组,最直接的理解方式就是消费者,组的概念只是消费的并行操作

因此,在之后若有组内的消费者未消费完的数据,则会交给组内其他消费者进行消费

消费者重要参数

  • bootstrap.servers

向Kafka集群建立初始连接用到的host/port列表。

  • key.deserializervalue.deserializer

指定接收消息的key和value的反序列化类型。一定要写全类名。

  • group.id

标记消费者所属的消费者组。

  • enable.auto.commit

默认值为true,消费者会自动周期性地向服务器提交偏移量。

  • auto.commit.interval.ms

如果设置了 enable.auto.commit 的值为true, 则该值定义了消费者偏移量向Kafka提交的频率,默认5s。

  • auto.offset.reset

当Kafka中没有初始偏移量或当前偏移量在服务器中不存在(如,数据被删除了),该如何处理? earliest:自动重置偏移量到最早的偏移量。 latest:默认,自动重置偏移量为最新的偏移量。 none:如果消费组原来的(previous)偏移量不存在,则向消费者抛异常。 anything:向消费者抛异常。

  • offsets.topic.num.partitions

__consumer_offsets的分区数,默认是50个分区。

  • heartbeat.interval.ms

Kafka消费者和coordinator之间的心跳时间,默认3s。 该条目的值必须小于 session.timeout.ms ,也不应该高于 session.timeout.ms 的1/3。

  • session.timeout.ms

Kafka消费者和coordinator之间连接超时时间,默认45s。超过该值,该消费者被移除,消费者组执行再平衡。

  • max.poll.interval.ms

消费者处理消息的最大时长,默认是5分钟。超过该值,该消费者被移除,消费者组执行再平衡。

  • fetch.min.bytes

默认1个字节。消费者获取服务器端一批消息最小的字节数。

  • fetch.max.wait.ms

默认500ms。如果没有从服务器端获取到一批数据的最小字节数。该时间到,仍然会返回数据。

  • fetch.max.bytes

默认Default: 52428800(50 m)。消费者获取服务器端一批消息最大的字节数。如果服务器端一批次的数据大于该值(50m)仍然可以拉取回来这批数据,因此,这不是一个绝对最大值。一批次的大小受message.max.bytes(broker config)or max.message.bytes (topic config)影响。

  • max.poll.records

一次poll拉取数据返回消息的最大条数,默认是500条。

消费者API

独立消费者案例

创建一个独立消费者,消费主题中数据

在测试类的主方法中写入以下代码

public static void main(String[] args) {

        // 1.创建消费者的配置对象
        Properties properties = new Properties();

        // 2.给消费者配置对象添加参数
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102: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<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);

        // 注册要消费的主题(可以消费多个主题)
        ArrayList<String> topics = new ArrayList<>();
        topics.add("first");
        kafkaConsumer.subscribe(topics);

        // 拉取数据打印
        while (true) {
            // 设置1s中消费一批数据
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));

            // 打印消费到的数据
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }

在IDEA中执行消费者程序

使用生产者生产消息,在IDEA中控制台观察消息

消费者组案例

将上述代码复制多分

同时运行即为消费者组

需要保证这几个消费者的组id,即group.id相同即可

分区的分配以及再平衡

问题:一个consumer group中有多个consumer组成,一个 topic有多个partition组成,那么到底由哪个consumer来消费哪个partition的数据?

Kafka有四种主流的分区分配策略: Range、RoundRobin、Sticky、CooperativeSticky。可以通过配置参数partition.assignment.strategy,修改分区的分配策略。默认策略是Range + CooperativeSticky。Kafka可以同时使用多个分区分配策略。

Range以及再平衡

Range 是对每个 topic 而言的

首先对同一个 topic 里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。

假如现在有 7 个分区,3 个消费者,排序后的分区将会是0,1,2,3,4,5,6;消费者排序完之后将会是C0,C1,C2。

由于7/3除不尽,则前面的几个消费者会多消费1个分区

最终的分区结果为3,2,2

注意:目前上述结果为一个topic的结果,就这个结果看来,这个分配并无问题,但是当消费n个topic时,第一个消费者会比其他消费者多消费n个分区的数据,这时候会导致数据倾斜!

Range分区分配再平衡

当消费者组中其中一个消费者挂掉时,其所负责的所有分区,将一次性全部分给剩下的消费者之一

这是Range方式的再平衡方式

RoundRobin以及再平衡

RoundRobin 针对集群中所有Topic而言。

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

graph TB
Partition-0-->Consumer1
Partition-1-->Consumer2
Partition-2-->Consumer3
Partition-3-->Consumer1
Partition-4-->Consumer2
Partition-5-->Consumer3
Partition-6-->Consumer1

Partition0交给Consumer1,Partition1交给Consumer2,Partition2交给Consumer3,Partition3交给Consumer1,以此类推。

RoundRobin分区分配策略

若消费者1挂掉

1号消费者的任务会按照RoundRobin的方式,把数据轮询分成0 、6和3号分区数据,分别由2号消费者或者3号消费者消费

Sticky以及再平衡

粘性分区定义:可以理解为分配的结果带有“粘性的”。即在执行一次新的分配之前,考虑上一次分配的结果,尽量少的调整分配的变动,可以节省大量的开销。 粘性分区是Kafka从0.11.x版本开始引入这种分配策略,首先会尽量均衡的放置分区到消费者上面,在出现同一消费者组内消费者出现问题的时候,会尽量保持原有分配的分区不变化

该方式,在第一次进行消费时,会随机平均分配任务到各个消费者,并且分区具有粘性

所谓粘性,简单来说就是,第一次分配的分区,一般情况下不再改变

Sticky分区分配再平衡

若消费者1挂掉

1号消费者的任务会按照粘性规则,尽可能均衡的随机分成几个分区数据,分别由2号消费者或者3号消费者消费

并且在此次平衡之后,2号和3号始终保持该分区分配方案进行消费,保持粘性

offset位移

offset的默认维护位置

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

自动提交offset

  • enable.auto.commit

默认值为true,消费者会自动周期性地向服务器提交偏移量。

  • auto.commit.interval.ms

如果设置了 enable.auto.commit 的值为true, 则该值定义了消费者偏移量向Kafka提交的频率,默认5s。

手动提交offset

手动提交offset的方法有两种:分别是commitSync(同步提交)和commitAsync(异步提交)。两者的相同点是,都会将本次提交的一批数据最高的偏移量提交;不同点是,同步提交阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而异步提交则没有失败重试机制,故有可能提交失败。

  • commitSync(同步提交):必须等待offset提交完毕,再去消费下一批数据。
  • commitAsync(异步提交) :发送完提交offset请求后,就开始消费下一批数据了。

指定Offset消费

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

当Kafka中没有初始偏移量(消费者组第一次消费)或服务器上不再存在当前偏移量时(例如该数据已被删除),该怎么办?

(1)earliest:自动将偏移量重置为最早的偏移量,类似于命令行中的--from-beginning (2)l atest(默认值):自动将偏移量重置为最新偏移量 (3)none:如果未找到消费者组的先前偏移量,则向消费者抛出异常