Kafka消费者

消费者和消费者组

消费者群组的概念

在生产者生产消息并发送到kakfa的主体上面后,应用程序需要创建一个消费者对象,订阅主题开始接收消息并进行一系列的处理。但是由于网络等原因,当生产者发送消息的速度远远大于消费者消费消息的速度时,该怎么办呢?

我们知道,Kafka是支持多个消费者订阅同一个主题的,所以可以对消费者进行一个横向伸缩,就像多个生产者可以向相同的主题写入消息一样。那么问题又来了

  • 多个消费者该怎么管理呢
  • 会不会出现消息的重复消费或者是消息的丢失呢
  • 添加消费者或消费者突然挂掉时,会出现什么问题

Kafka 消费者从属于消费者群组。一个群组里的消费者订阅的是同一个主题,每个消费者接收主题部分分区的消息。

在这里插入图片描述
需要注意的是,这里并不是一定是消费者1接收分区0、3,消费者2接收分区1、2的消息。一般不会让消费者的数量超过分区的数量,多余的消费者只会被闲置。相反的,对于同一个主题,可以为每一个需要获取一个或多个主题全部消息的应用程序创建一个消费者群组,然后往群组里添加消费者来伸缩读取能力和处理能力,群组里的每个消费者只处理 部分消息。

分区的分配策略

按照Kafka默认的消费逻辑设定,一个分区只能被同一个消费组(ConsumerGroup)内的一个消费者消费,Kafka提供了消费者客户端参数partition.assignment.strategy用来设置消费者与订阅主题之间的分区分配策略。默认情况下,此参数的值为:org.apache.kafka.clients.consumer.RangeAssignor,即采用RangeAssignor分配策略。除此之外,Kafka中还提供了另外两种分配策略:

  • RoundRobinAssignor
  • StickyAssignor。

消费者客户端参数partition.asssignment.strategy可以配置多个分配策略,彼此之间以逗号分隔。

RangeAssignor策略的原理是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。对于每一个topic,RangeAssignor策略会将消费组内所有订阅这个topic的消费者按照名称的字典序排序,然后为每个消费者划分固定的分区范围,如果不够平均分配,那么字典序靠前的消费者会被多分配一个分区。

假设n=分区数/消费者数量,m=分区数%消费者数量,那么前m个消费者每个分配n+1个分区,后面的(消费者数量-m)个消费者每个分配n个分区。

分区再均衡

分区的再均衡指的是原本分配给消费者组的分区,由于个别消费者的加入或者离开,为了避免造成有的消费者分配大量分区而有的消费者闲置的情况,从而引发的重新分配分区的行为。

  • 一个消费者新加入群组时,它读取的是原本其他消费者读取的消息
  • 一个消费者被关闭或者发生崩溃,它就离开了群组,它原本读取的分区将会由群组里其他的消费者读取

再均衡非常重要, 它为消费者群组带来了高可用性和伸缩性(我们可以放心地添加或移除消费者),不过在正常情况下,我们并不希望发生这样的行为。在再均衡期间,消费者无陆读取消息,造成整个群组小段时间的不可用。另外,当分区被重新分配给另一个消费者时,消费者当前的读取状态会丢失,它有可能还需要去刷新缓存,在它重新恢复状态之前会拖慢用程序。也就是说,我们要尽量避免不必要的再均衡

消费者通过向被指派为群组协调器的 broker (不同的群组可以有不同的协调器)发送心跳来维持它们和群组的从属关系以及它们对分区的所有权关系。只要消费者以正常的时间间隔发送心跳,就被认为是活跃的,说明它还在读取分区里的消息。消费者会在轮询消息(为了获取消息)或提交偏移量时发送心跳。如果消费者停止发送心跳的时间足够长,会话就会过期,群组协调器认为它已经死亡,就会触发一次再均衡。

如果一个消费者发生崩愤,井停止读取消息,群组协调器会等待几秒钟,确认它死亡了才触发再均衡。在这几秒钟时间里,死掉的消费者不会读取分区里的消息。在清理消费者时,消费者会通知协调器它将要离开群组,协调器会立即触发一次再均衡,尽量降低处理停顿。

消费者消费数据

创建kafka消费者

消费者的创建需要配置三个必要的属性,

  1. bootstrap.servers 指定kafka集群的连接地址信息
  2. key.deserializer 指定key的反序列化方式,使用指定的类把字节数组转成Java对象
  3. value.deserializer 与2相同,指定value的反序列化方式
//创建kafka消费者
Properties props = new Properties();

props.put( "bootstrap.servers","broker1:9092,broker2:9092");
props.put("group.id", "CountryCounter");
props.put( "key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
props.put( "value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");

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

订阅主题

consumer.subscribe(Collections.singletonList("custolleCounties"));

可以订阅一个主题,或者多个主题,更或者是通过正则订阅所有匹配的主题

获取数据

try{
	ConsumerRecords<String String> recods = consumer.poll(100);
}finally{
	consumer.close();
}

消费者通过poll() 方法轮询获取分区里的数据,传给poll() 方法的参数是一个超时时间,用于控制poll() 方法的阻塞时间(在消费者的冲区里没有可用数据时会发生阻塞)。如果该参数被设为 0, poll()方法会立即返回 ,否则它会在指定的毫秒数内一直等待 broker 返回数据。 poll() 方能返回一个记录列表。每条记录都包含了记录所属主题的信息记录在分区的信息记录在分区里的偏移量以及记录的键值对。我们一般会遍历这个列表 ,逐条处理这些记录。

在第一次调用新消费者的 poll ()方法时,它会负责查找GroupCoordinator(分区管理者,即群主) 然后加入群组,接受分配的分区。 如果发生了再均衡,整个过程也在轮询期间进行 。当然 心跳也是从轮询里发送出去的。所以我们要确保在轮询期间所做的任何处理工作都应该尽快完成。

在退出应用程序之前 使用close()方怯关闭消费者。网络连接和 socket 也会随之关闭,立即触发一次再均衡,而不是等待群组协调器发现它不再发送心跳并认定它已死亡,因为那样需要更长的时间,导致整个群组在一段时间内无法读取消息。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值