【深入理解Kafka系列】第三章 消费者

应用程序通过KafkaConsumer订阅主题,从订阅的主题中拉取消息。

1、消费者与消费组

        消费者( Consumer )负责订阅 Kafka 中的主题( Topic ),并且从订阅的主题上拉取消息。与其他一些消息中间件不同的是:在 Kafka 的消费理念中还有一层消费组( Consumer Group)的概念,每个消费者都有一个对应的消费组。当消息发布到主题后,只会被投递给订阅它的每个消费组中的一个消费者 。

        如图3-1;某个主题中共有 4 个分区( Partition) : P0 、 P1 、 P2 、 P3 。 有两个消费组 A和 B 都订阅了这个主题,消费组 A 中有 4 个消费者 ( C0 、 C1 、 C2 和 C3 ),消费组 B 中有 2个消费者 C4 和 C5 ) 。按照 Kafka 默认的规则,最后的分配结果是消费组 A 中的每一个消费者分配到 1个分区,消费组 B 中的每一个消费者分配到 2 个分区,两个消费组之间互不影响。每个消费者只能消费所分配到的分区中的消息。换言之,每一个分区只能被一个消费组中的一个消费者所消费 。

       我们再来看一下消费组内的消费者个数变化时所对应的分区分配的演变。假设目前某消费
组内只有一个消费者 C0 ,订阅了一个主题,这个主题包含 7 个分区: P0 、 P1、 P2 、 P3 、 P4 、P5 、 P6 , 也就是说,这个消费者 C0 订阅了 7 个分区,具体分配情形参考图 3 -2 。

      此时消费组内又加入了一个新的消费者 C1 ,按照既定的逻辑,需要将原来消费者C0的部分分区分配给消费者 C1消费 , 如图 3-3 所示 。 消费者 C0 和 C 1 各自负责消费所分配到的分区 ,彼此之间并无逻辑上的干扰 。

      消费者与消费组这种模型可以让整体的消费能力具备横向伸缩性,我们可以增加(或减少)
消费者的个数来提高 (或降低〕整体的消费能力 。 对于分区数固定的情况, 一昧地增加消费者
并不会让消费能力一直得到提升,如果消费者过多,出现了消费者的个数大于分区个数的情况,
就会有消费者分配不到任何分区 。 

传统的消息引擎处理模型主要有两种,队列模型,和发布-订阅模型

  • 队列模型:早期消息处理引擎就是按照队列模型设计的,所谓队列模型,跟队列数据结构类似,生产者产生消息,就是入队,消费者接收消息就是出队,并删除队列中数据,消息只能被消费一次。 但这种模型有一个问题,那就是只能由一个消费者消费,无法直接让多个消费者消费数据。基于这个缺陷,后面又演化出发布-订阅模型。
  • 发布-订阅模型: 发布订阅模型中,多了一个主题。消费者会预先订阅主题,生产者写入消息到主题中,只有订阅了该主题的消费者才能获取到消息。这样一来就可以让多个消费者消费数据。

        以往的消息处理引擎大多只支持其中一种模型,但借助kafka的消费者组机制,可以同时实现这两种模型。同时还能够对消费组进行动态扩容,让消费变得易于伸缩。

        消费组是一个逻辑上的概念,它将旗下的消费者归为一类,每一个消费者只隶属于一个消费组。每一个消费组都会有一个固定的名称,消费者在进行消费前需要指定其所属消费组的名称,这个可以通过消费者客户端参数 group.id 来配置,默认值为空字符串。

2、客户端开发

一个正常的消费逻辑需要具有如下几个步骤:

(1)配置消费者客户端参数及创建相应的消费者实例

(2)订阅主题

(3)拉取消息并消费

(4)提交消费位移

(5)关闭消费者实例

2.1、必要的参数配置

创建消费者实例之前需要做相应的参数配置,Kafka消费者客户端KafkaConsumer有4个参数是必填的:

  • bootstrap.servers :该参数的释义和生产者客户端 KafkaProducer 中 的相同,用来指定连接 Kafka 集群所需的 broker 地址清单,具体内容形式为host1:port1,host2 : port2 ,可以设置一个或多个地址,中间用逗号隔开,这里设置两个以上的 broker 地址信息,当其中任意一个看机时,消费者仍然可以连接到 Kafka 集群上。
  • group.id :消费者隶属的消费组的名称 , 默认值为“”。 如果设置为空,则会报出异
    常 : Exception in thread "main” org.apache.kafka.common.errors.InvalidGroupldException:
    The configured groupld is invalid 。 一般而言,这个参数需要设置成具有一定的业务意义
    的名称 。
  • key.deserializer 和 value.deserializer :与生产者客户端 KafkaProducer中的 key.serializer 和 value.serializer 参数对应。消费者从 broker 端获取的消息格式都是字节数组( byte[])类型,所以需要执行相应的反序列化操作才能还原成原有的对象格式 。

2.2、订阅主题与分区

        一个消费者可以订阅一个或多个主题。使用subscribe()方法,以集合的形式或者是正则的形式订阅特定的主题。subscribe()重载方法如下:

public void subscribe(Collection<String> topics ,ConsumerRebalanceListener listener)
public void subscribe(Collection<String> topics)
public void subscribe (Pattern pattern , ConsumerRebalanceListener listener)
public void subscribe (Pattern pattern)

        消费者不仅可以订阅主题,还可以直接订阅主题的特定分区,KafkaConsumer提供了一个assign()方法实现这功能:

public void assign(Collection<TopicPartition> partitions)

        TopicPartition 类只有 2 个属性 : topic 和 partition ,分别代表分区所属的主题和自身
的分区编号,这个类可以和我们通常所说的主题一分区的概念映射起来。

2.3、反序列化

        与KafkaProducer的序列化器对应,可以通过实现Deserializer<T>自定义序列化器。

2.4、消息消费

        Kafka的消费是基于拉模式的。Kafka中的消息消费是一个不断轮询的过程,消费者重复调用poll()方法,返回的是订阅主题(分区)上的一组消息。

        消费者消费到的每条消息的类型为ConsumerRecord,结构如下:

public class ConsumerRecord<K, V> {
    private final String topic;
    private final int partition;
    private final long offset ;
    private final long timestamp;
    private final TimestampType timestampType;
    private final int serializedKeySize;
    private final int serializedValueSize;
    private final Headers headers;
    private final K key ;
    private final V value ;
    private volatile Long checksum;
    //省略若干方法
}

        topic 和 partition 这两个字段分别代表消息所属主题的名称和所在分区的编号;offset 表示消息在所属分区的偏移量; timestamp 表示时间戳; key 和 value 分别表示消息的键和消息的值,一般业务应用要读取的就是value 。

2.5、位移提交

        对于分区中的每条消息都有唯一的offset(偏移量),用来表示消息在分区中对应的位置。对于消费者而言,也有一个offset(位移),消费者使用offset表示消费到分区中某个消息所在的位置。

        每次调用poll()方法时,返回的是还没有被消费过的消息集,所以需要记录上一次消费时的位移,并且这个消费位移必须持久化存储。新版kafka中,消费位移存储在Kafka内部主题为 _consumer_offset 的topic中,这种把消费位移存储的动作称为"提交",消费者在消费完消息之后需要再执行消费位移的提交。

        Kafka默认的消费位移的提交方式是自动提交,由消费者客户端参数enable.auto.commit配置,默认值为true,自动提交并不是每消费一条消息就提交一次,而是定期提交,提交周期由客户端参数auto.commit.interval.ms配置,默认5秒。

        Kafka位移提交是一个难点,存在重复消费和消息丢失的问题。

        Kafka还提供了手动位移提交的方式,使得开发人员对消费位移的管理控制更灵活。开启手动提交功能的前提是enable.auto.commit配置为false。

        手动提交又可分为同步提交与异步提交,对应KafkaConsumer的commitSync()和commitAsync()两种方法。

2.6、控制或关闭消费

        Kafka提供了对消费速度进行控制的方法,某些应用场景下我们需要暂停某些分区的消费而先消费其他分区,当达到一定条件再恢复这些分区的消费。KafkaConsumer使用pause()和resume()方法分别实现暂停某些分区在拉取操作时返回数据给客户端和恢复某些分区向客户端返回数据的操作。

public void pause(Collection<TopicPartition> partitions)
public void resume(Collection<TopicPartition< partitions)

2.7、指定位移消费

        有了消费位移的持久化才使消费者在崩溃或再平衡的时候,可以让接替的消费者能根据存储的位移维持继续消费。试想,当一个新的消费组建立的时候,它根本没有可以查找的消费位移,或者消费组的一个新消费者订阅了一个新的主题,它也没有可以查找的消费位移。

        在Kafka中每当消费者查找不到所消费的消费位移时,就会根据消费者客户端参数auto.offset.rest的配置决定从何处开始消费,这个参数的三个值:

  • "latest",默认值,表示从分区末尾开始消费。
  • "earliest",表示从起始处开始消费。
  • "none",表示当出现查找不到消费位移的时候,抛出NoOffstForPartitionException。

        有些时候,我们需要可以让我们从特定的位移处开始拉取消息,KafkaConsumer中的seek()方法提供了这个功能,让我们可以追前消费或回溯消费。

Public void seek(TopicPartition partition, long offset)

2.8、再平衡

       当有新的消费者加入消费者组、已有的消费者退出消费者组或者所订阅的主题的分区发生变化,就会触发到分区的重新分配,重新分配的过程叫做再平衡(Rebalance)。在再平衡发生的这一小段时间内,消费组变得不可用。(再平衡详解

        前面讲subscribe()方法的说到了在平衡监听器ConsumerRebalanceListener,再平衡监听器可以用来设定发生再平衡动作前后的一些准备或收尾动作(比如发生再平衡前提交offset等。)

2.9、消费者拦截器

        消费者拦截器主要是消费到消息或提交消息位移时进行一些操作。需要自定义实现org.apache.kafka.clients.consumer.ConsumerInterceptor接口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值