springboot整合kafka

一、引入依赖 (kafka的版本和springboot的版本对不上的话,启动会报错,包类不存在)

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

我的springboot版本:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

二、配置 yml

spring:

  kafka:
    bootstrap-servers: 192.168.233.11:9092,192.168.233.11:9093,192.168.233.11:9094
    #生产者的配置,大部分我们可以使用默认的,这里列出几个比较重要的属性
    producer:
      #0---表示不进行消息接收是否成功的确认
      #1---表示当Leader接收成功时确认
      #all -1---表示Leader和Follower都接收成功时确认
      acks: all
      #设置大于0的值将使客户端重新发送任何数据,一旦这些数据发送失败。注意,这些重试与客户端接收到发送错误时的重试没有什么不同。允许重试将潜在的改变数据的顺序,如果这两个消息记录都是发送到同一个partition,则第一个消息失败第二个发送成功,则第二条消息会比第一条消息出现要早。
      retries: 2
      #每批次发送消息的数量
      batch-size: 16384
      #producer可以用来缓存数据的内存大小。如果数据产生速度大于向broker发送的速度,producer会阻塞或者抛出异常,以“block.on.buffer.full”来表明。这项设置将和producer能够使用的总内存相关,但并不是一个硬性的限制,因为不是producer使用的所有内存都是用于缓存。一些额外的内存会用于压缩(如果引入压缩机制),同样还有一些用于维护请求。
      buffer-memory: 33554432
      #key序列化方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      properties:
        linger.ms: 1
        enable:
          idempotence: true
    #消费者的配置
    consumer:
      #是否开启自动提交
      enable-auto-commit: false
      #自动提交的时间间隔
      auto-commit-interval: 100ms
      #key的解码方式
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      properties:
        session.timeout.ms: 15000
      max-poll-records: 15
    listener:
      ack-mode: manual_immediate
      type: batch

上面我配置的是手动Ack,并且批量消息,一次可以拉取15条记录

yaml中的各个配置参数的意思,参考官方文档 https://kafka.apachecn.org/documentation.html#configuration

 

三、springboot自动装配机制

 

package org.springframework.boot.autoconfigure.kafka;

import java.io.IOException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Jaas;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaAdmin;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.security.jaas.KafkaJaasLoginModuleInitializer;
import org.springframework.kafka.support.LoggingProducerListener;
import org.springframework.kafka.support.ProducerListener;
import org.springframework.kafka.support.converter.RecordMessageConverter;
import org.springframework.kafka.transaction.KafkaTransactionManager;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({KafkaTemplate.class})
@EnableConfigurationProperties({KafkaProperties.class})
@Import({KafkaAnnotationDrivenConfiguration.class, KafkaStreamsAnnotationDrivenConfiguration.class})
public class KafkaAutoConfiguration {
    private final KafkaProperties properties;

    public KafkaAutoConfiguration(KafkaProperties properties) {
        this.properties = properties;
    }

    @Bean
    @ConditionalOnMissingBean({KafkaTemplate.class})
    public KafkaTemplate<?, ?> kafkaTemplate(ProducerFactory<Object, Object> kafkaProducerFactory, ProducerListener<Object, Object> kafkaProducerListener, ObjectProvider<RecordMessageConverter> messageConverter) {
        KafkaTemplate<Object, Object> kafkaTemplate = new KafkaTemplate(kafkaProducerFactory);
        messageConverter.ifUnique(kafkaTemplate::setMessageConverter);
        kafkaTemplate.setProducerListener(kafkaProducerListener);
        kafkaTemplate.setDefaultTopic(this.properties.getTemplate().getDefaultTopic());
        return kafkaTemplate;
    }

    @Bean
    @ConditionalOnMissingBean({ProducerListener.class})
    public ProducerListener<Object, Object> kafkaProducerListener() {
        return new LoggingProducerListener();
    }

    @Bean
    @ConditionalOnMissingBean({ConsumerFactory.class})
    public ConsumerFactory<?, ?> kafkaConsumerFactory(ObjectProvider<DefaultKafkaConsumerFactoryCustomizer> customizers) {
        DefaultKafkaConsumerFactory<Object, Object> factory = new DefaultKafkaConsumerFactory(this.properties.buildConsumerProperties());
        customizers.orderedStream().forEach((customizer) -> {
            customizer.customize(factory);
        });
        return factory;
    }

    @Bean
    @ConditionalOnMissingBean({ProducerFactory.class})
    public ProducerFactory<?, ?> kafkaProducerFactory(ObjectProvider<DefaultKafkaProducerFactoryCustomizer> customizers) {
        DefaultKafkaProducerFactory<?, ?> factory = new DefaultKafkaProducerFactory(this.properties.buildProducerProperties());
        String transactionIdPrefix = this.properties.getProducer().getTransactionIdPrefix();
        if (transactionIdPrefix != null) {
            factory.setTransactionIdPrefix(transactionIdPrefix);
        }

        customizers.orderedStream().forEach((customizer) -> {
            customizer.customize(factory);
        });
        return factory;
    }

    @Bean
    @ConditionalOnProperty(
        name = {"spring.kafka.producer.transaction-id-prefix"}
    )
    @ConditionalOnMissingBean
    public KafkaTransactionManager<?, ?> kafkaTransactionManager(ProducerFactory<?, ?> producerFactory) {
        return new KafkaTransactionManager(producerFactory);
    }

    @Bean
    @ConditionalOnProperty(
        name = {"spring.kafka.jaas.enabled"}
    )
    @ConditionalOnMissingBean
    public KafkaJaasLoginModuleInitializer kafkaJaasInitializer() throws IOException {
        KafkaJaasLoginModuleInitializer jaas = new KafkaJaasLoginModuleInitializer();
        Jaas jaasProperties = this.properties.getJaas();
        if (jaasProperties.getControlFlag() != null) {
            jaas.setControlFlag(jaasProperties.getControlFlag());
        }

        if (jaasProperties.getLoginModule() != null) {
            jaas.setLoginModule(jaasProperties.getLoginModule());
        }

        jaas.setOptions(jaasProperties.getOptions());
        return jaas;
    }

    @Bean
    @ConditionalOnMissingBean
    public KafkaAdmin kafkaAdmin() {
        KafkaAdmin kafkaAdmin = new KafkaAdmin(this.properties.buildAdminProperties());
        kafkaAdmin.setFatalIfBrokerNotAvailable(this.properties.getAdmin().isFailFast());
        return kafkaAdmin;
    }
}

四、生产者:

@RestController
public class MyController {

   @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;
    @RequestMapping("/send/msg")
    public String sendMsg(){
        for (int i = 0; i < 100; i++) {
            //不指定分区,会随机发到不同的分区
            kafkaTemplate.send("shop-topic",i+"aaaa");

            //指定了key,会用key的hashcode %分区总数,根据结果发送到指定分区,因此同一个key永远发到同一个分区
            kafkaTemplate.send("shop-topic",i+"aaa",i+"aaaa");
        }
        return "ok";
    }
}

五、消费者批量消费(yaml那里要开启批量配置)

/**
 * author: yangxiaohui
 * date:   2023/7/13
 */
@Service
public class OrderService {

    /**
     * kafka是按照消费者分组来消费的,同一条消息,在同一个分组中,只会被消费一次,例如 order服务部署了5个节点,他们的消费者组是一样,那么一条
     * 消息只会被这5个节点中的一个节点消费,groupId会自动创建
     * ack提交的是offset
     * @param consumerRecordList
     * @param acknowledgment
     */
    @KafkaListener(topics = "shop-topic",groupId = "my-group",clientIdPrefix = "orderService")
    public void listenMsg(List<ConsumerRecord> consumerRecordList, Acknowledgment acknowledgment){
        acknowledgment.acknowledge();
        for (ConsumerRecord consumerRecord : consumerRecordList) {
            System.out.println(consumerRecord.value());
        }

    }

}

六、消费者单条消费:一定要配置单条消费逻辑,不然会报错

spring:

  kafka:
    bootstrap-servers: 192.168.233.11:9092,192.168.233.11:9093,192.168.233.11:9094
    #生产者的配置,大部分我们可以使用默认的,这里列出几个比较重要的属性
    producer:
      #0---表示不进行消息接收是否成功的确认
      #1---表示当Leader接收成功时确认
      #all -1---表示Leader和Follower都接收成功时确认
      acks: all
      #设置大于0的值将使客户端重新发送任何数据,一旦这些数据发送失败。注意,这些重试与客户端接收到发送错误时的重试没有什么不同。允许重试将潜在的改变数据的顺序,如果这两个消息记录都是发送到同一个partition,则第一个消息失败第二个发送成功,则第二条消息会比第一条消息出现要早。
      retries: 2
      #每批次发送消息的数量
      batch-size: 16384
      #producer可以用来缓存数据的内存大小。如果数据产生速度大于向broker发送的速度,producer会阻塞或者抛出异常,以“block.on.buffer.full”来表明。这项设置将和producer能够使用的总内存相关,但并不是一个硬性的限制,因为不是producer使用的所有内存都是用于缓存。一些额外的内存会用于压缩(如果引入压缩机制),同样还有一些用于维护请求。
      buffer-memory: 33554432
      #key序列化方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      properties:
        linger.ms: 1
        enable:
          idempotence: true
    #消费者的配置
    consumer:
      #是否开启自动提交
      enable-auto-commit: false
      #自动提交的时间间隔
      auto-commit-interval: 100ms
      #key的解码方式
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      properties:
        session.timeout.ms: 15000
      max-poll-records: 15
    listener:
      ack-mode: manual_immediate
      type: single   

注意上面最后的配置那里 type=single

@Service
public class OrderService {

    /**
     * kafka是按照消费者分组来消费的,同一条消息,在同一个分组中,只会被消费一次,例如 order服务部署了5个节点,他们的消费者组是一样,那么一条
     * 消息只会被这5个节点中的一个节点消费,groupId会自动创建
     *
     * @param
     * @param
     */
    @KafkaListener(topics = "shop-topic",groupId = "my-group",clientIdPrefix = "orderService")
    public void listenMsg(ConsumerRecord consumerRecord,Acknowledgment  acknowledgment){
        acknowledgment.acknowledge();
        System.out.println(consumerRecord.value());
    }

}

 七、需要注意的地方:

 

第八、关于topic的创建

1.如果没有事先创建,然后往一个不存在的topic中发送消息,kafka会自动创建这个topic,并且是只有一个分区,一个副本的topic,如:

 

 2. 生产如果需要创建多分区,多副本的topic,可以登录kafka,使用命令行方式创建,就像数据库一样,我们都是先在数据库创建好表,然后java项目再连接该数据库

bin/kafka-topics.sh --create --zookeeper 192.168.233.11:2182,192.168.233.11:2183,192.168.233.11:2184 --replication-factor 2 --partitions 2 --topic order-topic

 第九、kafka常用的概念理解

 

 

 

posted @ 2023-07-13 14:42  yangxiaohui227  阅读(1105)  评论(0编辑  收藏  举报