文章目录
简介
本文根据kafka-1.0.0
kafka的consumer相对来说比producer复杂一些,因为他牵扯到对offset的提交,consumer group的管理等操作。
consumer使用样例
Properties props = new Properties();
props.put("bootstrap.servers", "127.0.0.1:9092");
props.put("group.id", "test_group");
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", 1000);
props.put("session.timeout.ms", 120000);
props.put("max.poll.interval.ms",600000);
props.put("max.poll.records", 100);
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
Consumer<String, String> consumer = new KafkaConsumer<>(props);
while (true) {
ConsumerRecords<String, String> records = consumer.poll(timeout);
print(records, table);
可以看到kafka的consumer使用起来也是很简单的,只能说明包装的好,里面也是包含了很多东西。
consumer 如何和server通信,
consumer在调用上面的poll()操作的时候会触发offset的提交动作,也就是只有到下一次poll()的时候本次消费的offset才会被提交(当然,这是针对使用自动提交的设置的时候),同时还会检查是否需要rebalance等操作(这个等下面讲)。
当然,consumer和server之间肯定要有一个心跳检测,来让server知道consumer还是活着的,以免consumer被从group当中踢出去。
因为consumer主线程在处理数据的时候后可能会消耗比较长的时间,所以如果使用consumer主线程来完成心跳维持的话时间不容易控制时间,很有可能因为处理数据消耗时间较长而导致超时被server端误认为死掉了。
因此,kafka在consumer端引入了一个heartbeat 线程来辅助维持consumer的心跳,这个线程只做心跳维护和一些状态的同步,比如当前group处于rebalance状态heartbeat线程再发送心跳请求时会返回正在rebalance的状态,这个时候hearbeat线程会在consumer端和consumer共享的变量设置标志位,标识正在进行rebalance,这样的话,consumer在进行下一次poll()的时候会检查这个标志位,并作出一些动作。
consumer的offset管理
每个consumer group中的consumer的因为要消费对应的数据需要记录offset,同时server端也要记录这些offset以便于consumer端在短暂重启以后还能保持继续消费。server端是如何记录这些数据的呢,在server端会有一个叫 Coordinator的组件来负责管理consumer group
__consumer_offsets
__consumer_offsets
是 Kafka 内部使用的一个 topic,专门用来存储 group 消费的情况,默认情况下有50个 partition,每个 partition 三副本。
每个consumer group 的元数据存储到那个partition有一些规则,通过这个 abs(GroupId.hashCode()) % NumPartitions 来计算出一个值,(其中,NumPartitions 是 __consumer_offsets 的 partition 数,默认是50个),这个值代表了 __consumer_offsets 的一个 partition,而这个 partition 的 leader 即为这个 Group 要交互的 GroupCoordinator 所在的节点。
下面这个是test的一个配置,可以看到topic的ReplicationFactor 设置成了1,这就导致了一旦有一个broker挂了,如果这个broker对应的partition对应的存储的有现有consuemr group的元数据,那么对应的offset元数据就丢了,也就没有办法正常消费了。
[deploy@b-kk-search-03 kafka_2.11-1.0.0]$ bin/kafka-topics.sh --zookeeper 10.9.10.17:2182 --topic __consumer_offsets --describe
Topic:__consumer_offsets PartitionCount:50 ReplicationFactor:1 Configs:segment.bytes=104857600,cleanup.policy=compact,compression.type=producer
Topic: __consumer_offsets Partition: 0 Leader: 1 Replicas: 1 Isr: 1
Topic: __consumer_offsets Partition: 1 Leader: 0 Replicas: 0 Isr: 0
Topic: __consumer_offsets Partition: 2 Leader: 1 Replicas: 1 Isr: 1
Topic: __consumer_offsets Partition: 3 Leader: 0 Replicas: 0 Isr: 0
Topic: __consumer_offsets Partition: 4 Leader: 1 Replicas: 1 Isr: 1
consumer group的rebalance
针对一个consumer group ,kafka server端的coordinator会在有新的consuemr加入或者现有的consumer 退出的时候触发rebalance,在rebalance阶段,所有现有的consumer都要重新加入这个group。重新加入分为两个阶段,1.join-group 请求以加入 group,2.然后再发送 sync-group 请求以获取被分配的partition等信息。
coordinator端接收到第一个join-group请求以后,会进入rebalance阶段,然后等待所有的consuemr(指在此之前通过heartbeat监测到的存活的)发起join-group,等待是有时间限制的,关于时间限制下面会单独讲一讲,因为他有些奇怪。等所有的consumer都发起了join-group以后,coordinator会从中选取一个consuemr作为leader来进行分配(分配策略放在consumer端主要是为了将来拓展的时候更加灵活),coordinator把所有consumer的元数据信息都发送给这个leader,由他来判断如何分配。当leader将分配结果通过sync-group发送到coordiantor的时候,coordinator会在其他的consumer的sync-group中将分配结果下发。然后就完成了rebalance。
关于coordinator在join-group的等待时间
在进入rebalance阶段的时候会等待所有之前存活的counsumer进行join-group,这个等待时间是多少,在查看kafka官方文档的时候有这样的配置。
rebalance.timeout.ms
The maximum allowed time for each worker to join the group once a rebalance has begun.
This is basically a limit on the amount of time needed for all tasks to flush any pending data
and commit offsets. If the timeout is exceeded, then the worker will be removed from
the group, which will cause offset commit failures.
default 60000
这里觉得使用的是这个值,但是我们在实际使用中发现不太对。
rebalance的等待时间达到比60s要长,实际上和max.poll.interval.ms 时间一致,后来翻看了不少源码才发现使用的就是 max.poll.interval.ms
,估计rebalance.timeout.ms
暂时还没有用到。