1 整体流程
2 消费者启动
2.1 容器启动
2.1.1 consumer启动
1)遍历每一个带有@KafkaListener注解的类或方法,经过一系列解析最终为其创建一个MessageListenerContainer,具体实现类为ConcurrentMessageListenerContainer
KafkaListenerEndpointRegistry#registerListenerContainer:
2)ConcurrentMessageListenerContainer创建完成之后,自动调用其start方法,start方法会根据我们在@KafkaListener中设置的topic和分片数来创建对应数量的KafkaMessageListenerContainer,并调用其start方法
KafkaListenerEndpointRegistry#start
AbstractMessageListenerContainer#start--->ConcurrentMessageListenerContainer#doStart
3)KafkaMessageListenerContainer.start()方法会创建一个ListenerConsumer,ListenerConsumer是一个Runnable接口实现类
AbstractMessageListenerContainer#start--->KafkaMessageListenerContainer#doStart
4)ListenerConsumer在构造的时候会创建一个Consumer,并分配对应的topic和partitions,然后执行一个while循环,在循环中不停的执行consumer.poll()方法拉取数据,并回调@KafkaListener对应的方法
5)提交到异步线程池,执行run方法
2.2 并发消费
spring-kafka可以创建单个消费者,而且还支持创建多个消费者:
ConcurrentMessageListenerContainer可以创建多个消费者,可以通过并发数属性来设置创建多少个消费者,ConcurrentMessageListenerContainer作用其实和部署多个消费者服务是一样的效果。支持创建两种类型消费者:消费者群组类型和独立消费者类。
关于ConcurrentMessageListenerContainer的并发数说明
这个并发数是指一个app实例创建多少个消费者。如果我们部署多个服务,比如10台服务,而分区个数时20,此时可以设置并发数是2,如果分区个数小于10,此时设置并发是1就可以了。
一个ip,对应三个consumer,一个consumer一个分片
注意:消费消息时使用多线程消费消息, 这种多线程处理可以在自己实现消费消息逻辑中自己实现。
3 消费者动作
3.1 指定分片消费
可以通过手动指定消费者固定消费哪一个分片。目前大多没有用到
3.2 消费进度提交(ACK机制)
1)这里while循环每次都判断是否auto commit,如果不是则processCommits
2)如果不是isManualImmediateAck,则每次是累加到offsets的map中
3)非TIME类型或者数据量到达即提交数量,即RECORD,BATCH
MANUAL,都走这里,或者COUNT/COUNT_TIME且数量达到
4)TIME时间到达
5)COUNT_TIME时间到达或者数量到达满足一种,前面已经处理数据量到达的情况
6)提交进度
这里会从offsets的map组装出commits,然后去提交(commitSync或者commitAsync),然后clear掉offsets
3.3 手动/自动提交
1)调用listener onmessage方法后,缓存待提交的消费记录
MANUAL MANUAL_IMMEDIATE类型的且非自动提交的,不会缓存,listener自己处理提交
2)处理缓存待提交的消费记录
run方法proceesCommit方法中,先处理缓存中的待提交记录,累加到offset中
累加到offset中后,交给后面的方法处理提交
3.4 重定位
暂时没有用到,重新定位分片的拉取偏移
4 消息拉取
spring-kafka拉取消息调用的原始的kafkaconsumer
4.1 拉取进度
consumer实例订阅的每个topic-partition都会有一个对应的TopicPartitionState对象,在这个对象中会记录上面内容,最需要关注的就是position这个属性,它表示上一次消费的位置
4.2 初始拉取偏移
1)首先查看当前TopicPartition的position是否为空,如果不为空,表示知道下次fetch position(拉取数据从哪个位置开始拉取),但是第一次消费这个TopicPartitionState.position肯定为空。
2)第一次position肯定为空,则通过各种方法,一定要获取到这个偏移。
4.3 更新拉取偏移
1)消息拉取回来后立即更新拉取偏移
不论消息是否被消费或消费正常
2)未提交消费进度的消息
除非消费者重启,或者发生重平衡,队列分给新的消费者,新消费者拉取到broker端的最近提交偏移,可以把未提交消费进度的那条消息拉取到重复消费
5 Listener类型
5.1 ACKNOWLEDGING_CONSUMER_AWARE
同时支持ACKNOWLEDGING和CONSUMER_AWARE两种类型。spring-kafka默认用这种KafkaListenerEndpoint#createMessageListenerInstance:listener endpoint的基本模型的接口,包括id,分组,主题,分区等的信息。
5.2 CONSUMER_AWARE
如果我们在消费消息时,需要用到consumer对象,则需要使用这个类型。
5.3 ACKNOWLEDGING
当需要手动提交时时,而不是自动提交或者spring-kafka自己实现提交的方式时,需要如下接口中acknowledment的acknowlegge()方法来提交偏移量
5.4 SIMPLE
就是在处理消息时,不需要考虑提交偏移量和使用Consumer对象。
5.5 决定哪种listenerType
上面说过,spring-kafka把listener封装成AcknowledgingConsumerAwareMessageListener,
所以默认用到是ACKNOWLEDGING_CONSUMER_AWARE
5.6 使用ListenerType
6 重平衡
6.1 消费端的消费者组成员变化
基本上影响最大的就是这个原因了
1)消费者处理消息超时, 即如果消费者处理消费的消息的时间超过了 Kafka集群配置的max.poll.interval.ms的值, 那么该消费者将会自动离组。
max.poll.interval.ms参数用于指定consumer两次poll的最大时间间隔(默认5分钟),如果超过了该间隔consumer client会主动向coordinator发起LeaveGroup请求,触发rebalance;然后consumer重新发送JoinGroup请求
2)心跳超时, 如果消费者在指定的session.timeout.ms时间内没有汇报心跳, 那么Kafka就会认为该消费已经dead了
3)新Consumer加入,旧consumer下线
4)重启consumer,长时间没有启动,超过心跳时间
6.2 订阅Topic变化
1)消费者订阅的topic发生变化
2)topic分区数目发生调整
6.3 JVM的影响
3)broker端所在的服务器发生GC