本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客
微信公众号小白AI
或者网站 https://xiaobaiai.net
文章目录
1 前言
本篇文章内容很全,很长,很细!不要心急,慢慢看!我都写完了,相信你看完肯定可以的,有任何问题可以随时交流!
本篇文章内容很全,很长,很细!不要心急,慢慢看!我都写完了,相信你看完肯定可以的,有任何问题可以随时交流!
本篇文章内容很全,很长,很细!不要心急,慢慢看!我都写完了,相信你看完肯定可以的,有任何问题可以随时交流!
本篇文章主要介绍Spring Kafka的常用配置、主题自动创建、发布消息到集群、订阅消息(群组)、流处理配置以及嵌入式Kafka做测试配置相关内容,最后通过两种方式去实现消息的发布和订阅功能,其中一种是基于Spring Integration
方式。本文内容基于Spring Kafka2.3.3文档及Spring Boot Kafka相关文档,Spring创建了一个名为Spring kafka
的项目,它封装了Apache的kafka客户端部分(生产者/消费者/流处理等),以便在Spring项目中快速集成kafka,Spring-Kafka项目提供了Apache Kafka自动化配置,通过Spring Boot的简化配置(以spring.kafka.*
作为前缀的配置参数),在Spring Boot中使用Kafka特别简单。并且Spring Boot还提供了一个嵌入式Kafka代理方便做测试。
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=myGroup
实现下面的所涉及到的功能实现,需要有如下环境:
- Java运行或开发环境(JRE/JDK)
- Kafka安装成功
更多的配置可以参考《Kafka,ZK集群开发或部署环境搭建及实验》
这一篇文章。
本文尽量做到阐述逻辑清晰,主要路线就是全局介绍Spring Kafka的主要功能及重点配置,而Spring Boot对Spring Kafka进一步简化配置,通过Spring Boot中的Kafka几大注解实现发布订阅功能,同时通过Spring Integration + 自定义Kafka配置方式实现一个较为复杂的Kafka发布订阅功能,本文通过自己实验和整理了较久的时间,涵盖了Spring Kafka大部分内容,希望大家耐心读下来,有什么问题随时反馈,一起学习。
2 Spring Kafka功能概览
Spring Kafka、Spring Integration和Kafka客户端版本联系或者兼容性如下(截至2019年12月9日):
Spring for Apache Kafka | Spring Integration for Apache Kafka Version | kafka-clients |
---|---|---|
2.3.x | 3.2.x | 2.3.1 |
2.2.x | 3.1.x | 2.0.1, 2.1.x, 2.2.x |
2.1.x | 3.0.x | 1.0.x, 1.1.x, 2.0.0 |
1.3.x | 2.3.x | 0.11.0.x, 1.0.x |
具体更多版本特点可以看官网,spring kafka当前最新为2.3.4版本。
Spring Kafka相关的注解有如下几个:
注解类型 | 描述 |
---|---|
EnableKafka | 启用由AbstractListenerContainerFactory 在封面(covers)下创建的Kafka监听器注解端点,用于配置类; |
EnableKafkaStreams | 启用默认的Kafka流组件 |
KafkaHandler | 在用KafkaListener注解的类中,将方法标记为Kafka消息监听器的目标的注解 |
KafkaListener | 将方法标记为指定主题上Kafka消息监听器的目标的注解 |
KafkaListeners | 聚合多个KafkaListener注解的容器注解 |
PartitionOffset | 用于向KafkaListener添加分区/初始偏移信息 |
TopicPartition | 用于向KafkaListener添加主题/分区信息 |
如使用@EnableKafka
可以监听AbstractListenerContainerFactory
子类目标端点,如ConcurrentKafkaListenerContainerFactory
是AbstractKafkaListenerContainerFactory
的子类。
public class ConcurrentKafkaListenerContainerFactory<K,V>
extends AbstractKafkaListenerContainerFactory<ConcurrentMessageListenerContainer<K,V>,K,V>
@Configuration
@EnableKafka
public class AppConfig {
@Bean
public ConcurrentKafkaListenerContainerFactory myKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory();
factory.setConsumerFactory(consumerFactory());
factory.setConcurrency(4);
return factory;
}
// other @Bean definitions
}
@EnableKafka
并不是在Spring Boot中启用Kafka必须的,Spring Boot附带了Spring Kafka的自动配置,因此不需要使用显式的@EnableKafka
。如果想要自己实现Kafka配置类,则需要加上@EnableKafka
,如果你不想要Kafka自动配置,比如测试中,需要做的只是移除KafkaAutoConfiguration
:
@SpringBootTest("spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration")
2.1 自动创建主题
💡 要在应用启动时就创建主题,可以添加NewTopic
类型的Bean。如果该主题已经存在,则忽略Bean。
2.2 发送消息
Spring的KafkaTemplate
是自动配置的,你可以直接在自己的Bean中自动连接它,如下例所示:
@Component
public class MyBean {
private final KafkaTemplate kafkaTemplate;
@Autowired
public MyBean(KafkaTemplate kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
// ...
}
KafkaTemplate
包装了一个生产者,并提供了向kafka主题发送数据的方便方法。提供异步和同步(发送阻塞)方法,异步(发送非阻塞)方法返回ListenableFuture
,以此监听异步发送状态,成功还是失败,KafkaTemplate提供如下接口:
ListenableFuture<SendResult<K, V>> sendDefault(V data);
ListenableFuture<SendResult<K, V>> sendDefault(K key, V data);
ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, K key, V data);
ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, Long timestamp, K key, V data);
ListenableFuture<SendResult<K, V>> send(String topic, V data);
ListenableFuture<SendResult<K, V>> send(String topic, K key, V data);
ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, V data);
ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, V data);
ListenableFuture<SendResult<K, V>> send(ProducerRecord<K, V> record);
ListenableFuture<SendResult<K, V>> send(Message<?> message);
Map<MetricName, ? extends Metric> metrics();
List<PartitionInfo> partitionsFor(String topic);
<T> T execute(ProducerCallback<K, V, T> callback);
// Flush the producer.
void flush();
interface ProducerCallback<K, V, T> {
T doInKafka(Producer<K, V> producer);
}
sendDefault
API 要求已向模板提供默认主题。部分API接受一个时间戳作为参数,并将该时间戳存储在记录中,如何存储用户提供的时间戳取决于Kafka主题上配置的时间戳类型,如果主题配置为使用CREATE_TIME
,则记录用户指定的时间戳(如果未指定则生成)。如果将主题配置为使用LOG_APPEND_TIME
,则忽略用户指定的时间戳,并且代理将添加本地代理时间。metrics
和 partitionsFor
方法委托给底层Producer上的相同方法。execute方法提供对底层生产者的直接访问
要使用模板,可以配置一个生产者工厂并在模板的构造函数中提供它。下面的示例演示了如何执行此操作:
@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, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// See https://kafka.apache.org/documentation/#producerconfigs for more properties
return props;
}
@Bean
public KafkaTemplate<Integer, String> kafkaTemplate() {
// KafkaTemplate构造函数中输入生产者工厂配置
return new KafkaTemplate<Integer, String>(producerFactory());
}
然后,要使用模板,可以调用其方法之一发送消息。
当你使用包含Message<?>
参数的方法时,主题、分区和键信息在消息头中提供,有如下子项:
KafkaHeaders.TOPIC
KafkaHeaders.PARTITION_ID
KafkaHeaders.MESSAGE_KEY
KafkaHeaders.TIMESTAMP
如访问头部信息中某一项信息:
public void handleMessage(Message<?> message) throws MessagingException {
LOGGER.debug("===Received Msg Topic: {}", message.getHeaders().get(KafkaHeaders.TOPIC));
}
可选的功能是,可以使用ProducerListener
配置KafkaTemplate
,以获得带有发送结果(成功或失败)的异步回调,而不是等待将来完成。以下列表显示了ProducerListener
接口的定义:
public interface ProducerListener<K, V> {
void onSuccess(String topic, Integer partition, K key, V value, RecordMetadata recordMetadata);
void onError(String topic, Integer partition, K key, V value, Exception exception);
boolean isInterestedInSuccess();
}
默认情况下,模板配置有LoggingProducerListener
,它只记录错误,在发送成功时不执行任何操作。只有当isInterestedInSuccess
返回true时才调用onSuccess
。
为了方便起见,如果你只想实现其中一个方法,那么将提供抽象ProducerListenerAdapter
。对于isInterestedInSuccess
,它返回false。下面演示了异步结果回调:
public void sendMessage(String msg) {
LOGGER.info("===Producing message[{}]: {}", mTopic, msg);
ListenableFuture<SendResult<String, String>> future = mKafkaTemplate.send(mTopic, msg);
future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {
@Override
public void onSuccess(SendResult<String, String> result) {
LOGGER.info("===Producing message success");
}
@Override
public void onFailure(Throwable ex) {
LOGGER.info("===Producing message failed");
}
});
}
如果希望阻止式发送线程等待结果,可以调用future
的get()
方法。你可能希望在等待之前调用flush()
,或者为了方便起见,模板有一个带有autoFlush
参数的构造函数,该构造函数在每次发送时都会导致模板flush()
。不过,请注意,刷新可能会显著降低性能:
public void sendToKafka(final MyOutputData data) {
final ProducerRecord<String, String> record = createRecord(data);
try {
template.send(record).get(10, TimeUnit.SECONDS);
handleSuccess(data);
}
catch (ExecutionException e) {
handleFailure(data, record, e.getCause());
}
catch (TimeoutException | InterruptedException e) {
handleFailure(data, record, e);
}
}
使用DefaultKafkaProducerFactory:
如上面使用KafkaTemplate
中所示,ProducerFactory
用于创建生产者。默认情况下,当不使用事务时,DefaultKafkaProducerFactory
会创建一个供所有客户机使用的单例生产者,如KafkaProducer
javadocs中所建议的那样。但是,如果对模板调用flush(),这可能会导致使用同一个生产者的其他线程延迟。从2.3版开始,DefaultKafkaProducerFactory
有一个新属性producerPerThread
。当设置为true
时,工厂将为每个线程创建(和缓存)一个单独的生产者,以避免此问题。
当
producerPerThread
为true时,当不再需要生产者时,用户代码必须在工厂上调用closeThreadBoundProducer()
。这将实际关闭生产者并将其从ThreadLocal
中移除。调用reset()或destroy()不会清理这些生产者。
创建DefaultKafkaProducerFactory
时,可以通过调用只接受属性映射的构造函数(请参阅使用KafkaTemplate中的示例)从配置中获取键和/或值序列化器类,或者序列化程序实例可以传递给DefaultKafkaProducerFactory
构造函数(在这种情况下,所有生产者共享相同的实例)。或者,可以提供Supplier<Serializer> s
(从版本2.3开始),用于为每个生产者获取单独的Serializer
实例:
@Bean
public ProducerFactory<Integer, CustomValue> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs(), null, () -> new CustomValue