我们继续讨论console consumer的实现原理,本篇着重探讨ZookeeperConsumerConnector的使用,即后续所有的内容都由下面这条语句而起:
val connector = Consumer.create(config)
那么问题来了?这条语句后面执行了什么呢?我们先看create方法的定义
def create(config: ConsumerConfig): ConsumerConnector = {
val consumerConnect = new ZookeeperConsumerConnector(config)
consumerConnect
}
可以看出它的全部逻辑就是创建一个ZookeeperConsumerConnector实例并调用它的构造函数。现在问题变得简单了,我们必须要弄清楚ZookeeperConsumerConnector在创建实例的时候都做了哪些事情:
1. 创建KafkaScheduler ---- 该调度器的任务是定时地提交位移到zookeeper中
2. 生成consumer.id, 格式是[group.id]_主机名-时间戳-随机UUID前8位;如果在命令行中指定了consumer id,则格式为[group.id]_[consumer id]
3. 创建连接zookeeper客户端
4. 创建ConsumerFetcherManager
5. 生成定时任务,根据auto.commit.interval.ms的配置定时地提交位移,默认是1分钟
提交位移到zookeeper就是要定期将已消费过的消息位移保存到zookeeper上,具体的逻辑也很简单,本文在这里就不赘述了。我们只关心上面步骤中的第四步——创建ConsumerFetcherManager。那么,ConsumerFetcherManager是做什么用的呢?
顾名思义,ConsumerFetcherManager就是消费者获取线程的管理器,它在内存中维护了两个映射关系:
1. 获取者线程与broker的映射,即每个broker上面都有哪些获取者线程
2. topic分区与分区消费信息的映射,这里的分区消费信息包含很多内容,比如底层的消费队列、保存到zk上的已消费位移、获取过的最大位移以及获取大小等信息。
有了这些信息,一个消费者线程管理器就可以很方便地对消费者线程进行动态地重分配。