什么是 Rebalance? Rebalance 为什么会发生?Rebalance 的情况下 consumer 是否还能正确消费消息呢?

记得之前在一段时间密集面试的时候总会问候选人这些问题。

重平衡 Rebalance 就是让整个 Consumer Group 下的所有的 Consumer 实例久如何消费订阅主题的所有分区达成共识的过程。在 Rebalance 的过程中,所有 Consumer 实例都需要参与进来,在 Coordinator 的帮助下完成分配。所以可以很明显的回答上面的第三个问题,在 Rebalance 的时候是无法进行消费的持续消费的。就可能会造成队列的段时间阻塞。

Rebalance 为什么会发生?说一种我经常会碰到的情况

1. 我们发现线上处理能力不够了,需要向 group 中增加新的 consumer ,就会触发 Rebalance 。任何新成员的「进入」和「离开」都会触发 Rebalance。消费会停下来重新给所有  Consumer 分配对应的 partiitons.

2.我们为 topic 增加分区,比如原本一个 topic 只有 10个分区,后因为性能问题需要扩展增加到 20 个分区就会发生 Rebanlace.

 

大的情况可以分为这两种,其实第一种新成员 「进入」 和 「离开」还可以细讲一下,因为几乎百分之 90 以上的离开和进入都不是我们想要的结果。他有可能是消费超过超时时间被一脚踢出了 group 造成离开从而造成 Rebalance 。然后又因为踢出之后又去请求又意外的加入 group 从而继续引发 Rebalance 往复循环。 

我们的消费者在与 broker 进行沟通的时候都是与一个叫 Coordinator 的组件进行交互, Coodinator 是专门负责管理消费者组 加入离开和位移的组件。那么什么情况下 Coordinator 会认为某个 Consumer 实例挂了需要退组呢?

当 Rebalance 完成之后,我们的每个 Consumer 都会向 Coordinator 定时发送心跳心情,以表明客户端还活着。如果某个 Consumer 没有按照约定好的规则发送请求给 Coordinator ,Coordinator就会认为这个 Consumer 已经挂了,并将其一脚踢出 Group 然后 response 组内其他 Consumer 尽快开始 Rebalance。

Kafka broker 端有个日志叫 server.log ,在这个日志中我们可以看到 GroupCoordinator 的身影



[2019-07-17 15:20:37,266] INFO [GroupCoordinator 0]: Preparing to rebalance group answer_action with old generation 741 (__consumer_offsets-9) (kafka.coordinator.group.GroupCoordinator)
[2019-07-17 15:20:37,266] INFO [GroupCoordinator 0]: Group answer_action with generation 742 is now empty (__consumer_offsets-9) (kafka.coordinator.group.GroupCoordinator)
[2019-07-17 15:21:37,662] INFO [GroupCoordinator 0]: Preparing to rebalance group answer_action with old generation 742 (__consumer_offsets-9) (kafka.coordinator.group.GroupCoordinator)
[2019-07-17 15:22:07,663] INFO [GroupCoordinator 0]: Stabilized group answer_action generation 743 (__consumer_offsets-9) (kafka.coordinator.group.GroupCoordinator)
[2019-07-17 15:22:07,664] INFO [GroupCoordinator 0]: Assignment received from leader for group answer_action for generation 743 (kafka.coordinator.group.GroupCoordinator)
[2019-07-17 15:22:37,664] INFO [GroupCoordinator 0]: Member kafka-python-1.3.5-3feb5deb-de82-40a4-8da9-1c6ec7d1ca4f in group answer_action has failed, removing it from the group (kafka.coordinator.group.GroupCoordinator)



从第一条开始可以看到是 preparing to rebalance ,group-id 是 answer_action 

但是大家注意



[2019-07-17 15:22:37,664] INFO [GroupCoordinator 0]: Member kafka-python-1.3.5-3feb5deb-de82-40a4-8da9-1c6ec7d1ca4f in group answer_action has failed, removing it from the group (kafka.coordinator.group.GroupCoordinator)



因为 group 中其中有一个 consumer 的异常,被 coordinator 踢出了 group 。

所以平时 Kafka 出现的 Rebalance 相关的异常我们可以很容易的在日志中发现,并且可以得到一些细节。

 

Consumer 端中有一个参数



session_timeout_ms (int): The timeout used to detect failures when
    using Kafka's group management facilities. The consumer sends
    periodic heartbeats to indicate its liveness to the broker. If
    no heartbeats are received by the broker before the expiration of
    this session timeout, then the broker will remove this consumer
    from the group and initiate a rebalance. Note that the value must
    be in the allowable range as configured in the broker configuration
    by group.min.session.timeout.ms and group.max.session.timeout.ms.
    Default: 10000



Python 社区客户端 kafka-python 该参数的默认值是 10s 。

如果 Coordinator 在 10s 内没有收到 Group 下 Consumer 的实例心跳,就会认为这个 Consumer 已经挂了,就会触发 Rebalance。

除了 session.timeout.ms 还有一个控制发送心跳周期的参数 heartbeat.interval.ms



heartbeat_interval_ms (int): The expected time in milliseconds
    between heartbeats to the consumer coordinator when using
    Kafka's group management facilities. Heartbeats are used to ensure
    that the consumer's session stays active and to facilitate
    rebalancing when new consumers join or leave the group. The
    value must be set lower than session_timeout_ms, but typically
    should be set no higher than 1/3 of that value. It can be
    adjusted even lower to control the expected time for normal
    rebalances. Default: 3000



heartbeat.interval.ms 的默认周期是 3s。这里社区客户端作者也描述得非常清楚(社区客户端的文档真的很好!)应该设置这个参数小于 session_timeout_ms ,通常大家会推荐一个公式为

heartbet_inverval_ms * 3 = session_timeout_ms 

为什么要这么做?

因为 session_timeout_ms 到期如果 coordinator 没有收到心跳会认为客户端死了,如果按照上述的配置,期间客户端至少有三次时间访问到 coordinator 并且刷新过期时间。这样如果中间有 1 2 次因为网络问题没有发送成功的情况也可以一定程度避免。

心跳时间不超过 session_timeout_ms 的值,但是也不应该过长,过长可能会引起 Rebalance 的检测缓慢且失去效果。如果有一个 consumer 失效了,如果他无法再恢复我们要做的迅速让他被踢出 group 中。否则严重的话可能会引起 partitions 的数据倾斜,lag 也会越来越大。

 

除了 



session.timeout.ms
heartbeat.interval.ms



 

还有两个参数从客户端角度去控制 Rebalance 



max_poll_records (int): The maximum number of records returned in a
    single call to :meth:`~kafka.KafkaConsumer.poll`. Default: 500


max_poll_interval_ms (int): The maximum delay between invocations of
    :meth:`~kafka.KafkaConsumer.poll` when using consumer group
    management. This places an upper bound on the amount of time that
    the consumer can be idle before fetching more records. If
    :meth:`~kafka.KafkaConsumer.poll` is not called before expiration
    of this timeout, then the consumer is considered failed and the
    group will rebalance in order to reassign the partitions to another
    member. Default 300000



max_poll_records 控制我们一次从 broker 请求多少数据过来,默认是 500 条。

max_poll_inerval_ms 控制两次 poll 之间的时间间隔,如果我们请求过来的 500 条消息 300 s 都还没有消费完,没有继续调用 poll 那么 consumer 会自动向 coordinator 发出消息要求把自己踢出组内。coordinator 收到消息会开始新一轮的 Rebalance。

 

这里关于提交的时间还有一个需要被注意的地方。在社区 Kafka-Python 1.4.0 以下的版本中(broker 0.10.1 之前),心跳是跟随 poll 一起发送的。并没有启动一个 background 独立的线程去发送心跳包。这会造成一个什么问题呢?

之前如果我们拉取一批消息开始处理,他如果超过了设置的 session.timeout.ms 也就是 默认的 10s ,那么就会被触发 rebalance 。因为如果你不调用 poll 方法,你就无法发送心跳。Coodinator 无法收到心跳就会按照约定把你踢出 group 然后进行 rebalance .

Java 版本在发布了该功能之后,社区 kafka 版本从 1.4.0 之后开始支持了 background thread 处理心跳。详情可以参阅 reference。

请注意 max_poll_inerval_ms 这个参数是1.4.0版本以上的参数 1.3 的最后一个版本 1.3.5 不会有该参数的存在。所以如果使用 1.3.5 版本及其以下版本需要自己保证 单次拉取数据 的处理时间 < session_timeout_ms ,否则就会不停的触发 Rebalance 导致程序重复消费,严重可能引起死循环崩溃。

另外 1.3.5 及其以下版本中 session.timeout.ms 的默认值是 30s 并非现在版本的 10s ,时间比较长也是为了避免长时间处理引发的 Rebanlance 心跳时间还是 3s 。

 

Reference:

https://github.com/dpkp/kafka-python/releases

https://github.com/dpkp/kafka-python/pull/1266  KAFKA-3888 Use background thread to process consumer heartbeats

https://cwiki.apache.org/confluence/display/KAFKA/KIP-62%3A+Allow+consumer+to+send+heartbeats+from+a+background+thread  KIP-62: Allow consumer to send heartbeats from a background thread