• spring kafka consumer原理解析一


    前言:Spring kafka 是 Spring 对 kafka API的一次封装,省去了写生产和消费的代码,而只需要加个注解即可使用 kafka 收发消息。然而 Spring 是如何实现的呢?与我们自己手写有啥不同?see ↓

    以下仅对消费者源码进行分析:

    一、消费者的创建

    老套路:查看源码首先是找到入口,无可厚非,spring kafka 的入口即为@KafkaListenner注解,因为我们在使用Spring kafka时配置一个@KafkaListnner即可消费到 kafka 的数据。

    (1)入口:KafkaListenerAnnotationBeanPostProcessor 一个实现了 BPP 的类,以下只贴出核心的几个方法 ↓

    public class KafkaListenerAnnotationBeanPostProcessor<K, V>
            implements BeanPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton {
    
        @Override
        public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
            if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
                Class<?> targetClass = AopUtils.getTargetClass(bean);
                Collection<KafkaListener> classLevelListeners = findListenerAnnotations(targetClass); // 找到所有有@KafkaListenner的方法和类
                final boolean hasClassLevelListeners = classLevelListeners.size() > 0;
                final List<Method> multiMethods = new ArrayList<Method>();
                Map<Method, Set<KafkaListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                        new MethodIntrospector.MetadataLookup<Set<KafkaListener>>() {
    
                            @Override
                            public Set<KafkaListener> inspect(Method method) {
                                Set<KafkaListener> listenerMethods = findListenerAnnotations(method);
                                return (!listenerMethods.isEmpty() ? listenerMethods : null);
                            }
    
                        });
                if (hasClassLevelListeners) {
                    Set<Method> methodsWithHandler = MethodIntrospector.selectMethods(targetClass,
                            new ReflectionUtils.MethodFilter() {
    
                                @Override
                                public boolean matches(Method method) {
                                    return AnnotationUtils.findAnnotation(method, KafkaHandler.class) != null;
                                }
    
                            });
                    multiMethods.addAll(methodsWithHandler);
                }
                if (annotatedMethods.isEmpty()) {
                    this.nonAnnotatedClasses.add(bean.getClass());
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("No @KafkaListener annotations found on bean type: " + bean.getClass());
                    }
                }
                else {
                    // Non-empty set of methods
                    for (Map.Entry<Method, Set<KafkaListener>> entry : annotatedMethods.entrySet()) {
                        Method method = entry.getKey();
                        for (KafkaListener listener : entry.getValue()) {
                            processKafkaListener(listener, method, bean, beanName);
                        }
                    }
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug(annotatedMethods.size() + " @KafkaListener methods processed on bean '"
                                + beanName + "': " + annotatedMethods);
                    }
                }
                if (hasClassLevelListeners) {
                    processMultiMethodListeners(classLevelListeners, multiMethods, bean, beanName); // 然后跟着这行代码往下面这个方法看,接下来对取到的有@KafkaListenner类和方法进行了什么处理
                }
            }
            return bean;
        }
    
        private void processMultiMethodListeners(Collection<KafkaListener> classLevelListeners, List<Method> multiMethods,
                Object bean, String beanName) {
            List<Method> checkedMethods = new ArrayList<Method>();
            for (Method method : multiMethods) {
                checkedMethods.add(checkProxy(method, bean));
            }
         // 这里会遍历拿到的 classLevelListeners 集合,即每一个 kafkaListenner, 然后创建了一个 endpoint 对象,接下来看 processListenner 方法
    for (KafkaListener classLevelListener : classLevelListeners) { MultiMethodKafkaListenerEndpoint<K, V> endpoint = new MultiMethodKafkaListenerEndpoint<K, V>(checkedMethods, bean); endpoint.setBeanFactory(this.beanFactory); processListener(endpoint, classLevelListener, bean, bean.getClass(), beanName); } } protected void processListener(MethodKafkaListenerEndpoint<?, ?> endpoint, KafkaListener kafkaListener, Object bean, Object adminTarget, String beanName) {
         // 从这个方法可以知道,spring 会把拿到的 kafkaListenner 的一些信息(topic, partitions等)封装到那个 endpoint 里面 endpoint.setBean(bean); endpoint.setMessageHandlerMethodFactory(
    this.messageHandlerMethodFactory); endpoint.setId(getEndpointId(kafkaListener)); endpoint.setTopicPartitions(resolveTopicPartitions(kafkaListener)); endpoint.setTopics(resolveTopics(kafkaListener)); endpoint.setTopicPattern(resolvePattern(kafkaListener)); String group = kafkaListener.group(); if (StringUtils.hasText(group)) { Object resolvedGroup = resolveExpression(group); if (resolvedGroup instanceof String) { endpoint.setGroup((String) resolvedGroup); } } KafkaListenerContainerFactory<?> factory = null; String containerFactoryBeanName = resolve(kafkaListener.containerFactory()); if (StringUtils.hasText(containerFactoryBeanName)) { Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name"); try { factory = this.beanFactory.getBean(containerFactoryBeanName, KafkaListenerContainerFactory.class); } catch (NoSuchBeanDefinitionException ex) { throw new BeanInitializationException("Could not register Kafka listener endpoint on [" + adminTarget + "] for bean " + beanName + ", no " + KafkaListenerContainerFactory.class.getSimpleName() + " with id '" + containerFactoryBeanName + "' was found in the application context", ex); } }      // 封装成了 endpoint 对象之后则进行注册 this.registrar.registerEndpoint(endpoint, factory); } }

    通过分析这个 BPP 可以获得的信息是:在  IOC 容器初始化的时候获取所有带有 @KafkaListenner 注解的方法或类,然后将其信息封装到 endpoint 对象中,最后则对 endpoint 对象进行注册。接下来对注册进行分析 ↓

    (2)KafkaListenerEndpointRegistrar 一个实现了 InitializingBean 的类,为啥要实现它呢?暂且埋下伏笔。

    public class KafkaListenerEndpointRegistrar implements BeanFactoryAware, InitializingBean {
    
        private final List<KafkaListenerEndpointDescriptor> endpointDescriptors = new ArrayList<>();
    
        public void registerEndpoint(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory) {
            Assert.notNull(endpoint, "Endpoint must be set");
            Assert.hasText(endpoint.getId(), "Endpoint id must be set");
            // 可以看出 这里又将 endpoint 封装到了 KafkaListenerEndpointDescriptor 对象中,然后将其注册(保存)到了endpointDescriptors (一个List)中去。
    // 于是从一开始根据 @KafkaListener 注解获取对象到注册则全部结束,是不是 consumer 创建就到此结束?当然还有一些工作,接下来就是真正核心的东西:创建 kafkaListener 容器
    KafkaListenerEndpointDescriptor descriptor = new KafkaListenerEndpointDescriptor(endpoint, factory); synchronized (this.endpointDescriptors) { if (this.startImmediately) { // Register and start immediately this.endpointRegistry.registerListenerContainer(descriptor.endpoint, resolveContainerFactory(descriptor), true); } else { this
    .endpointDescriptors.add(descriptor); } } }
    // 创建容器 start, 这就是为什么要实现 InitializingBean 接口的原因。因为在获取 @KafkaListener 的一些信息以及注册完成之后,还需要创建 KafkaListener 容器 @Override
    public void afterPropertiesSet() { registerAllEndpoints(); } protected void registerAllEndpoints() { synchronized (this.endpointDescriptors) {
          // 这里发现,spring 对每一个 kafkaListener 注册一个 ListenerContainer 容器,即有多少个 @KafkaListener 它就会注册多少个 Listener 容器 
    for (KafkaListenerEndpointDescriptor descriptor : this.endpointDescriptors) { this.endpointRegistry.registerListenerContainer( descriptor.endpoint, resolveContainerFactory(descriptor)); } this.startImmediately = true; // trigger immediate startup } } }

    从以上解析可以知道:spring 会将 @KafkaListener 注解中的信息注册到一个 list 集合中,信息注册完成之后则会进行容器的创建,有多少个 @KafkaListener 就注册多少个 Listener 容器。然而,容器到底是什么容器呢?是如何注册的?又有什么用呢?

    (3)KafkaListenerEndpointRegistry   在这个类里我们将看到 Spring 是如何进行容器注册的

    public class KafkaListenerEndpointRegistry implements DisposableBean, SmartLifecycle, ApplicationContextAware,
            ApplicationListener<ContextRefreshedEvent> {
    
      private final Map<String, MessageListenerContainer> listenerContainers = new ConcurrentHashMap<String, MessageListenerContainer>();
    
        public void registerListenerContainer(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory) {
            registerListenerContainer(endpoint, factory, false);
        }
    public void registerListenerContainer(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory, boolean startImmediately) { Assert.notNull(endpoint, "Endpoint must not be null"); Assert.notNull(factory, "Factory must not be null");      // 获取 id 即为 @KafkaListener 里的 id 作为 Map 的 key 值 String id = endpoint.getId(); Assert.hasText(id, "Endpoint id must not be empty"); synchronized (this.listenerContainers) { Assert.state(!this.listenerContainers.containsKey(id), "Another endpoint is already registered with id '" + id + "'");
           // 创建容器,这里是创建什么容器,又是如何创建的呢? MessageListenerContainer container
    = createListenerContainer(endpoint, factory);
          // 将容器注册到 listenerContainers (一个 Map)中 this.listenerContainers.put(id, container);
    if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) { List<MessageListenerContainer> containerGroup; if (this.applicationContext.containsBean(endpoint.getGroup())) { containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class); } else { containerGroup = new ArrayList<MessageListenerContainer>(); this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup); } containerGroup.add(container); }
           // 容器启动
    if (startImmediately) { startIfNecessary(container); } } } }

    单单这个类远远满足不了我们对容器注册的好奇心,这里我们只能获取到这些信息:创建容器然后将容器注册到了一个 Map 中,最后是启动容器。然而容器是什么容器又是如何创建的还未知,于是接着摸索 ↓

    (4)ConcurrentKafkaListenerContainerFactory  通过不懈的追踪,终于找到了 ‘元凶’.是的就是这个类创建了 Listener 容器。

    public class ConcurrentKafkaListenerContainerFactory<K, V>
            extends AbstractKafkaListenerContainerFactory<ConcurrentMessageListenerContainer<K, V>, K, V> {
    
      @Override
    protected ConcurrentMessageListenerContainer<K, V> createContainerInstance(KafkaListenerEndpoint endpoint) { Collection<TopicPartitionInitialOffset> topicPartitions = endpoint.getTopicPartitions(); if (!topicPartitions.isEmpty()) { ContainerProperties properties = new ContainerProperties( topicPartitions.toArray(new TopicPartitionInitialOffset[topicPartitions.size()])); return new ConcurrentMessageListenerContainer<K, V>(getConsumerFactory(), properties); } else { Collection<String> topics = endpoint.getTopics(); if (!topics.isEmpty()) { ContainerProperties properties = new ContainerProperties(topics.toArray(new String[topics.size()])); return new ConcurrentMessageListenerContainer<K, V>(getConsumerFactory(), properties); } else { ContainerProperties properties = new ContainerProperties(endpoint.getTopicPattern()); return new ConcurrentMessageListenerContainer<K, V>(getConsumerFactory(), properties); } } } }

    哎呀,原来是创建了 ConcurrentMessageListenerContainer 啊,现在终于明白了 Spring 原来是创建这个 Listener 容器。然而这个容器又是何方圣神,come on ↓

    (5)ConcurrentMessageListenerContainer  于是明白了容器的注册过程(创建 ConcurrentMessageListenerContainer  容器,然后把容器注册到 listenerContainers (一个 Map)中,最后就是启动容器)之后,就是研究容器究竟做了什么。

    // 大家留意一下 AbstractMessageListenerContainer 这个类喔,待会有惊喜
    public class ConcurrentMessageListenerContainer<K, V> extends AbstractMessageListenerContainer<K, V> {
    
       private final List<KafkaMessageListenerContainer<K, V>> containers = new ArrayList<>();
    
        private int concurrency = 1;
    
        @Override
        protected void doStart() {
            if (!isRunning()) {
                ContainerProperties containerProperties = getContainerProperties();
                TopicPartitionInitialOffset[] topicPartitions = containerProperties.getTopicPartitions();
           // 这里可以知道,分区数不能小于这个 concurrency,这个东西到底是什么呢?现在知道它是一个 int 属性而且默认值为1
    if (topicPartitions != null && this.concurrency > topicPartitions.length) { this.logger.warn("When specific partitions are provided, the concurrency must be less than or " + "equal to the number of partitions; reduced from " + this.concurrency + " to " + topicPartitions.length); this.concurrency = topicPartitions.length; } setRunning(true);        // 然后根据 concurrency 遍历创建 KafkaMessageListenerContainer,并启动这个容器,最后将其注册到 containers (一个 List)中 for (int i = 0; i < this.concurrency; i++) { KafkaMessageListenerContainer<K, V> container; if (topicPartitions == null) { container = new KafkaMessageListenerContainer<>(this.consumerFactory, containerProperties); } else { container = new KafkaMessageListenerContainer<>(this.consumerFactory, containerProperties, partitionSubset(containerProperties, i)); } if (getBeanName() != null) { container.setBeanName(getBeanName() + "-" + i); } if (getApplicationEventPublisher() != null) { container.setApplicationEventPublisher(getApplicationEventPublisher()); } container.setClientIdSuffix("-" + i); container.start(); this.containers.add(container); } } } }

    看了这里是不是有些疑问?为啥容器里面又创建容器?而且这个容器创建的个数还可以使用 concurrency 指定。那么这个 KafkaMessageListenerContainer 又是何方圣神,它和 ConcurrentMessageListenerContainer  又有什么关系呢?带着这些疑问继续追寻。

    (6)KafkaMessageListenerContainer 

    // 大家发现没,这个类也继承了 AbstractMessageListenerContainer,这一点可以知道,此类和 ConcurrentMessageListenerContainer 是来自一个老子的(兄弟关系)
    public class KafkaMessageListenerContainer<K, V> extends AbstractMessageListenerContainer<K, V> {
    
      private GenericAcknowledgingMessageListener<?> acknowledgingMessageListener;
    
        @Override
        protected void doStart() {
            if (isRunning()) {
                return;
            }
            ContainerProperties containerProperties = getContainerProperties();
    
            if (!this.consumerFactory.isAutoCommit()) {
                AckMode ackMode = containerProperties.getAckMode();
                if (ackMode.equals(AckMode.COUNT) || ackMode.equals(AckMode.COUNT_TIME)) {
                    Assert.state(containerProperties.getAckCount() > 0, "'ackCount' must be > 0");
                }
                if ((ackMode.equals(AckMode.TIME) || ackMode.equals(AckMode.COUNT_TIME))
                        && containerProperties.getAckTime() == 0) {
                    containerProperties.setAckTime(5000);
                }
            }
    
            Object messageListener = containerProperties.getMessageListener();
            Assert.state(messageListener != null, "A MessageListener is required");
            if (messageListener instanceof GenericAcknowledgingMessageListener) {
                this.acknowledgingMessageListener = (GenericAcknowledgingMessageListener<?>) messageListener;
            }
            else if (messageListener instanceof GenericMessageListener) {
                this.listener = (GenericMessageListener<?>) messageListener;
            }
            else {
                throw new IllegalStateException("messageListener must be 'MessageListener' "
                        + "or 'AcknowledgingMessageListener', not " + messageListener.getClass().getName());
            }
            if (containerProperties.getConsumerTaskExecutor() == null) {
                SimpleAsyncTaskExecutor consumerExecutor = new SimpleAsyncTaskExecutor(
                        (getBeanName() == null ? "" : getBeanName()) + "-C-");
                containerProperties.setConsumerTaskExecutor(consumerExecutor);
            }
            if (containerProperties.getListenerTaskExecutor() == null) {
                SimpleAsyncTaskExecutor listenerExecutor = new SimpleAsyncTaskExecutor(
                        (getBeanName() == null ? "" : getBeanName()) + "-L-");
                containerProperties.setListenerTaskExecutor(listenerExecutor);
            }
         // 终于,在这里我们知道原来 Spring 是在这里创建的 kafka consumer,不信可以到这个 ListenerConsumer 里去瞧瞧
    this.listenerConsumer = new ListenerConsumer(this.listener, this.acknowledgingMessageListener); setRunning(true);
         // 看到这个 Future 似乎 get 到了啥东西。是的,多线程,这里使用了多线程-为每一个listenerConsumer开启一个线程 this.listenerConsumerFuture = containerProperties .getConsumerTaskExecutor() .submitListenable(this
    .listenerConsumer); } }

    通过对这个类的分析,我们明白了 KafkaMessageListenerContainer 和 ConcurrentMessageListenerContainer  的关系,而且还发现 Spring 实际创建 kafka consumer 也是在这个容器中发生的,而且还嗅到了多线程。于是进一步确认,经过资料查阅果然:

    //接口,封装原生KafkaConsumer,一个container封装一个consumer
    interface MessageListenerContainer;
    //单线程container实现,只启动一个consumer
    class KafkaMessageListenerContainer implemets MessageListenerContainer;
    //多线程container实现,负责创建多个KafkaMessageListenerContainer
    class ConcurrentMessageListenerContainer implemets MessageListenerContainer;
    
    //接口,工厂模式,container工厂,负责创建container,当使用@KafkaListener时需要提供
    interface KafkaListenerContainerFactory<C extends MessageListenerContainer>;
    //container工厂的唯一实现,且参数为多线程container,如果需要单线程,setConsurrency(null)即可,这也是默认参数
    class KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<K, V>>

    大家看到了没有,一切都水落石出:原来 Spring 提供了单线程和多线程两种方式使用 kafka consumer,而是否使用多线程由之前那个 int 属性  concurrency 变量决定,而且在分析 ConcurrentMessageListenerContainer  的时候知道它的默认值是1,也就是默认是使用单线程的。而且这个线程数还不能大于分区数。顺便提及一下:spring kafka 提供了两种提交方式,手动提交和自动提交,而手动提交 Spring 是如何监听到的呢,这个就是在 KafkaMessageListenerContainer 我们看到的那个 acknowledgingMessageListener 做出的贡献了。

    消费者剩下花絮请看:spring kafka consumer原理解析二

  • 相关阅读:
    Android 中的selector
    Android 中SimpleDateFormat的使用注意
    Android 和iOS中 View的滚动
    Android 和iOS中 Gesture 和 Touch
    iOS 的UIWindow 类研究
    iOS keyChain 的使用
    关于Intent ,Task, Activity的理解
    Android Broadcast 和 iOS Notification
    Android 程序中得到root activity的引用
    ios中的addChildViewController 和 android中的fragment
  • 原文地址:https://www.cnblogs.com/lzj123/p/10741284.html
Copyright © 2020-2023  润新知