springboot整合kafka配置方式

xzlnuli發表於2024-05-24

背景:

很多整合kafka的方式,都是透過配置檔案的方式直接配置了,靈活性較差,若遇到需要動態調整分組ID的時候,就會遇到瓶頸

踩坑:

1、kafka配置外網使用的時候,bootstrap-servers需要的是域名地址,直接暴露外網ip,kafka啟動都是不成功的,本地可以使用127.0.0.1進行通訊

使用伺服器外網的時候,最好 本地配置hosts轉發到kafka伺服器上

如:bootstrap-servers: 048f7a2e7d03:9092

2、auto-offset-reset: 配置很重要,根據實際情況來配置,不然容器導致重複消費的問題

3、key 和value的序列化問題,手動進行配置的時候,生產者配置:"org.apache.kafka.common.serialization.StringSerializer" 直接指定某個序列化方式,不需要指定class

springboot整合kafka配置方式使用:

kafka:
    bootstrap-servers: 048f7a2e7d03:9092
    producer:
      # 發生錯誤後,訊息重發的次數。
      retries: 0
      #當有多個訊息需要被髮送到同一個分割槽時,生產者會把它們放在同一個批次裡。該引數指定了一個批次可以使用的記憶體大小,按照位元組數計算。
      batch-size: 16384
      # 設定生產者記憶體緩衝區的大
      buffer-memory: 33554432
      # 鍵的序列化方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      # 值的序列化方式
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      # acks=0 : 生產者在成功寫入訊息之前不會等待任何來自伺服器的響應。
      # acks=1 : 只要叢集的首領節點收到訊息,生產者就會收到一個來自伺服器成功響應。
      # acks=all :只有當所有參與複製的節點全部收到訊息時,生產者才會收到一個來自伺服器的成功響應。
      acks: 1
      properties:
        max.block.ms: 2000

    consumer:
      group-id: testGroup
      # 自動提交的時間間隔 在spring boot 2.X 版本中這裡採用的是值的型別為Duration 需要符合特定的格式,如1S,1M,2H,5D
      auto-commit-interval: 1S
      # 該屬性指定了消費者在讀取一個沒有偏移量的分割槽或者偏移量無效的情況下該作何處理:
      # latest(預設值)在偏移量無效的情況下,消費者將從最新的記錄開始讀取資料(在消費者啟動之後生成的記錄)
      # earliest :在偏移量無效的情況下,消費者將從起始位置讀取分割槽的記錄
      auto-offset-reset: latest
      # 是否自動提交偏移量,預設值是true,為了避免出現重複資料和資料丟失,可以把它設定為false,然後手動提交偏移量
      enable-auto-commit: false
      # 鍵的反序列化方式
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      # 值的反序列化方式
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    listener:
      # 在偵聽器容器中執行的執行緒數。
      concurrency: 5
      #listner負責ack,每呼叫一次,就立即commit
      ack-mode: manual_immediate
      missing-topics-fatal: false

注意listener的配置:

ack-mode:一般配置為MANUAL 或者MANUAL_IMMEDIATE 標識手動進行offset的提交

auto-offset-reset:很重要

latest(預設值)在偏移量無效的情況下,消費者將從最新的記錄開始讀取資料(在消費者啟動之後生成的記錄)

earliest :在偏移量無效的情況下,消費者將從起始位置讀取分割槽的記錄

解釋:latest就是表示有新的分組進來,從當前的offset進行訊息消費,之前的offset不管

earliest:表示有新的分組進來,offset從0開始進行消費

根據業務需求來, 配置的不好,可能導致重複消費問題

springboot整合kafka配置方式

1、配置kafka生產者

import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * kafka的消費者配置
 *
*/

@Slf4j
@Component
public class KafkaProducerConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String BROKERS;

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

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

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

    @Value("${spring.kafka.producer.acks}")
    private String ACK;

    @Value("${spring.kafka.producer.properties.max.block.ms}")
    private Integer BLOCK_MS;

    /**
     * 生產者配置
     *
     * @return 配置
     */
    @Bean
    public Map<String, Object> producerConfigs() {
        Map<String, Object> properties = new HashMap<String, Object>();
        //kafka server地址
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BROKERS);
        properties.put(ProducerConfig.ACKS_CONFIG,ACK);
        //訊息傳送失敗重試次數
        properties.put(ProducerConfig.RETRIES_CONFIG, RETRIES);
        //去緩衝區中一次拉16k的資料,傳送到broker
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, BATCH_SIZE);
        //設定快取區大小 32m
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, BUFFER_MEMORY);
        //key序列化器選擇,直接指定序列化包class,不能直接寫 StringSerializer.class
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //value序列化器選擇
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        //設定sasl認證
        properties.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
        properties.put(ProducerConfig.MAX_BLOCK_MS_CONFIG,BLOCK_MS);
        return properties;
    }

    /**
     * Producer Template 配置
     */
    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        Map<String, Object> stringObjectMap = producerConfigs();
        DefaultKafkaProducerFactory<String, String> objectObjectDefaultKafkaProducerFactory = new DefaultKafkaProducerFactory<>(stringObjectMap);
        return new KafkaTemplate<>(objectObjectDefaultKafkaProducerFactory);
    }
}

注意上面標紅的部分:配置key和valued的序列化方式的時候,直接指定配置的class

2、配置kafka消費者

import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.listener.ContainerProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * kafka的消費者配置
 *
 * @author G008186
 */

@Slf4j
@Component
public class KafkaConsumerConfig {

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

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

    @Value("${spring.kafka.consumer.auto-commit-interval}")
    private String AUTO_COMMIT_INTERVAL_MS;

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

    @Value("${spring.kafka.listener.concurrency}")
    private Integer CONCURRENCY;

    @Value("${spring.kafka.listener.missing-topics-fatal}")
    private Boolean TOPICS_FATAL;

    private String CURRENT_INSTANCE_GROUP_ID;
/**構建kafka監聽工廠*/
    @Bean("kafkaListenerContainerFactory")
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<String, String>();

//這裡配置監聽器的模式,記得需要如下,先獲取properties,然後才可以設定 factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); factory.setConcurrency(CONCURRENCY); factory.setMissingTopicsFatal(TOPICS_FATAL); factory.setConsumerFactory(consumerFactory());
return factory; } /**初始化消費工廠配置 其中會動態指定消費分組*/ private ConsumerFactory<String, String> consumerFactory() { Map<String, Object> properties = new HashMap<String, Object>(); properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BROKERS); properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, ENABLE_AUTO_COMMIT); properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1); properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); /**多例項部署每個例項設定不同groupId 實現釋出訂閱*/ //CURRENT_INSTANCE_GROUP_ID = KafkaConstant.SSE_GROUP.concat(String.valueOf(System.identityHashCode(sendSyncTaskFactory))); log.info("當前例項WsMsgConsumer group_id:{}",CURRENT_INSTANCE_GROUP_ID); properties.put(ConsumerConfig.GROUP_ID_CONFIG, CURRENT_INSTANCE_GROUP_ID); properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, AUTO_OFFSET_RESET); return new DefaultKafkaConsumerFactory<String, String>(properties); } }

注意紅色部分,配置ack-mode的方式

相關文章