1. 前言
对于使用Apache Kafka的Spring项目,我们在Spring核心提供了Kafka消息的集成。提供了公共的接入“模板”,作为消息发送的高级抽象层,还为消息的POJO提供支持。
3. 介绍
本示例提供一个快速的入门例子,直接运行即可。
3.1. 快速游览(Quick Tour for the Impatient)
这是Spring Kafka的五分钟速览。
先决条件:您的Apache Kafka已经安装并且运行了。然后,您必须有spring-kafka JAR及其所有依赖项。 最简单的方法是在构建工具中声明一个依赖项。以下示例显示了如何使用Maven
进行操作:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.4.1.RELEASE</version>
</dependency>
Gradle的引入:
compile 'org.springframework.kafka:spring-kafka:2.4.1.RELEASE'
使用Spring Boot时,如果忽略该版本,则Spring Boot将自动引入与您的Boot版本兼容的正确版本:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
Gradle的方式:
compile 'org.springframework.kafka:spring-kafka'
3.1.1. 兼容性
适用于以下的版本:
- Apache Kafka Clients 2.2.0
- Spring Framework 5.2.x
- 最小的 Java 版本: 8
3.1.2. 一个非常非常快速的例子
如下例所示,您可以使用普通Java发送和接收消息:
package com.example.kafka;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.IntegerDeserializer;
import org.apache.kafka.common.serialization.IntegerSerializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.listener.ContainerProperties;
import org.springframework.kafka.listener.KafkaMessageListenerContainer;
import org.springframework.kafka.listener.MessageListener;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
class KafkaTests01 {
private Logger logger = LoggerFactory.getLogger(getClass());
private String group = "group01";
private String topic1 = "topic1";
@Test
public void testAutoCommit() throws Exception {
logger.info("Start auto");
// 启动消费者
ContainerProperties containerProps = new ContainerProperties("topic1", "topic2");
final CountDownLatch latch = new CountDownLatch(4);
containerProps.setMessageListener(new MessageListener<Integer, String>() {
@Override
public void onMessage(ConsumerRecord<Integer, String> message) {
logger.info("received: " + message);
latch.countDown();
}
});
KafkaMessageListenerContainer<Integer, String> container = createContainer(containerProps);
container.setBeanName("testAuto");
container.start(); // 启动消费者
Thread.sleep(1000); // wait a bit for the container to start
// 启动生产者
KafkaTemplate<Integer, String> template = createTemplate();
template.setDefaultTopic(topic1);
template.sendDefault(0, "foo");
template.sendDefault(2, "bar");
template.sendDefault(0, "baz");
template.sendDefault(2, "qux");
template.flush();
assertTrue(latch.await(60, TimeUnit.SECONDS));
container.stop(); // 关闭消费者
logger.info("Stop auto");
}
private KafkaMessageListenerContainer<Integer, String> createContainer(ContainerProperties containerProps) {
Map<String, Object> props = consumerProps();
DefaultKafkaConsumerFactory<Integer, String> cf = new DefaultKafkaConsumerFactory<Integer, String>(props);
KafkaMessageListenerContainer<Integer, String> container = new KafkaMessageListenerContainer<>(cf, containerProps);
return container;
}
private KafkaTemplate<Integer, String> createTemplate() {
Map<String, Object> senderProps = senderProps();
ProducerFactory<Integer, String> pf = new DefaultKafkaProducerFactory<Integer, String>(senderProps);
KafkaTemplate<Integer, String> template = new KafkaTemplate<>(pf);
return template;
}
private Map<String, Object> consumerProps() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, group);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100");
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return props;
}
private Map<String, Object> senderProps() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.RETRIES_CONFIG, 0);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return props;
}
}
3.1.3. 使用Java配置
你也可以使用Java的Spring配置来完成与上一个示例中相同的效果。 以下示例显示了如何执行此操作:
@Autowired
private Listener listener;
@Autowired
private KafkaTemplate<Integer, String> template;
@Test
public void testSimple() throws Exception {
template.send("annotated1", 0, "foo");
template.flush();
assertTrue(this.listener.latch1.await(10, TimeUnit.SECONDS));
}
@Configuration
@EnableKafka
public class Config {
@Bean
ConcurrentKafkaListenerContainerFactory<Integer, String>
kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
@Bean
public ConsumerFactory<Integer, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
...
return props;
}
@Bean
public Listener listener() {
return new Listener();
}
@Bean
public ProducerFactory<Integer, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
...
return props;
}
@Bean
public KafkaTemplate<Integer, String> kafkaTemplate() {
return new KafkaTemplate<Integer, String>(producerFactory());
}
}
public class Listener {
private final CountDownLatch latch1 = new CountDownLatch(1);
@KafkaListener(id = "foo", topics = "annotated1")
public void listen1(String foo) {
this.latch1.countDown();
}
}
3.1.4. Spring Boot更简单的方式
Spring Boot可以更加简单。 下面的Spring Boot应用示例将三个消息发送到一个主题,然后接收它们,然后停止:
package com.example.kafka.demo03;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@SpringBootApplication
public class Application implements CommandLineRunner {
public static Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args).close();
}
@Autowired
private KafkaTemplate<String, String> template;
private final CountDownLatch latch = new CountDownLatch(3);
@Override
public void run(String... args) throws Exception {
this.template.send("myTopic", "foo1");
this.template.send("myTopic", "foo2");
this.template.send("myTopic", "foo3");
latch.await(60, TimeUnit.SECONDS);
logger.info("All received");
}
@KafkaListener(topics = "myTopic")
public void listen(ConsumerRecord<?, ?> cr) throws Exception {
logger.info(cr.toString());
latch.countDown();
}
}
配置application.properties
:
spring.kafka.consumer.group-id=foo
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.listener.missing-topics-fatal=false
spring.kafka.consumer.group-id
指定消费者组id。spring.kafka.consumer.auto-offset-reset
确保新的消费者组能获得我们之前发送的消息,为了测试方便(生产配置latest,只获取最新的消息)。spring.kafka.listener.missing-topics-fatal
监听的topic如果不存在,则不报错
private final CountDownLatch latch = new CountDownLatch(3);
我问一下这个为什么设置成3呢
因为测试就发送了3条消息,所以接收到3条之后,就可以结束了。
CountDownLatch
,详细的用法参见:https://www.orchome.com/1065那这样会不会在消费完这三条消息之前,其他的消息不能再进入队列呀
这个例子是不是这个意思呀
因为这个例子就发送3个消息,所以到了3个,进程就销毁结束了。
大佬 问一下 第二种Java配置方式 消费者是怎么消费消息的?
什么意思?
实用工具 3.1.3的例子 消息是怎么消费的?
你好,
这几天通过您的kafka分享,对kafka也大概有了了解.并且也写出了demo
现在有些问题想请教您一下.
关于springBoot和kafka集成时,各个依赖版本的问题,
在刚开始搭建环境的时候,经常遇到classNotFound的问题,搜了一下有说是依赖版本之间不兼容的问题
从spring官网给出了spring-kafka和springBoot的版本对应表中,我可以知道这两个依赖的兼容版本
现在的问题是,
kafka的依赖kafka-clients和kafka-streams,这两个依赖,版本兼容性我可以怎么获取?
我通过您的分享,看到了kafka-clients 0.10.0可以使用kafka-streams 2.5
现在我写了个streams的wordCounts-demo.但是启动提示 org.apache.kafka.common.internals.Topic.
编译是通过的.
大佬如果知道原因,可以指导我一下解决思路吗.我猜测还是依赖的版本兼容问题
还有个问题kafka-clients这个依赖对应的是不是kafka的版本?
org.apache.kafka.common.internals.Topic. 这个问题是我stream依赖 是0.10.0.1的时候报的.
现在我改成2.5..编译都无法通过.
我搭建的demo是以kafka 2.12-0.10.0为准的
SpringBoot写错啦
thanks,已修正。