前言

kafka是目前IT中常用组件,java+kafka、python+kafka、lua+kafka、go+kafka是常见组合。

kafka常用语业务层的异步数据处理。

也用于agent层服务器数据的采集。

大数据,ELK中kafka更是核心组件,flink+kafka进行海量数据的处理。

云计算心跳管理,IT七大对象自动化监管,lua+kafka也是常见场景。

kafka很牛,虽然和redis内存库不同,采用磁盘进行数据存储但是读写速度超强,因为他是顺序读写。

所以使用场景下,快速掌握kafka的使用及其重要,当然至于Kafka的saas化不需要太多考虑,只需要直接云上申请服务集群即可。

使用场景说明-springboot+kafka

在Spring Boot微服务集成Kafka客户端spring-kafka-2.8.2操作Kafka。
使用Spring封装的KafkaTemplate操作Kafka生产者Producer。

使用Spring封装的@KafkaListener操作Kafka的消费者Consumer。

JDK 1.8,

Spring Boot 2.7.11, -- springboot 3.x之后有了巨大变化,但是目前企业用的主流JDK还是8,JDK21虽然很新,但是企业追求的是稳定。

spring-kafka-2.8.2。

基础概念

Event:An event records the fact that "something happened" in the world or in your business. It is also called record or message in the documentation.

Broker:一个Kafka节点就是一个broker;多个Broker可以组成一个Kafka集群。

Topic:Kafka根据Topic对消息进行归类,发布到Kafka的每条消息都需要指定一个Topic。

Producer:消息生产者,向Broker发送消息的客户端。

Consumer:消息消费者,从Broker读取消息的客户端。

ConsumerGroup:每个Consumer属于一个特定的ConsumerGroup,一条消息可以被多个不同的ConsumerGroup消费;但是一个ConsumerGroup中只能有一个Consumer能够消费该消息。

Partition:一个topic可以分为多个partition,每个partition内部消息是有序的。

publish:发布,使用Producer向Kafka写入数据。

subscribe:订阅,使用Consumer从Kafka读取数据。

kafka使用demo

添加依赖

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

增加kafka配置

这里使用的application.properties文件,当然目前yaml也非常流行,但是自动化部署中yaml不太好用,对缩进要求太严格

其实最完美的配置是json,但是目前看业界很少用,只有前端vue项目,或者底层go-agent类项目,采用json配置的较多。

特别说明:

网上基本千篇一律的demo都是自动装配,标准配置,但是在真实的IT生产中是不可能的。

application.properties文件也不会使用标准的K-V配置,自动装配。

就拿一个出海项目为例可能几十个profile配置文件,真实业务也往往使用多集群,多实例,账号密码还要加密认证,使用3~4级工作秘钥进行安全加固。

往往都需要手工装载,初始化bean,然后再使用。

spring.kafka.demo.bootstrap-servers=192.168.19.203:29001
spring.kafka.demo.username=demo
spring.kafka.demo.password=demo#123
spring.kafka.demo.kafka.ssl.truststore.password=demo@123
spring.kafka.demo.consumer.group-id=test_group_id
spring.kafka.demo.enable-auto-commit=true
spring.kafka.demo.auto-offset-rest=latest
spring.kafka.demo.auto-commit-interval=100
spring.kafka.demo.max-poll-records=100

装载bean

我们不使用:spring-boot-autoconfigure-2.6.3.jar,@ConfigurationProperties 自动去状态bean。

springboot提供了大量的data-template,有个大的json罗列了各种组件start的模板。

package com.wht.test.kafka;

import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.config.SslConfigs;
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.system.ApplicationHome;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
 * desc
 *
 * @Author 红尘过客
 * @DateTime 2023-07-29 21:54:25
 */
@Configuration
@Slf4j
public class KafkaConfig {
    @Value("$(spring.kafka.demo.bootstrap-servers)")
    private String kafkaStrapServers;

    @Value("$(spring.kafka.demo.username:null)")
    private String username;

    @Value("$(spring.kafka.demo.password:null)")
    private String password;

    @Value("$(spring.kafka.demo.kafka.ssl.truststore.password:null)")
    private String jskPassword;

    @Value("$(spring.kafka.demo.consumer.group-id)")
    private String testGroupId;

    @Value("$(spring.kafka.demo.enable-auto-commit)")
    private String enableAutoCommit;

    @Value("$(spring.kafka.demo.auto-offset-rest)")
    private String autoOffsetRest;

    @Value("$(spring.kafka.demo.auto-commit-interval)")
    private String autoCommitInterval;

    @Value("$(spring.kafka.demo.max-poll-records)")
    private String maxPollRecords;

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

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(getConsumerFactory());
        factory.setConcurrency(3);
        factory.setBatchListener(true);
        factory.getContainerProperties().setPollTimeout(3000);
        return factory;
    }

    private ProducerFactory<String, String> getProducerFactory() {
        return new DefaultKafkaProducerFactory<String, String>(producerConfig());
    }

    private ConsumerFactory<String, String> getConsumerFactory() {
        return new DefaultKafkaConsumerFactory<String, String>(consumerConfig());
    }

    private Map<String, Object> producerConfig() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaStrapServers);
        props.put(ProducerConfig.RETRIES_CONFIG, 0);
        props.put(ProducerConfig.ACKS_CONFIG, "1");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        setKafkaSecurity(props);
        return props;
    }

    private void setKafkaSecurity(Map<String, Object> props) {
        if (username != null) {
            props.put("security.protocol", "SASL.SSL");
            props.put("sasl.mechanism", "PLAIN");
            props.put("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule " +
                    "required username=\"" + username + "\" password=\"" + password + "\";");
            props.put("ssl.truststore.password", jskPassword);
            props.put("ssl.endpoint.identification.algorithm", "");
            props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, getConfigFilePath());
        }
    }

    private Map<String, Object> consumerConfig() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaStrapServers);
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, testGroupId);
        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitInterval);
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetRest);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        setKafkaSecurity(props);
        return props;
    }

    private String getConfigFilePath() {
        ApplicationHome applicationHome = new ApplicationHome(this.getClass());
        String rootPath = applicationHome.getSource().getParentFile().toString();
        String configFilePath = rootPath + File.separator + "resources" + File.separator + "client.truststore.jks";
        File configFile = new File(configFilePath);
        if (!configFile.exists()) {
            configFilePath = rootPath + File.separator + "config" + File.separator + "client.truststore.jks";
        }
        log.info("jks file path = {}", configFilePath);
        return configFilePath;
    }
}

推送消息示例

package com.wht.test.kafka;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * 发送消息demo
 *
 * @Author 红尘过客
 * @DateTime 2023-07-29 22:39:58
 */
@Component
@Slf4j
public class ProducerTest {
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    private int count;

    @Scheduled(cron = "*/30 * * * * ?")
    public void run() {
        for (int i = 0; i < 100; i++) {
            kafkaTemplate.send("test_kafka_topic", "test_message_" + count++);
            log.info("推送消息成功。。。。。。。。。。。。。。", count);
        }
    }
}

消费消息示例

package com.wht.test.kafka;

import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * kafka消费验证
 *
 * @Author 红尘过客
 * @DateTime 2023-07-29 22:43:04
 */
@Component
@Slf4j
public class KafkaListenTest {

    @KafkaListener(topics = "", containerFactory = "test_kafka_topic", clientIdPrefix = "test_kafka_topic-", concurrency = "3")
    public void Listen(List<String> list) {
        for (int i = 0; i < list.size(); i++) {
            log.info("批量消费消息成功-{}", list.size());
            log.info(list.get(i));
        }

    }
}

暂时记录

kafka的使用很简单,但是也会遇到各种问题。

例如2分钟推送5亿数据,消费过程中可能遇到OOM,cpu爆掉,参数配置不合理,消费组掉线。

JKS启动报错等等问题。