依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        

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

 

配置:KafkaConfig

package com.sea.common.config;

import java.util.Map;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.listener.ContainerProperties;

import com.google.common.collect.Maps;

@Configuration
@EnableKafka
public class KafkaConfig {

    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;

    @Value("${spring.kafka.consumer.group-id}")
    private String groupId;

    @Value("${spring.kafka.consumer.enable-auto-commit}")
    private Boolean autoCommit;

    @Value("${spring.kafka.consumer.auto-offset-reset}")
    private String autoOffsetReset;

    @Value("${spring.kafka.consumer.max-poll-records}")
    private Integer maxPollRecords;

    @Value("${spring.kafka.producer.retries}")
    private Integer retries;

    @Value("${spring.kafka.producer.batch-size}")
    private Integer batchSize;

    @Value("${spring.kafka.producer.buffer-memory}")
    private Integer bufferMemory;



    //############################# producer 的基本配置 ################################*/
    /**
     * producer 的基本配置
     * @return
     */
    @Bean
    public Map<String, Object> producerConfigs() {
        Map<String, Object> props = Maps.newHashMap();
        props.put(ProducerConfig.ACKS_CONFIG, "0");//推荐设置为1
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ProducerConfig.RETRIES_CONFIG, retries);
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        return props;
    }

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        return new DefaultKafkaProducerFactory<>(producerConfigs());
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

    
    //############################# consumer 的基本配置 ################################*/
    /**
     * consumer基本属性配置
     * @return
     */
    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = Maps.newHashMap();
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, autoCommit);
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);// #最早未被消费的offset earliest
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);//#批量消费一次最大拉取的数据量
        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 180000);//#连接超时时间
        props.put(ConsumerConfig.REQUEST_TIMEOUT_MS_CONFIG, 180000);
        props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 180000);//#手动提交设置与poll的心跳数,如果消息队列中没有消息,等待毫秒后,调用poll()方法。如果队列中有消息,立即消费消息,每次消费的消息的多少可以通过max.poll.records配置。
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
//        props.put(ConsumerConfig.DEFAULT_FETCH_MAX_BYTES+"",15728640); //#设置拉取数据的大小,15M
        return props;
    }
    

    /**
     * 并发数3
     */
//    @Bean //配置默认kafkaFactory
//    @ConditionalOnMissingBean(name = "kafkaBatchListener3")
//    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaBatchListener3() {
//        ConcurrentKafkaListenerContainerFactory<String, String> factory = (ConcurrentKafkaListenerContainerFactory<String, String>) batchFactory();
//        factory.setConcurrency(3);
//        return factory;
//    }
    
    /**
     * 配置为批量消费
     * @return
     */
    @Bean
    public KafkaListenerContainerFactory<?> batchFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerConfigs()));
        //设置为批量消费,每个批次数量在Kafka配置参数中设置ConsumerConfig.MAX_POLL_RECORDS_CONFIG
        factory.setBatchListener(true);
        
        //设置并发量为3
        factory.setConcurrency(3);
        // set the retry template 失败retry
//        factory.setRetryTemplate(retryTemplate());
        //设置为手动ack
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);
        return factory;
    }

}

 

application.xml

### kafka configure
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=seatest//该值任意,建议使用项目名
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.max-poll-records=5
spring.kafka.producer.retries=3
spring.kafka.producer.batch-size=16384
spring.kafka.producer.buffer-memory=33554432

或者(参考调整):

kafka:
  producer:
    bootstrap-servers: 10.161.11.222:6667,10.161.11.223:6667,10.161.11.224:6667
    batch-size: 16785                                   #一次最多发送数据量
    retries: 1                                          #发送失败后的重复发送次数
    buffer-memory: 33554432                             #32M批处理缓冲区
    linger: 1
  consumer:
    bootstrap-servers: 10.161.11.222:6667,10.161.11.223:6667,10.161.11.224:6667
    auto-offset-reset: latest                           #最早未被消费的offset earliest
    max-poll-records: 3100                              #批量消费一次最大拉取的数据量
    enable-auto-commit: false                           #是否开启自动提交
    auto-commit-interval: 1000                          #自动提交的间隔时间
    session-timeout: 20000                              #连接超时时间
    max-poll-interval: 15000                            #手动提交设置与poll的心跳数,如果消息队列中没有消息,等待毫秒后,调用poll()方法。如果队列中有消息,立即消费消息,每次消费的消息的多少可以通过max.poll.records配置。
    max-partition-fetch-bytes: 15728640                 #设置拉取数据的大小,15M
  listener:
    batch-listener: true                                #是否开启批量消费,true表示批量消费
    concurrencys: 3,6                                   #设置消费的线程数
    poll-timeout: 1500                                  #只限自动提交,

 

 

发送数据:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.concurrent.ListenableFuture;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Producertester {

    private static Logger log = LoggerFactory.getLogger(Producertester.class);

    @Autowired
    KafkaTemplate<String, String> kafkaTemplate;
    private static String TOPIC = "sea";

    @Test
    public void testSender() throws Exception {
        /**
         * 参数1:topic 参数2: message
         */
        kafkaTemplate.send(TOPIC, "ni hao ma");

    }

    /**
     * * 带回调函数, 前提是 props.put(ProducerConfig.ACKS_CONFIG, "1");//设置为1 或者all
     * 
     * @param topic
     * @param message
     * @throws Exception
     */
    @Test
    public void testSenderwithCallBack() throws Exception {

        ListenableFuture<SendResult<String, String>> sender = kafkaTemplate.send("sea", "chifanle ");

        // 发送成功
//    SuccessCallback successCallback = result -> log.info("数据发送成功!");
        // 发送失败回调
//    FailureCallback failureCallback = ex -> log.error("数据发送失败!");
//        void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);
        sender.addCallback(successCallback -> {
        }, failureCallback -> log.info("数据发送失败!"));
        SendResult<String, String> sendResult = sender.get();
        System.err.println(sendResult);
        // SendResult [producerRecord=ProducerRecord(topic=sea, partition=null, headers=RecordHeaders(headers = [], isReadOnly = true), key=null, value=chifanle , timestamp=null), recordMetadata=sea-0@-1]

    }

}

 

 

 

消费数据:

@Component
public class KafkaConsumer {

    /**
     * 方式二: 批量消费, 增大吞吐量
     * @param records
     * @param ack
     */

    @KafkaListener(topics = "sea", containerFactory = "batchFactory", errorHandler = "consumerAwareErrorHandler")
    public void listen(List<ConsumerRecord<String, String>> records, Acknowledgment ack){  
        System.err.println(records);
        System.err.println("&&&&&&&&&&&&&&&&&&");

        ack.acknowledge();
    }
    
    
    
    /**
     * 方式一:单条消费
     * @param record
     * @param ack
     */
   //@KafkaListener(containerFactory = "batchFactory",topics = {"topic1","topic2"})

    @KafkaListener(topics = "sea1",errorHandler = "consumerAwareErrorHandler")
    public void listen(ConsumerRecord<?,String> record,Acknowledgment ack) { 
        System.out.println(record);
      ack.acknowledge();
    }

}

 

异常处理:

 

@Component
public class KafkaErrorListener {


    @Bean
    public ConsumerAwareListenerErrorHandler consumerAwareErrorHandler() 
    {
         return new ConsumerAwareListenerErrorHandler() 
                        {
            @Override
            public Object handleError(Message<?> message, ListenerExecutionFailedException e, Consumer<?, ?> consumer) {
                  System.err.println("consumer message occur error, "+ e);
                  //doing something
                return null;
            }
        };
    }
}

 

 

 

分区批量消费:

@Component
public class KafkaPartitionConsumer {

/**
 * 分区消费: 此处只测试批量分区消费
 * 
 * 说明: topic "sea" 有两个分区, 分区0,1  (同一个group 中的Consumer 如果不指定分区,或者指定的分区是一样的 那么消费的数据 一模一样, 毫无意义 )
 * 下面使用两个 Consumer 分别去消费两个不同的partition 的数据, 这样一条数据,只会被一个consumer 消费
 * 
 * @param records
 * @param ack
 */
    @KafkaListener(id = "id2",groupId="sea7", containerFactory = "batchFactory",topicPartitions = { @TopicPartition(topic = "sea", partitions = { "0" }) })
//     @KafkaListener(id = "id1",groupId="sea8", containerFactory = "batchFactory",topicPartitions = { @TopicPartition(topic = "sea",partitionOffsets =  @PartitionOffset(partition = "0",initialOffset = "-1")) })
    public void listen2(List<ConsumerRecord<String, String>> records, Acknowledgment ack){
        System.err.println("方式2  方式2  方式2  方式2 方式2 方式2 方式2 方式2 ");
        System.err.println(records.get(0).value());
        ack.acknowledge();
    }
    
    
    
   
//      @KafkaListener(id = "id1",groupId="sea9", containerFactory = "batchFactory",topicPartitions = { @TopicPartition(topic = "sea", partitions = { "1" }) })
      @KafkaListener(id = "id1",groupId="sea8", containerFactory = "batchFactory",topicPartitions = { @TopicPartition(topic = "sea",partitionOffsets =  @PartitionOffset(partition = "1",initialOffset = "-1")) })
      public void listen1(List<ConsumerRecord<String, String>> records, Acknowledgment ack){
        
        System.err.println("方式一 方式一 方式一 方式一 方式一 方式一 方式一 方式一 ");
        System.out.println(records.get(0).value());
        ack.acknowledge();
    }
}