对原生 Kafka 的扩展 Kafka-Plus("KP")
框架的整体设计哲学: Less is More。旨在做到 简洁不简单。
项目 kafka-plus
示例工程
kafkaplus-spring-boot-starter-examples
kafkaplus-spring-boot3-starter-examples
0.动机
前提: 本文研究和探讨的是原生
Kafka并非实际开发使用更多的spring-kafka。
0.1.原生 kafka 的痛点
- 不管是
Producer还是Consumer甚至是Admin在使用的过程中,都不可避免的需要配置一个Property或Map对象;- 这个即使对有开发经验的老手来说,也是一个不小的负担,每次需要明确要使用哪些参数以及其对应的 参数名(
config.key) 和 参数值(config.value)
- 这个即使对有开发经验的老手来说,也是一个不小的负担,每次需要明确要使用哪些参数以及其对应的 参数名(
- 配置以及写起来也很繁琐;
- …
0.2.扩展点
基于以上种种痛点 Kafka-Plus 简称 KP 应运而生。
- 所有的
API采用流式(链式)的方式构建AdminBuilderConsumerBuilderProducerBuilderProducerRecordBuilder
Kafka常用的config.key都转义为枚举,方便大家使用- 减少使用的难度
Enumx.enums.Kafka.Modex.enums.Kafka.Bootstrapx.enums.Kafka.Bootstrap.Server
x.enums.Kafka.Consumerx.enums.Kafka.Producer
- 所有的
API都交由KafkaEngine来发起,方便管理
接下来我们就动手实操吧, Let's Go
1.使用
1.1.版本号
https://central.sonatype.com/artifact/io.github.photowey/kafka-plus/versions
https://central.sonatype.com/artifact/io.github.photowey/kafkaplus-spring-boot-starter/versions
1.2.依赖
1.2.1.Kafka-Clients
<!-- ${kafka-plus.version} == ${latest.version} -->
<dependency>
<groupId>io.github.photowey</groupId>
<artifactId>kafka-plus-engine</artifactId>
<version>${kafka-plus.version}</version>
</dependency>
1.2.2.Spring Boot
- version <
3.x
<!-- ${kafka-plus.version} == ${latest.version} -->
<dependency>
<groupId>io.github.photowey</groupId>
<artifactId>kafkaplus-spring-boot-starter</artifactId>
<version>${kafka-plus.version}</version>
</dependency>
1.2.3.Spring Boot
- version >=
3.x
<!-- ${kafka-plus.version} == ${latest.version} -->
<dependency>
<groupId>io.github.photowey</groupId>
<artifactId>kafkaplus-spring-boot3-starter</artifactId>
<version>${kafka-plus.version}</version>
</dependency>
2.APIs
2.1.KafkaEngine
如何获取一个 KafkaEngine 实例?
2.1.1.原生
KafkaEngine kafkaEngine = KafkaEngineHolder.INSTANCE.kafkaEngine()
2.1.2.Spring Boot
- 依赖查找
- 依赖注入
- 实现
KafkaEngineAware接口- 通常还可以配合
KafkaEngineGetter一起使用
- 通常还可以配合
- …
// 1.BeanFactory 依赖查找
KafkaEngine kafkaEngine = this.beanfactory.getBean(KafkaEngine.class);
// 2.Autowired 依赖注入
@Autowired
private KafkaEngine kafkaEngine;
// 3....
2.2.Admin
我们如何创建一个 Admin 实例呢?
@Test
void testAdmin() {
KafkaEngine kafkaEngine = super.kafkaEngine();
// 1.通过 KafkaEngine 实例获取 AdminService 实例
// 2.通过 AdminService 发起一个 创建 Admin 的动作
// 3.流式(链式) 配置指定的参数
try (Admin admin = kafkaEngine.adminService().createAdmin()
.boostrapServers(this.defaultBoostrapServers())
.checkConfigs(super::testBoostrapServers)
.build()) {
Assertions.assertNotNull(admin);
}
}
值得注意的是: checkXxx 方法采用 钩子函数 的方式给开发者一个机会,可以校验我们的配置是否合理或正确,完全由开发者自己掌控,框架只负责在合适的时机触发。
2.3.Topic
2.3.1.Create
创建 Topic
- 创建
Admin实例 - 创建
NewTopic实例 - 通过
Admin实例创建Topic
@Test
void testCreateTopic() throws Exception {
KafkaEngine kafkaEngine = super.kafkaEngine();
// 1.创建 Admin 实例
try (Admin admin = kafkaEngine.adminService().createAdmin()
.boostrapServers(this.defaultBoostrapServers())
.checkConfigs(super::testBoostrapServers)
.build()) {
// 2.创建 NewTopic 实例
NewTopic topic = kafkaEngine.adminService().createTopic()
.topic(this.defaultTopic())
.numPartitions(1)
.replicationFactor(1)
.build();
// 3.通过 Admin 实例创建 Topic
CreateTopicsResult topicsResult = admin.createTopics(Collections.singleton(topic));
// topicsResult...
}
}
2.3.2.Delete
删除 Topic
- 创建
Admin实例 - 通过
Admin实例删除Topic
@Test
void testDeleteTopic() throws Exception {
KafkaEngine kafkaEngine = super.kafkaEngine();
// 1.创建 Admin 实例
try (Admin admin = kafkaEngine.adminService().createAdmin()
.boostrapServers(this.defaultBoostrapServers())
.checkConfigs(super::testBoostrapServers)
.build()) {
// 2.通过 Admin 实例删除 Topic
DeleteTopicsResult topicsResult = admin.deleteTopics(Collections.singleton(this.defaultTopic()));
// topicsResult...
}
}
2.4.Consumer
2.4.1.不带订阅 Topic
- 创建
KafkaConsumer实例 - 手动订阅
Topic
@Test
void testConsumer() {
KafkaEngine kafkaEngine = this.kafkaEngine();
// 1.创建 KafkaConsumer 实例
try (KafkaConsumer<String, String> consumer = kafkaEngine.consumerService().createConsumer()
.boostrapServers(this.defaultBoostrapServers())
.keyDeserializer(StringDeserializer.class)
.valueDeserializer(StringDeserializer.class)
.autoOffsetReset(Kafka.Consumer.AutoOffsetReset.EARLIEST)
.groupId(this.defaultGroup())
.autoCommit(true)
.checkConfigs(super::testBoostrapServers)
.build()) {
// 2.订阅 Topic
consumer.subscribe(Collections.singletonList(this.defaultTopic()));
Assertions.assertNotNull(consumer);
}
}
2.4.2带订阅 Topic
- 获取
KafkaConsumer实例- 在创建
KafkaConsumer实例时,我们也可以一步到位ConsumerBuilder props(Properties props)ConsumerBuilder configs(Map<String, Object> configs)
- 如果现有的
Builder满足不了您的开发配置,设计了扩展接口开发者可以自行扩展ConsumerBuilder enhanceProps(Consumer<Properties> fx)- 如果开发者是开用
Properties一步到位配置的,可以采用该钩子函数继续扩展
- 如果开发者是开用
ConsumerBuilder enhanceConfigs(Consumer<Map<String, Object>> fx)- 如果开发者是采用
Builder模式配置或者configs(Map<String, Object> configs)模式配置,可采用该钩子函数扩展
- 如果开发者是采用
- 在创建
- 在获取
KafkaConsumer实例时,配置需要订阅的Topic - 我们同样可以采用
checkXxx钩子函数来对我们的配置进行校验
@Test
void testConsumer_with_subscribe() {
KafkaEngine kafkaEngine = this.kafkaEngine();
// 1.获取 KafkaConsumer 实例
// 2.在获取 KafkaConsumer 实例时,配置需要订阅的 Topic
try (KafkaConsumer<String, String> consumer = kafkaEngine.consumerService().createConsumer()
.boostrapServers(this.defaultBoostrapServers())
.keyDeserializer(StringDeserializer.class)
.valueDeserializer(StringDeserializer.class)
.autoOffsetReset(Kafka.Consumer.AutoOffsetReset.EARLIEST)
.groupId(this.defaultGroup())
.autoCommit(true)
// 配置需要订阅的 Topic
.subscribe(this.defaultTopic())
.checkConfigs(super::testBoostrapServers)
.build()) {
Assertions.assertNotNull(consumer);
}
}
2.5.Producer
Producer的设计思想和使用方式大致和Consumer类似,做到一致性。
- 配置键值对的序列化器
- 框架考虑到在实际开发过程中可能会使用到用对象作为消息体载体,于是基于
Jackson设计了JacksonSerializerJacksonDeserializer
@Test
void testProducer() {
StringSerializer keySerializer = new StringSerializer();
StringSerializer valueSerializer = new StringSerializer();
KafkaEngine kafkaEngine = this.kafkaEngine();
try (KafkaProducer<String, String> producer = kafkaEngine.producerService().createProducer()
.boostrapServers(this.defaultBoostrapServers())
.keySerializer(keySerializer)
.valueSerializer(valueSerializer)
.build()) {
Assertions.assertNotNull(producer);
}
}
2.6.ProducerRecord
使用方式依然和前序的风格一致。
@Test
void testProducerRecord() {
KafkaEngine kafkaEngine = this.kafkaEngine();
ProducerRecord<String, String> record = kafkaEngine.producerService().createProducerRecord()
.topic(this.defaultTopic())
.key("key-9527")
.value("value-9527")
.build();
Assertions.assertNotNull(record);
}
3.Next
- 基于
KafkaPlusProperties实现Consumer和Producer的自动配置
4.思考中
接下来
KP将会考虑是否对Kafka的消费端再次进行增强。
- 支持类似于
@KafkaListener的注解模式- 在合适的场景下开启并发消费,提升系统的消费能力
- 期待未来
KRaft在4.x中正式启用时我们再次出发- …
至此,KP 也大致讲的差不多了,如果感兴趣那就赶快 Run 起来吧~
4.总结
通过 API 的抽象设计,让松散和繁杂的事情简单化。
Less is More