kafka(六:消费者组和重平衡)

    Consumer Group 是 Kafka 提供的可扩展且具有容错性的消费者机制。组内可以有多个消费者,它们共享一个公共的 ID,这个 ID 被称为 Group ID,它标识唯一的一个 Consumer Group。组内的所有消费者协调在一起来消费订阅主题的所有分区。当然,每个分区只能由同一个消费者组内的一个 Consumer 实例来消费

    Consumer Group 之间彼此独立,互不影响,它们能够订阅相同的一组主题而互不干涉。如果所有实例都属于同一个 Group,那么它实现的就是消息队列模型;如果所有实例分别属于不同的 Group,那么它实现的就是发布 / 订阅模型。

    当 Consumer Group 订阅了多个主题后,组内的每个实例不要求一定要订阅主题的所有分区,它只会消费部分分区中的消息。

如何设定consumer的个数:

理想情况下,Consumer 实例的数量应该等于该 Group 订阅主题的分区总数。如果你有 3 个实例,6个分区,那么平均下来每个实例大约消费 2 个分区(6 / 3 = 2);如果设置了 8 个实例,有 2 个实例(8 – 6 = 2)将不会被分配任何分区,它们永远处于空闲状态。因此在实际使用过程中一般不推荐设置大于总分区数的 Consumer 实例。设置多余的实例只会浪费资源。

consumer是如何管理consumer offset的:

它是一组 KV 对,Key 是分区,V 对应 Consumer 消费该分区的最新位移。即 Map<TopicPartition, Long>,其中 TopicPartition 表示一个分区,而 Long 表示位移的类型。

老版本的 Consumer Group 把位移保存在 ZooKeeper 中,当 Consumer 重启后,它能自动从 ZooKeeper 中读取位移数据,从而在上次消费截止的地方继续消费。ZooKeeper 是一个分布式的协调服务框架,Kafka 重度依赖它实现各种各样的协调管理。将位移保存在 ZooKeeper 外部系统的做法,最显而易见的好处就是减少了 Kafka Broker 端的状态保存开销。将服务器节点做成无状态的,这样可以自由地扩缩容,实现伸缩性。Kafka 最开始是基于这样的考虑才将 Consumer Group 位移保存在独立于 Kafka 集群之外的框架中。但是ZooKeeper 其实并不适合进行频繁的写更新,而 Consumer Group 的位移更新却是一个非常频繁的操作。这种大吞吐量的写操作会极大地拖慢 ZooKeeper 集群的性能。

在新版本的 Consumer Group 中,采用了将位移保存在 Kafka 内部主题的方法。这个内部主题就是 __consumer_offsets。

 

Kafka 0.10.1.0 版本开始,KafkaConsumer 就变为了双线程的设计,即用户主线程和心跳线程

所谓用户主线程,就是你启动 Consumer 应用程序 main 方法的那个线程,而新引入的心跳线程(Heartbeat Thread)只负责定期给对应的 Broker 机器发送心跳请求,以标识消费者应用的存活性。引入这个心跳线程还有一个目的,那就是期望它能将心跳频率与主线程调用 KafkaConsumer.poll 方法的频率分开,从而解耦消息处理逻辑与消费者组成员存活性管理。虽然有心跳线程,但实际的消息获取逻辑依然是在用户主线程中完成的。因此可以安全地认为 KafkaConsumer 是单线程的设计。

KafkaConsumer 类不是线程安全的。所有的网络 I/O 处理都是发生在用户主线程中,因此使用过程中必须要确保线程安全,否则程序会抛出 ConcurrentModificationException 异常。

KafkaConsumer 中有个wakeup()方法,你可以在其他线程中安全地调用KafkaConsumer.wakeup()来唤醒 Consumer。

 

重平衡:

  Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 Consumer 如何达成一致,来分配订阅 Topic 的每个分区。比如某个 Group 下有 20 个 Consumer 实例,它订阅了一个具有 100 个分区的 Topic。正常情况下,Kafka 平均会为每个 Consumer 分配 5 个分区。这个分配的过程就叫 Rebalance。

Consumer Group 何时进行 Rebalance :

  1. 组成员数发生变更。比如有新的 Consumer 实例加入组或者离开组,或是有 Consumer 实例崩溃被“踢出”组。
  2. 订阅主题数发生变更。Consumer Group 可以使用正则表达式的方式订阅主题,比如 consumer.subscribe(Pattern.compile(“t.*c”)) 就表明该 Group 订阅所有以字母 t 开头、字母 c 结尾的主题。在 Consumer Group 的运行过程中,你新创建了一个满足这样条件的主题,那么该 Group 就会发生 Rebalance。
  3. 订阅主题的分区数发生变更。Kafka 当前只能允许增加一个主题的分区数。当分区数增加时,就会触发订阅该主题的所有 Group 开启 Rebalance。

Rebalance 发生时,Group 下所有的 Consumer 实例都会协调在一起共同参与。kafka的分配策略实现如何让consumer知晓消费订阅哪些分区。

Rebalance 过程对 Consumer Group 消费过程有极大的影响,类似于JVM的 stop the world,在 stop the world期间,所有应用线程都会停止工作。Rebalance 过程也和这个类似,在 Rebalance 过程中,所有 Consumer 实例都会停止消费,等待 Rebalance 完成,Rebalance 极大影响 Consumer 端 TPS。

协调者 Coordinator

 Coordinator专门为 Consumer Group 服务,负责为 Group 执行 Rebalance 以及提供位移管理和组成员管理等。Consumer 端应用程序在提交位移时,其实是向 Coordinator 所在的 Broker 提交位移。同样地 Consumer 应用启动时也是向 Coordinator 所在的 Broker 发送各种请求,然后由 Coordinator 负责执行消费者组的注册、成员管理记录等元数据管理操作。

所有 Broker 在启动时,都会创建和开启相应的 Coordinator 组件。所有 Broker 都有各自的 Coordinator 组件。Consumer Group 通过__consumer_offsets 确定为它服务的 Coordinator 在哪台 Broker 上。__consumer_offsets是新版本的消费者位移。

Kafka 为某个 Consumer Group 确定 Coordinator 所在的 Broker :

第 1 步:确定由位移主题的哪个分区来保存该 Group 数据:partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)。

第 2 步:找出该分区 Leader 副本所在的 Broker,该 Broker 即为对应的 Coordinator。

Kafka 会计算该Consumer Group 的 group.id 参数的哈希值。其次,Kafka 会计算 __consumer_offsets 的分区数,通常是 50 个分区,之后将刚才那个哈希值对分区数进行取模加求绝对值计算,即 abs(627841412 % 50) = 12。位移主题的分区12负责保存这个 Group 的数据。

Coordinator 会在什么情况下认为某个 Consumer 实例已挂从而要退组:

当 Consumer Group 完成 Rebalance 之后,每个 Consumer 实例都会定期地向 Coordinator 发送心跳请求,表明它还存活着。如果某个 Consumer 实例不能及时地发送这些心跳请求,Coordinator 就会认为该 Consumer 已经“死”了,从而将其从 Group 中移除,然后开启新一轮 Rebalance。Consumer 端有个参数,叫 session.timeout.ms,就是被用来表征此事的。该参数的默认值是 10 秒,即如果 Coordinator 在 10 秒之内没有收到 Group 下某 Consumer 实例的心跳,它就会认为这个 Consumer 实例已经挂了。session.timout.ms 决定了 Consumer 存活性的时间间隔。

Consumer 还提供了一个允许控制发送心跳请求频率的参数,heartbeat.interval.ms。这个值设置得越小,Consumer 实例发送心跳请求的频率就越高。频繁地发送心跳请求会额外消耗带宽资源,但好处是能够更加快速地知晓当前是否正在进行 Rebalance。Coordinator 通知各个 Consumer 实例开启 Rebalance 的方法,就是将 REBALANCE_NEEDED 标志封装进心跳请求的响应体中。

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

 

第一类非必要 Rebalance 是因为未能及时发送心跳,导致 Consumer 被“踢出”Group 而引发的。如下参考:

  • 设置 session.timeout.ms = 6s。
  • 设置 heartbeat.interval.ms = 2s。
  • 要保证 Consumer 实例在被判定为“dead”之前,能够发送至少 3 轮的心跳请求,即 session.timeout.ms >= 3 * heartbeat.interval.ms。

将 session.timeout.ms 设置成 6s 主要是为了让 Coordinator 能够更快地定位已经挂掉的 Consumer。

第二类 Rebalance 是 Consumer 消费时间过长导致的。举个例子,Consumer 消费数据时需要将消息处理之后写入到 MongoDB,这是一个很重的消费逻辑。MongoDB 的一丁点不稳定都会导致 Consumer 程序消费时长的增加。此时max.poll.interval.ms参数值的设置显得尤为关键。如果要避免非预期的 Rebalance,你最好将该参数值设置得大一点,比你的下游最大处理时间稍长一点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值