Kafka基本原理(四)消费者

1.消费者组概念

一个消费者属于消费者组。一个群组里的消费者订阅的是同一个主题,每个消费者接收主题的一部分分区消息。一个主题可以被多个消费群组使用,消费者群组之间互不影响。

假设一个Topic有四个分区,有两个消费者组,分配情况如下:

注意:不要让一个consumer group的消费者数量多于订阅主题的分区数,多于的消费者只会被闲置。 

2.创建消费者

消费者正常的消费逻辑,如下:

  1. 配置消费者客户端参数
  2. 创建消费者实例
  3. 订阅主题
  4. 拉取并消费消息
  5. 位移提交
  6. 关闭消费者

2.1一个简单的消费者

public static void main(String[] args) {
    //参数设置
    Properties properties = new Properties();
    properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    properties.put("bootstrap.servers", "127.0.0.1:9092");
    properties.put("group.id", "test kafka");

    KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
    //主题订阅
    consumer.subscribe(Collections.singletonList(topic));

    //消费消息
    while (true) {
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
        for (ConsumerRecord<String, String> record : records) {
            System.out.println(record.value());
        }
    }
}

 消费者的参数设置也不止上述几个,详细的内容可以参考中文官网

2.2订阅主题

  • 一个消费者可以订阅一个或多个主题。

订阅主题通过调用subscribe()方法即可:

nsumer.subscribe(Collections.singletonList("test.1"));

另外,可是使用正则表达式,订阅多个主题

consumer.subscribe("test.*");
  •  订阅主题的特定分区,kafkaConsumer提供了assign()方法实现这些功能

2.3消费消息

消费消息分长轮询、pull模式、push模式三种。

(1)Push

Push方式是Server端接收到消息后,主动把消息推送给Client端,实时性高。对于一个提供队列服务的Server来说,用Push方式主动推送消息有很多弊端:

a.首先是加大Server端的工作量,进而影响Server的性能;

b.其次,Client的处理能力各不相同,Client的状态不收Server控制,如果Client不能及时处理Server推送过来的消息,会造成各种潜在问题;

(2)Pull

Pull方式是Client端循环地从Server端拉取消息,主动权在Client手里,自己拉取到一定量的消息后,处理稳妥了再接着拉取。Pull方式的主要问题是:

a.循环拉取消息的间隔不好设定,间隔太短就处于一个"忙等"的状态,浪费资源;

b.每个Pull的时间间隔太长,Server端有消息到来时,有可能没有被技术处理。

(3)长轮询

a: push + pull 模式结合的方式,通过Client和Server端的配合,达到既有Pull的有点,又能达到保证实时性的目的。长轮询的核心是,Broker端HOLD住客户端过来的请求一小段时间,在这个时间内,有新消息到达,就利用现有的连接立刻返回消息给Consumer。长轮询的主动权还是掌握在Consumer手中,Broker即使有大量的消息积压,也不会主动推送给Consumer。

b: 长轮询的局限性是,HOLD住Consumer请求的时候需要占用资源,它适合用在消息队列这种客户端连接数可控的场景中。

2.4反序列化

和序列化相对于的是反序列化,kafka服务器的消息只有经过反序列化才能获取对应的实体。kafka反序列化方式表:

(1)如果自定义了序列化,自然是需要自定义对应反序列话工具;

(2)使用了第三方的序列化工具,反之,也是需要第三方对应的反序列化工具;

2.5位移提交

(1)自动提交

kafka默认的提交方式,客户端参数enable.auto.commit配置默认值true.。默认5s内,查询一次是需要提交。自动提交很简单,但是会存在重复消费和消息丢失的坑你。在5s内,有消费者离开,进行再平衡,就能造成重复消费的问题。

(2)手动提交

  • 同步提交

commitSync(),失败的时候回一直重试提交,直到成功

  • 异步提交

commitAysc()

  • 同步和异步组合提交
try {
    while (true) {
       ConsumerRecords<String, String> records = consumer.poll(100);
       for (ConsumerRecord<String, String> record : records) {
           System.out.printf("topic = %s, partition = %s, offset = %d,
           customer = %s, country = %s\n",
           record.topic(), record.partition(),
           record.offset(), record.key(), record.value());
       }
       //异步提交
       consumer.commitAsync();
    }
} catch (Exception e) {
    log.error("Unexpected error", e);
} finally {
    try {
        //同步提交
        consumer.commitSync();
    } finally {
        consumer.close();
    }
}

2.6再均衡监听器

一个消费者组内的 consumer 共同读取 Topic 的分区。

  1. 当一个 consumer 加入组时,读取的是原本由其他 consumer 读取的分区。
  2. 当一个 consumer 离开组时(被关闭或发生崩溃),原本由它读取的分区将由组里的其他 consumer 来读取。
  3. 当 Topic 发生变化时,比如添加了新的分区,会发生分区重分配。

分区的所有权从一个消费者转移到另一个消费者,这样的行为被称为再均衡(rebalance)。再均衡非常重要,为消费者组带来了高可用性和伸缩性,可以放心的增加或移除消费者。

再均衡期间,消费者无法读取消息,造成整个 consumer group 一小段时间的不可用。另外,当分区被重新分配给另一个消费者时,当前的读取状态会丢失。

消费者通过向作为组协调器(GroupCoordinator)的 broker(不同的组可以有不同的协调器)发送心跳来维持和群组以及分区的关系。心跳表明消费者在读取分区里的消息。消费者会在轮询消息或提交偏移量(offset)时发送心跳。如果消费者停止发送心跳的时间足够长,会话就会过期,组协调器认为消费者已经死亡,会触发一次再均衡。

在 Kafka 0.10的版本中,对心跳行为进行了修改,由一个独立的线程负责心跳。

订阅主题相关函数,存在再平衡的监听器:

 实现该监听器,需要实现oNPartitionRevoked和oNPartitonsAssiged两个接口即可,代码示例如下,

    consumer.subscribe(Arrays.asList(topic), new ConsumerRebalanceListener() {
        @Override
        public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
            consumer.commitSync(currentOffsets);
        }

        @Override
        public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
            //do nothing.
        }
    });

 2.7多线程

kafkaConsumer线程不安全,但是这并不能说明,我们只能用单线程的方式执行。

方式一:

public static void main(String[] args) {
    Properties props = initConfig();
    int consumerThreadNum = 4;
    for (int i = 0; i < consumerThreadNum; i++) {
        new KafkaConsumerThread(props, topic).start();
    }
}

public static class KafkaConsumerThread extends Thread {
    private KafkaConsumer<String, String> kafkaConsumer;

    public KafkaConsumerThread(Properties props, String topic) {
        this.kafkaConsumer = new KafkaConsumer<>(props);
        this.kafkaConsumer.subscribe(Arrays.asList(topic));
    }

    @Override
    public void run() {
        try {
            while (true) {
                ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(100));
                for (ConsumerRecord<String, String> record : records) {
                    //process record.
                    System.out.println(record.value());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            kafkaConsumer.close();
        }
    }
}

这种方式等于是开启多个Consumer消费者,优点是能保证顺序消费,缺点是每个线程都要维护一个独立的TCP链接,如果线程数比较大,网络开销比较大。

方式二:

当然就是线程池的方式。这种方式缺点是不能保证顺序消费,如果需要顺序消费,实现比较麻烦。

3.参考

Kafka消费者:从Kafka中读取数据

kafka自定义消息序列化和反序列化方式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值