前言
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启动报错等等问题。