Springboot整合kafka及常见问题

Springboot整合kafka及常见问题

1 Maven依赖

<dependency>
    <groupId>org.springframework.kafkagroupId>
    <artifactId>spring-kafkaartifactId>
    <version>1.0.0.RELEASEversion>
dependency>


<dependency>
    <groupId>org.apache.kafkagroupId>
    <artifactId>kafka-clientsartifactId>
    <version>0.9.0.1version>
dependency>

2 Config配置

2.1 生产者配置

/**
 * kafka生产配置
 */
@Configuration
@EnableKafka
public class KafkaProducerConfig {
     
    @Value("${kafka.producer.servers}")
    private String servers;
    @Value("${kafka.producer.retries}")
    private int retries;
    @Value("${kafka.producer.batch.size}")
    private int batchSize;
    @Value("${kafka.producer.linger}")
    private int linger;
    @Value("${kafka.producer.buffer.memory}")
    private int bufferMemory;

    public Map<String, Object> producerConfigs() {
     
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        props.put(ProducerConfig.RETRIES_CONFIG, retries);
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
        props.put(ProducerConfig.LINGER_MS_CONFIG, linger);
        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;
    }

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

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

2.2 消费者配置

此处根据需要,对于不同的主题,使用不同的配置参数。之所以没有考虑使用 @KafkaLisener 注解中的group和id,是因为我暂时无法理解其中的group与id与group.id的区别与意义。况且将监听配置写入配置文件也比使用Constant类在语义上更为分明。

/**
 * kafka消费者配置
 */
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
     

    @Value("${kafka.consumer.servers}")
    private String servers;
    @Value("${kafka.consumer.enable.auto.commit}")
    private boolean enableAutoCommit;
    @Value("${kafka.consumer.session.timeout}")
    private String sessionTimeout;
    @Value("${kafka.consumer.auto.commit.interval}")
    private String autoCommitInterval;
    @Value("${kafka.consumer.data.group.id}")
    private String dataGroupId;
    @Value("${kafka.consumer.model.group.id}")
    private String modelGroupId;
    @Value("${kafka.consumer.auto.offset.reset}")
    private String autoOffsetReset;
    @Value("${kafka.consumer.concurrency}")
    private int concurrency;
    @Value("${kafka.listener.ack-mode}")
    private String ack_mode;
    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerModelContainerFactory() {
     
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(ModelConsumerFactory());
        //factory.setConcurrency(concurrency);
        factory.getContainerProperties().setPollTimeout(1500);
        factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.valueOf(ack_mode));
        return factory;
    }

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerDataContainerFactory() {
     
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(DataConsumerFactory());
        //factory.setConcurrency(concurrency);
        factory.getContainerProperties().setPollTimeout(1500);
        factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.valueOf(ack_mode));
        return factory;
    }

    public ConsumerFactory<String, String> ModelConsumerFactory() {
     
        return new DefaultKafkaConsumerFactory<>(ModelConsumerConfigs());
    }

    public ConsumerFactory<String, String> DataConsumerFactory() {
     
        return new DefaultKafkaConsumerFactory<>(DataConsumerConfigs());
    }

    public Map<String, Object> ModelConsumerConfigs() {
     
        Map<String, Object> propsMap = new HashMap<>();
        propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
        propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitInterval);
        propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimeout);
        propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, modelGroupId);
        propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
        return propsMap;
    }

    public Map<String, Object> DataConsumerConfigs() {
     
        Map<String, Object> propsMap = new HashMap<>();
        propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
        propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitInterval);
        propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimeout);
        propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, dataGroupId);
        propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
        return propsMap;
    }
}

3 配置文件

kafka:
  consumer.servers: localhost:9092,localhost:9093
  consumer.enable.auto.commit: false
  consumer.session.timeout: 6000
  consumer.auto.commit.interval: 100
  consumer.auto.offset.reset: earliest
  consumer.topic: data
  consumer.data.group.id: data_container
  consumer.model.group.id: model_container_2
  consumer.concurrency: 1

  listener.ack-mode: MANUAL_IMMEDIATE

  producer.servers: localhost:9092
  producer.retries: 0
  producer.batch.size: 4096
  producer.linger: 1
  producer.buffer.memory: 40960

配置解释

  1. consumer.servers:对应broker服务地址。关于此,有两种说法。

新版消费者通过brokers而不是zookeeper是因为对消费端而言zookeeper应该是透明的,消费端需要的是kafka里的数据而zookeeper是kafka集群内部需要的,不应被暴露给外部,当然消费者获取broker信息本质仍是通过zookeeper来获取,只是这一过程被kafka隐藏起来了,从语义上隐藏了zookeeper服务

因为zookeeper要被替代了,kafka团队不想再依赖zk了,目前,Kafka使用ZooKeeper来存储分区和代理的元数据,并选择一个Broker作为Kafka控制器,而希望通过删除对ZooKeeper的依赖,将使Kafka能够以一种更具伸缩性和健壮性的方式管理元数据,实现对更多分区的支持,它还将简化Kafka的部署和配置。但是目前我们还是需要Zookeeper(最新版kafka是2.3.1)

  1. consumer.enable.auto.commit:控制消费者的提交模式,默认为自动提交,改为手动提交时,要配合 listener.ack-mode使用。
  2. consumer.auto.offset.reset:

earliest
当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
latest
当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
none
topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常

  1. consumer.concurrency:在侦听器容器中运行的线程数
  2. listener.ack-mode:提交模式
  • RECORD
    每处理一条commit一次
  • BATCH(默认)
    每次poll的时候批量提交一次,频率取决于每次poll的调用频率
  • TIME
    每次间隔ackTime的时间去commit(跟auto commit interval有什么区别呢?)
  • COUNT
    累积达到ackCount次的ack去commit
  • COUNT_TIME
    ackTime或ackCount哪个条件先满足,就commit
  • MANUAL
    listener负责ack,但是背后也是批量上去
  • MANUAL_IMMEDIATE
    listner负责ack,每调用一次,就立即commit
  1. 其他参考:kafka消费者配置参数

4 生产消费使用

4.1 生产者

@Autowired
KafkaTemplate kafkaTemplate;

直接调用kafkaTemplatesend方法即可

4.2 消费者

这里的用法就比较粗糙,应该把topics和id省掉,使用配置中的topics和group.id

@Component
public class ModelConsumer {
     
    @Autowired
    KafkaTemplate kafkaTemplate;

    @KafkaListener(topics = Constant.MODEL_RECEIVE_TOPIC, id = Constant.MODEL_RECEIVE_GROUP,
    containerFactory = "kafkaListenerModelContainerFactory")
    public void receiveMessage(ConsumerRecord record, Acknowledgment ack) {
     
    	//提交偏移量
        ack.acknowledge();
    }
}

5 常见问题

5.1 消费者只有一个在消费,其他没有接收到消息

  1. 可能是分区数量没有修改,应该在kafka配置文件中修改创建时主题的分区数,或借助kafka-topics.bat (windows环境下),命令行修改主题的分区数量
  2. 这种情况我没有遇到,但是值得注意:3个分区,2个消费者,却只有一个消费者在消费数据

5.2 没有正确地手动消费数据

  1. 检查consumer.enable.auto.commit
  2. 检查ack_mode,如果按照网上的使用MANUAL,也无法手动控制消费者提交偏移
  3. 检查消费者的接收消息函数,建议参考上文,实测使用传递参数为(String msg, Acknowledgment ack)的情况下,提交偏移量有问题

你可能感兴趣的:(kafka,kafka,java,spring)