SpringCloud整合Kafka(包括使用用户名密码)
最近小哥的项目要接入其他数据来源,是通过kafka传输的,所以记录下最近的一些整合记录。本次笔记只记录基本用法,无深入研究笔记
首先pom引入kafka
不多废话直接上代码
// 我这里使用项目匹配的kafka版本2.8.8,所以没有写版本号
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
编写配置代码
我这边将主要的配置信息都写到了nacos的配置文件中
消费者配置
// 这里的nacos配置内容涉及到敏感信息,所以我会脱敏后放到最后一部分
// 需要的同学可以参考
@SpringBootConfiguration
public class KafkaConsumerConfig {
@Value("${spring.kafka.consumer.bootstrap-servers}")
private String bootstrapServers;
@Value("${spring.kafka.consumer.group-id}")
private String groupId;
@Value("${spring.kafka.consumer.enable-auto-commit}")
private boolean enableAutoCommit;
@Value("${spring.kafka.properties.session.timeout.ms}")
private String sessionTimeout;
@Value("${spring.kafka.properties.max.poll.interval.ms}")
private String maxPollIntervalTime;
@Value("${spring.kafka.consumer.max-poll-records}")
private String maxPollRecords;
@Value("${spring.kafka.consumer.auto-offset-reset}")
private String autoOffsetReset;
@Value("${spring.kafka.listener.concurrency}")
private Integer concurrency;
@Value("${spring.kafka.listener.missing-topics-fatal}")
private boolean missingTopicsFatal;
@Value("${spring.kafka.listener.poll-timeout}")
private long pollTimeout;
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> propsMap = new HashMap<>(16);
// 服务器地址,不多说配置直接用
propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
// groupId不多说,直接用
propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
//是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
//自动提交的时间间隔,自动提交开启时生效
propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "2000");
//该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
//我们使用latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
//两次poll之间的最大间隔,默认值为5分钟。如果超过这个间隔会触发reBalance
propsMap.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalTime);
//这个参数定义了poll方法最多可以拉取多少条消息,默认值为500。
propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
//当broker多久没有收到consumer的心跳请求后就触发reBalance,默认值是10s
propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimeout);
//序列化(我们这边使用StringDeserializer,与生产者保持一致)
propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
// 下面四个参数是用户名密码的参数,没有用户名密码可以去掉以下配置
propsMap.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name);
propsMap.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
propsMap.put("java.security.auth.login.config", "10000");
// 这里username设置用户名, password设置密码我写死到代码里了,可以更改为nacos配置
propsMap.put(SaslConfigs.SASL_JAAS_CONFIG, String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"admin1234\";"));
return propsMap;
}
// 消费者工厂,将配置信息加载进去
@Bean("consumerFactory")
public DefaultKafkaConsumerFactory consumerFactory(){
return new DefaultKafkaConsumerFactory(consumerConfigs());
}
@Bean("listenerContainerFactory")
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Object, Object>> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
//在侦听器容器中运行的线程数,一般设置为 机器数*分区数
factory.setConcurrency(concurrency);
//消费监听接口监听的主题不存在时,默认会报错,所以设置为false忽略错误
factory.getContainerProperties().setMissingTopicsFatal(missingTopicsFatal);
//自动提交关闭,需要设置手动消息确认
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
factory.getContainerProperties().setPollTimeout(pollTimeout);
return factory;
}
}
生产者配置
//
@SpringBootConfiguration
public class KafkaProviderConfig {
@Value("${spring.kafka.producer.bootstrap-servers}")
private String bootstrapServers;
@Value("${spring.kafka.producer.acks}")
private String acks;
@Value("${spring.kafka.producer.retries}")
private String retries;
@Value("${spring.kafka.producer.batch-size}")
private String batchSize;
@Value("${spring.kafka.producer.buffer-memory}")
private String bufferMemory;
@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>(16);
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
//响应模式,我们使用acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
props.put(ProducerConfig.ACKS_CONFIG, acks);
//发生错误后,消息重发的次数,开启事务必须大于0
props.put(ProducerConfig.RETRIES_CONFIG, retries);
//当多个消息发送到相同分区时,生产者会将消息打包到一起,以减少请求交互. 而不是一条条发送
props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
//有的时刻消息比较少,过了很久,比如5min也没有凑够16KB,这样延时就很大,所以需要一个参数. 再设置一个时间,到了这个时间,
props.put(ProducerConfig.LINGER_MS_CONFIG, "5000");
//生产者内存缓冲区的大小
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);
//用户名密码配置,没有用户名密码可以去掉以下配置
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name);
props.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
props.put("java.security.auth.login.config", "10000");
// 可以在nacos配置文件中配置
props.put(SaslConfigs.SASL_JAAS_CONFIG, String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"admin1234\";"));
return props;
}
// 生产者工厂
@Bean("kafkaProduceFactory")
public ProducerFactory<Object, Object> producerFactory() {
DefaultKafkaProducerFactory<Object, Object> factory = new DefaultKafkaProducerFactory<>(producerConfigs());
factory.setTransactionIdPrefix("kafkaTx-");
return factory;
}
// 事务处理
// 这里的事务处理会和项目中的其他事务起冲突,所以我一般会把@Bean去掉,不用spring代理
@Bean("kafkaTransactionManager")
@Primary
public KafkaTransactionManager<Object, Object> kafkaTransactionManager(ProducerFactory<Object, Object> producerFactory) {
return new KafkaTransactionManager<Object, Object>(producerFactory);
}
@Bean
public KafkaTemplate<Object, Object> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
写接收和发送代码
推送消息DOMO:
@RestController
@RequestMapping("/provider")
//这个注解代表这个类开启Springboot事务,因为我们在Kafka的配置文件开启了Kafka事务,不然会报错
@Transactional(rollbackFor = RuntimeException.class)
public class KafkaController {
private final KafkaTemplate<Object, Object> kafkaTemplate;
public KafkaController(KafkaTemplate<Object, Object> kafkaTemplate, KafkaSendResultHandler kafkaSendResultHandler) {
this.kafkaTemplate = kafkaTemplate;
//回调方法、异常处理
this.kafkaTemplate.setProducerListener(kafkaSendResultHandler);
}
@RequestMapping("/send")
public void sendMultiple() {
String message = "发送到Kafka的消息";
kafkaTemplate.send("KAFKA_TEST_TOPICS", message );
System.out.println(message );
}
/**
* Kafka提供了多种构建消息的方式
* @throws ExecutionException
* @throws InterruptedException
* @throws TimeoutException
*/
public void SendDemo() throws ExecutionException, InterruptedException, TimeoutException {
//后面的get代表同步发送,括号内时间可选,代表超过这个时间会抛出超时异常,但是仍会发送成功
kafkaTemplate.send("topic1", "发给topic1").get(1, TimeUnit.MILLISECONDS);
//使用ProducerRecord发送消息
ProducerRecord<Object, Object> producerRecord = new ProducerRecord<>("topic.quick.demo", "use ProducerRecord to send message");
kafkaTemplate.send(producerRecord);
//使用Message发送消息
Map<String, Object> map = new HashMap<>();
map.put(KafkaHeaders.TOPIC, "topic.quick.demo");
map.put(KafkaHeaders.PARTITION_ID, 0);
map.put(KafkaHeaders.MESSAGE_KEY, 0);
GenericMessage<Object> message = new GenericMessage<>("use Message to send message", new MessageHeaders(map));
kafkaTemplate.send(message);
}
}
接收消息DEMO:
@RestController()
public class KafkaConsumerListener extends BaseController {
@Resource
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
/**
* 监听kafka消息
*
* 使用autoStartup = "false"必须指定id
*/
@KafkaListener(id = "${spring.kafka.consumer.group-id}", topics = {"KAFKA_TEST_TOPICS"}, autoStartup = "false")
public void listenTopics(ConsumerRecord<Object, Object> consumerRecord, Acknowledgment ack) {
try {
System.out.println("listenTopics接受消息:" + consumerRecord.value());
//手动确认
ack.acknowledge();
} catch (Exception e) {
System.out.println("消费失败:" + e);
}
}
/**
* 下面的方法可以手动操控kafka的队列监听情况
* 先发送一条消息,因为autoStartup = "false",所以并不会看到有消息进入监听器。
* 接着启动监听器,/start/testGroup。可以看到有一条消息进来了。
* start是开启监听,stop是关闭监听
* pause是暂停监听,resume是继续监听
* @param listenerId consumer的group-id
*/
@RequestMapping("/pause/{listenerId}")
public void pause(@PathVariable String listenerId) {
try {
Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).pause();
} catch (Exception e) {
e.printStackTrace();
}
}
@RequestMapping("/resume/{listenerId}")
public void resume(@PathVariable String listenerId) {
try {
Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).resume();
} catch (Exception e) {
e.printStackTrace();
}
}
@RequestMapping("/start/{listenerId}")
public void start(@PathVariable String listenerId) {
try {
Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).start();
} catch (Exception e) {
e.printStackTrace();
}
}
@RequestMapping("/stop/{listenerId}")
public void stop(@PathVariable String listenerId) {
try {
Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
现在一个简单的demo就做好了,大家快去搭建自己的kafka试试吧!
异常处理
后续就是设计自己专属的处理日志,可以输出日志更好的定位问题
生产者推送处理
@Component
public class KafkaSendResultHandler implements ProducerListener<Object, Object> {
@Override
public void onSuccess(ProducerRecord producerRecord, RecordMetadata recordMetadata) {
System.out.println("消息发送成功:" + producerRecord.toString());
}
@Override
public void onError(ProducerRecord producerRecord, @Nullable RecordMetadata recordMetadata, Exception exception) {
System.out.println("消息发送失败:" + producerRecord.toString() + exception.getMessage());
}
}
消费者异常处理
@Component
public class KafkaConsumerListenerError implements KafkaListenerErrorHandler {
@Override
@NonNull
public Object handleError(Message<?> message, ListenerExecutionFailedException e) {
return new Object();
}
@Override
public Object handleError(Message<?> message, ListenerExecutionFailedException exception, Consumer<?, ?> consumer) {
System.out.println("消息详情:" + message);
System.out.println("异常信息::" + exception);
System.out.println("消费者详情::" + consumer.groupMetadata());
System.out.println("监听主题::" + consumer.listTopics());
return KafkaListenerErrorHandler.super.handleError(message, exception, consumer);
}
}
感兴趣的小伙伴们快去试试吧
配置文件
小伙伴们一定要注意缩进
spring:
producer:
# Kafka服务器
bootstrap-servers: 127.0.0.1:9092
transaction-id-prefix: kafkaTx-
retries: 3
acks: all
batch-size: 16384
buffer-memory: 1024000
consumer:
# Kafka服务器
bootstrap-servers: 127.0.0.1:9092
group-id: testGroup
auto-offset-reset: latest
enable-auto-commit: false
max-poll-records: 3
properties:
max:
poll:
interval:
ms: 600000
session:
timeout:
ms: 10000
listener:
concurrency: 4
ack-mode: manual_immediate
missing-topics-fatal: false
poll-timeout: 600000
附 - windows部署kafka
当我们没有kafka服务器,就只能部署到本地玩一下
kafka部署需要先部署zookeeper,现在kafka高版本包都会带有zookeeper所以我们直接去官网下载kafka,小哥下载的是2.8.2,下面是下载地址
图片:
解压之后我们打开文件夹,选择config进入文件夹,打开server.properties找到log.dirs项,这一项是日志地址,我们记录下这一项后续会用到:
我们找到log.dirs的地址,每次启动前将该地址下的kafka日志文件全部清理,否则启动会报错无法写入(为什么会报错呢?小编找了好多资料总结了一句话,kafka一开始就不打算提供用户在windows上面部署)
好了我们回到kafka目录,小编的是 E:\kafka\kafka_2.12-2.8.2,在当前目录打开CMD,开始启动我们的kafka
启动kafka先启动zookeeper
.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties
zookeeper启动完成后,不要关闭CMD,重新另起一个CMD启动kafka
.\bin\windows\kafka-server-start.bat .\config\server.properties
好了,启动完毕了小伙伴们就可以开始玩自己的kafka了
如果小伙伴们觉得老铁的文章还不错或者对您有一丁丁点的帮助就留下个赞吧
不争人中龙凤,只做草原牛马,老铁们江湖再见!