官网:http://kafka.apache.org/21/documentation.html
依赖:
<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(); } }