• Spring Kafka


    在Spring中应用Kafka及配置

    1、需要的jar包

            <dependency>
                <groupId>org.springframework.kafka</groupId>
                <artifactId>spring-kafka</artifactId>
                <version>2.2.0.RELEASE</version>
            </dependency>

    兼容:

      • Apache Kafka Clients 2.0.0
      • Spring Framework 5.1.x
      • Minimum Java version: 8

    2、Kafka配置类

    import org.apache.kafka.clients.consumer.ConsumerConfig;
    import org.apache.kafka.clients.producer.ProducerConfig;
    import org.apache.kafka.common.serialization.IntegerDeserializer;
    import org.apache.kafka.common.serialization.IntegerSerializer;
    import org.apache.kafka.common.serialization.StringDeserializer;
    import org.apache.kafka.common.serialization.StringSerializer;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.kafka.annotation.EnableKafka;
    import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
    import org.springframework.kafka.core.*;
    import org.springframework.kafka.listener.ContainerProperties;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Kafka的配置类
     *
     * @author yangyongjie
     * @date 2019/10/11
     * @desc
     */
    @Configuration
    @EnableKafka
    public class KafkaConfig {
    
        @Value("${kafka.bootstrap.servers}")
        private String bootstrapServers;
    
        @Value("${kafka.group.id}")
        private String groupId;
    
        @Value("${kafka.retries}")
        private String retries;
    
        /**
         * kafka消息监听器的工厂类
         *
         * @return
         */
        @Bean
        ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {
            ConcurrentKafkaListenerContainerFactory<Integer, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
            factory.setConsumerFactory(consumerFactory());
            ContainerProperties containerProperties = factory.getContainerProperties();
            // 当Acknowledgment.acknowledge()方法被调用即提交offset
            containerProperties.setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
            // 调用commitAsync()异步提交
            containerProperties.setSyncCommits(false);
            return factory;
        }
    
        /**
         * 消费者工厂
         *
         * @return
         */
        @Bean
        public ConsumerFactory<Integer, String> consumerFactory() {
            return new DefaultKafkaConsumerFactory<>(consumerConfigs());
        }
    
        /**
         * 消费者拉取消息配置
         *
         * @return
         */
        @Bean
        public Map<String, Object> consumerConfigs() {
            Map<String, Object> props = new HashMap<>(16);
            // kafka集群地址
            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
            // groupId
            props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
            // 开启自动提交
            props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
            // 自动提交offset到zk的时间间隔,时间单位是毫秒
    //        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
            // session超时设置,14秒,超过这个时间会认为此消费者挂掉,将其从消费组中移除
            props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "14000");
            //键的反序列化方式,key表示分区
            props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
            //值的反序列化方式
            props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
            return props;
        }
    
        /**
         * 生产者工厂
         *
         * @return
         */
        @Bean
        public ProducerFactory<Integer, String> producerFactory() {
            return new DefaultKafkaProducerFactory<>(producerConfigs());
        }
    
        /**
         * 生产者发送消息配置
         *
         * @return
         */
        @Bean
        public Map<String, Object> producerConfigs() {
            Map<String, Object> props = new HashMap<>(8);
            // kafka集群地址
            props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
            // 消息发送确认方式
            props.put(ProducerConfig.ACKS_CONFIG, "1");
            // 消息发送重试次数
            props.put(ProducerConfig.RETRIES_CONFIG, retries);
            // 重试间隔时间
            props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, "1000");
            //键的反序列化方式,key表示分区
            props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
            //值的反序列化方式
            props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            return props;
        }
    
        /**
         * Kafka模版类,用来发送消息
         *
         * @return
         */
        @Bean
        public KafkaTemplate<Integer, String> kafkaTemplate() {
            return new KafkaTemplate<Integer, String>(producerFactory());
        }
    
    }

    3、发送消息

    KafkaTemplate包装了producer而且提供了便捷的方法发送消息到kafka topic。

    API:

    ListenableFuture<SendResult<K, V>> sendDefault(V data); //sendDefault方法需要提供给一个默认的topic
    ListenableFuture<SendResult<K, V>> sendDefault(K key, V data);
    ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, K key, V data);
    ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, Long timestamp, K key, V data);
    ListenableFuture<SendResult<K, V>> send(String topic, V data);
    ListenableFuture<SendResult<K, V>> send(String topic, K key, V data);
    ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, V data);
    ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, V data);
    ListenableFuture<SendResult<K, V>> send(ProducerRecord<K, V> record);
    ListenableFuture<SendResult<K, V>> send(Message<?> message); //topic,partition,key等信息在message头中定义
    Map<MetricName, ? extends Metric> metrics();
    List<PartitionInfo> partitionsFor(String topic);
    <T> T execute(ProducerCallback<K, V, T> callback);
    // Flush the producer.
    void flush();
    interface ProducerCallback<K, V, T> {
     T doInKafka(Producer<K, V> producer);
    }

    如:

        @Autowired
        private KafkaTemplate kafkaTemplate;
    
        kafkaTemplate.send("test", "this is my first demo");

    4、消费消息

      消费消息可以通过配置一个MessageListenerContainer然后提供一个Message Listener去接收消息;或者使用@KafkaListener注解

      1、提供MessageListenerContainer和Message Listener的方式

    MessageListenerContainer的两个实现类:

      • KafkaMessageListenerContainer   使用单线程接收所有的消息

      • ConcurrentMessageListenerContainer  相当于多个KafkaMessageListenerContainer使用多个线程消费

      另外需要提供一个Message Listener,目前提供的8个messageListener接口:

    public interface MessageListener<K, V> { ❶ // 调用poll()来轮询Kafka集群的消息,并自动提交offset
     void onMessage(ConsumerRecord<K, V> data);
    }
    public interface AcknowledgingMessageListener<K, V> { ❷ // 调用poll()来轮询Kafka集群的消息,手动提交
     void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment);
    }
    // 调用poll()来轮询Kafka集群的消息,自动提交offset或使用提供的Consumer对象手动提交
    public interface ConsumerAwareMessageListener<K, V> extends MessageListener<K, V> { ❸ void onMessage(ConsumerRecord<K, V> data, Consumer<?, ?> consumer); }
    // 调用poll()来轮询Kafka集群的消息,使用提供的Consumer对象手动提交
    public interface AcknowledgingConsumerAwareMessageListener<K, V> extends MessageListener<K, V> { ❹ void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer); }
    // 调用poll()来轮询Kafka集群的消息,自动提交offset或手动提交,AckMode.RECORD不支持
    public interface BatchMessageListener<K, V> { ❺ void onMessage(List<ConsumerRecord<K, V>> data); }
    // 调用poll()来轮询Kafka集群的消息,手动提交
    public interface BatchAcknowledgingMessageListener<K, V> { ❻ void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment); }
    // 调用poll()来轮询Kafka集群的消息,自动提交offset或使用提供的Consumer对象手动提交,AckMode.RECORD不支持
    public interface BatchConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> { ❼ void onMessage(List<ConsumerRecord<K, V>> data, Consumer<?, ?> consumer); } public interface BatchAcknowledgingConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> { ❽ // 调用poll()来轮询Kafka集群的消息,使用提供的Consumer对象手动提交 void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer); }

    需要注意的是:Consumer不是线程安全的,只能在调用此Listener的线程上调用此方法。

     ConcurrentMessageListenerContainer,唯一的构造器和KafkaMessageListenerContainer的第一个构造器相似:

    public ConcurrentMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
    ContainerProperties containerProperties)

    有个属性:private int concurrency = 1; 其值的多少表示将会创建多少个KafkaMessageListenerContainer。

      KafkaMessageListenerContainer的第一个构造器中,当监听多个topic时,如,需要监听3个topic,每个topic有5个分区,那么设置concurrency=15的话,将会发现只有5个激活的Consumers,每个Consumer从每个topic中分配了一个分区,其他10个则处于空闲状态。这是因为Kafka默认的PartitionAssignor(分区分配器)是RangeAssignor,上面的情况应该使用RoundRobinAssignor来代替。RoundRobinAssignor将会将分区分配给所有的Consumers,这样每个Consumer会分得一个topic的一个分区。

    想要切换PartitionAssignor,使用ConsumerConfigs.PARTITION_ASSIGNMENT_STRATEGY_CONFIG

      KafkaMessageListenerContainer的第二个构造器中,ConcurrentMessageListenerContainer分发多个TopicPartition给KafkaMessageListenerContainer,也就是说,当有6个分区的时候,需要提供的concurrency 为3,每个ConcurrentMessageListenerContainer获得两个分区;当有5个分区的时候,前两个ConcurrentMessageListenerContainer每个获得2个分区,最后一个获得一个分区。

      Committing Offsets

      consumer poll()方法将会返回一个或多个ConsumerRecords,每个ConsumerRecord都将会调用一次MessageListener。对于每个ConsumerRecord都提供了多个选择去提交offsets,当enable.auto.commit设置为true的时候,Kafka将自动提交offsets,如果为false的话,有以下几种AckMode(消息确认)方式:

    RECORD 提交offset当监听器处理完每个ConsumerRecord后
    BATCH 提交offset当监听器处理完poll()返回的所有的ConsumerRecord
    TIME 提交offset当监听器处理完poll()返回的所有的ConsumerRecord并且距离上次提交的时间超过了ackTime
    COUNT 提交offset当监听器处理完poll()返回的所有的ConsumerRecord并且距离上次提交已经接收了超过ackCount个ConsumerRecord
    COUNT_TIME 提交offset当监听器处理完poll()返回的所有的ConsumerRecord,当ackTime或者ackCount成立时
    MANUAL 在Acknowledgment.acknowledge()中使用,和BATCH相似
    MANUAL_IMMEDIATE 当Acknowledgment.acknowledge()方法被调用即提交offset

      默认的提交方式为BATCH,默认使用commitSync()同步提交。在ContainerProperties中修改

      调用commitSync()同步提交或者commitAsync()异步提交,取决于syncCommits的配置,默认为true。

      使用这种方式在上面的KafkaConfig配置类中加上如下配置:

    使用监听容器工厂,创建监听容器:

     /**
         * MessageListenerContainer
         * @param myKafkaMessageListener 自定义的消息监听器,也可使用ConcurrentMessageListenerContainer
         * @return
         */
        @Bean
        public KafkaMessageListenerContainer<Integer, String> getContainer(MyKafkaMessageListener myKafkaMessageListener) {
            ContainerProperties containerProperties = new ContainerProperties("test");
            containerProperties.setAckMode(ContainerProperties.AckMode.RECORD);
    
            //使用自定义的MessageListener
            containerProperties.setMessageListener(myKafkaMessageListener);
            containerProperties.setGroupId("bssout");
            containerProperties.setClientId("myListener");
    
            KafkaMessageListenerContainer<Integer, String> kafkaMessageListenerContainer = new KafkaMessageListenerContainer<>(consumerFactory(), containerProperties);
    
            return kafkaMessageListenerContainer;
        }

    MyKafkaMessageListener :

    @Component
    public class MyKafkaMessageListener extends MyAbstractKafkaMessageListener  {
    
        private static final Logger LOGGER= LoggerFactory.getLogger(MyKafkaMessageListener.class);
    
        @Override
        public void onMessage(ConsumerRecord consumerRecord, Consumer consumer) {
            String topic=consumerRecord.topic();
            String value= (String) consumerRecord.value();
            LOGGER.info("my_value={}",value);
            consumer.commitAsync();
        }
    }

    MyAbstractKafkaMessageListener :

    public abstract class MyAbstractKafkaMessageListener implements ConsumerAwareMessageListener {
    
        @Override
        public void onMessage(Object o) {
            return;
        }
    }

      2、使用@KafkaListener注解的方式:

    @Component
    public class KafkaListenerTest {
    
        private static final Logger LOGGER= LoggerFactory.getLogger(KafkaListenerTest.class);
    
        /**
         * id为consumerId
         * @param record
         */
        @KafkaListener(topics = "test")
        public void listen(ConsumerRecord<?, ?> record,Acknowledgment acknowledgment) {
            LOGGER.info("test-value={}",record.value());
            LOGGER.info("test-topic={}",record.topic());

          // 手动提交offset
          acknowledgment.acknowledge();

        }
    }

    @KafkaListener注解的属性(成员变量)详解:

      (1)、【String id() default ""】:唯一的标识,如果没有提供,则会自动生成,如果提供了,id的值将会覆盖系统配置的groupId的值,除非此注解的idIsGroup属性设置为false(默认true)。

      (2)、【String containerFactory() default ""】:KafkaListenerContainerFactory 监听容器工厂的bean的name,若没有指定,则使用系统默认的,如果系统配置的有多个需要指定name值

      (3)、【String[] topics() default {}】:监听的topic,可监听多个,值可以为具体的topic name,property-placeholder keys或者 expressions(SPEL)

      (4)、【String topicPattern() default ""】:上面topic参数的模式,可配置更加详细的监听信息,如监听某个Topic的指定分区,或者从offset的某个位置开始监听

      (5)、【TopicPartition[] topicPartitions() default {}】:监听topic的分区信息

      (6)、【String groupId() default ""】:覆盖系统配置的消费组groupId

      (7)、【boolean idIsGroup() default true】:如果groupId属性没有指定,则用id属性的值覆盖系统指定的groupId

      (8)、【String errorHandler() default ""】:设置一个KafkaListenerErrorHandler监听异常处理器去处理方法抛出的异常

      (9)、【String clientIdPrefix() default ""】:如果提供了,重写在consumerFactory中配置的client id的前缀

      (10)、【String concurrency() default ""】:重写container factory的concurrency配置

      (11)、【String autoStartup() default ""】:是否自动启动

      (12)、【String beanRef() default "__listener"】:真实监听容器的BeanName,需要在 BeanName前加 "__"

    另外这种方式要求一个配置类,在其当中有一个name为kafkaListenerContainerFactory的bean用来生成ConcurrentMessageListenerContainer,并且类上有@EnableKafka和@Configuration注解。如:

    @Configuration
    @EnableKafka
    public class KafkaConfig {
        @Bean
        KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>>
        kafkaListenerContainerFactory() {
            ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
                    new ConcurrentKafkaListenerContainerFactory<>();
            factory.setConsumerFactory(consumerFactory());
            factory.setConcurrency(3);
            factory.getContainerProperties().setPollTimeout(3000);
            return factory;
        }
        @Bean
        public ConsumerFactory<Integer, String> consumerFactory() {
            return new DefaultKafkaConsumerFactory<>(consumerConfigs());
        }
        @Bean
        public Map<String, Object> consumerConfigs() {
            Map<String, Object> props = new HashMap<>();
            props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "");
            ...
            return props;
        }
    }

     @EnableKafka注解向容器中导入了 KafkaBootstrapConfiguration 配置类,此配置类中注入了KafkaListenerAnnotationBeanPostProcessor @KafkaListener注解的后置处理器

    /**
     * Enable Kafka listener annotated endpoints that are created under the covers by a
     * {@link org.springframework.kafka.config.AbstractKafkaListenerContainerFactory
     * @see KafkaListener
     * @see KafkaListenerAnnotationBeanPostProcessor
     * @see org.springframework.kafka.config.KafkaListenerEndpointRegistrar
     * @see org.springframework.kafka.config.KafkaListenerEndpointRegistry
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(KafkaBootstrapConfiguration.class)
    public @interface EnableKafka {
    }

      @KafkaListener注解由KafkaListenerAnnotationBeanPostProcessor类解析,其实现了BeanPostProcessor接口,并在postProcessAfterInitialization方法内解析@KafkaListener注解。

        @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);
                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,
                            (ReflectionUtils.MethodFilter) method ->
                                    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);
                }
            }
            return bean;
        }

      

      更多详细信息可参考:https://www.jianshu.com/c/0c9d83802b0c

      官方文档:https://docs.spring.io/autorepo/docs/spring-kafka-dist/2.2.0.RELEASE/reference/html/_reference.html

  • 相关阅读:
    文件上传漏洞全面渗透姿势总结
    注册frpc为windows服务,可在未登录用户时启动
    SpringIOC 容器注入方式
    如何交换 Integer 的值?
    分布式websocket服务器
    win10安装Hbase2.3.0
    hadoop常用的命令
    win10安装kafka 2.122.8.1
    win10激活码
    win10 flume source为spooldir,输出到hdfs
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/11662699.html
Copyright © 2020-2023  润新知