Reactive Spring实战 -- 响应式Kafka交互

算法迷航
• 阅读 2893

本文分享如何使用KRaft部署Kafka集群,以及Spring中如何实现Kafka响应式交互。

KRaft

我们知道,Kafka使用Zookeeper负责为kafka存储broker,Consumer Group等元数据,并使用Zookeeper完成broker选主等操作。
虽然使用Zookeeper简化了Kafka的工作,但这也使Kafka的部署和运维更复杂。

Kafka 2.8.0开始移除了Zookeeper,并使用Kafka內部的仲裁(Quorum)控制器來取代ZooKeeper,官方称这个控制器为 "Kafka Raft metadata mode",即KRaft mode。从此用户可以在不需要Zookeeper的情况下部署Kafka集群,这使Fafka更加简单,轻量级。
使用KRaft模式后,用户只需要专注于维护Kafka集群即可。

注意:由于该功能改动较大,目前Kafka2.8版本提供的KRaft模式是一个测试版本,不推荐在生产环境使用。相信Kafka后续版本很快会提供生产可用的kraft版本。

下面介绍一下如果使用Kafka部署kafka集群。
这里使用3台机器部署3个Kafka节点,使用的Kafka版本为2.8.0。

1.生成ClusterId以及配置文件。
(1)使用kafka-storage.sh生成ClusterId。

$ ./bin/kafka-storage.sh random-uuid
dPqzXBF9R62RFACGSg5c-Q

(2)使用ClusterId生成配置文件

$ ./bin/kafka-storage.sh format -t <uuid> -c ./config/kraft/server.properties
Formatting /tmp/kraft-combined-logs

注意:只需要在生成一个ClusterId,并使用该ClusterId在所有机器上生成配置文件,即集群中所有节点使用的ClusterId需相同。

2.修改配置文件
脚本生成的配置文件只能用于单个Kafka节点,如果在部署Kafka集群,需要对配置文件进行一下修改。

(1)修改config/kraft/server.properties(稍后使用该配置启动kafka)

process.roles=broker,controller 
node.id=1
listeners=PLAINTEXT://172.17.0.2:9092,CONTROLLER://172.17.0.2:9093
advertised.listeners=PLAINTEXT://172.17.0.2:9092
controller.quorum.voters=1@172.17.0.2:9093,2@172.17.0.3:9093,3@172.17.0.4:9093

process.roles指定了该节点角色,有以下取值

  • broker: 这台机器将仅仅当作一个broker
  • controller: 作为Raft quorum的控制器节点
  • broker,controller: 包含以上两者的功能

一个集群中不同节点的node.id需要不同。
controller.quorum.voters需要配置集群中所有的controller节点,配置格式为<nodeId>@<ip>:<port>。

(2)
kafka-storage.sh脚本生成的配置,默认将kafka数据存放在/tmp/kraft-combined-logs/,
我们还需要/tmp/kraft-combined-logs/meta.properties配置中的node.id,使其与server.properties配置中保持一起。

node.id=1

3.启动kafka
使用kafka-server-start.sh脚本启动Kafka节点

$ ./bin/kafka-server-start.sh ./config/kraft/server.properties

下面测试一下该kafka集群
1.创建主题

$ ./bin/kafka-topics.sh --create --partitions 3 --replication-factor 3 --bootstrap-server 172.17.0.2:9092,172.17.0.3:9092,172.17.0.4:9092 --topic topic1 

2.生产消息

$ ./bin/kafka-console-producer.sh --broker-list 172.17.0.2:9092,172.17.0.3:9092,172.17.0.4:9092 --topic topic1

3.消费消息

$ ./bin/kafka-console-consumer.sh --bootstrap-server 172.17.0.2:9092,172.17.0.3:9092,172.17.0.4:9092 --topic topic1 --from-beginning

这部分命令的使用与低版本的Kafka保持一致。

Kafka的功能暂时还不完善,这是展示一个简单的部署示例。
Kafka文档:https://github.com/apache/kaf...

Spring中可以使用Spring-Kafka、Spring-Cloud-Stream两个框架实现kafka响应式交互。
下面分别看一下这两个框架的使用。

Spring-Kafka

1.添加引用
添加spring-kafka引用

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>2.5.8.RELEASE</version>
</dependency>

2.准备配置文件,内容如下

spring.kafka.producer.bootstrap-servers=172.17.0.2:9092,172.17.0.3:9092,172.17.0.4:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.LongSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer

spring.kafka.consumer.bootstrap-servers=172.17.0.2:9092,172.17.0.3:9092,172.17.0.4:9092
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.LongDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.group-id=warehouse-consumers
spring.kafka.consumer.properties.spring.json.trusted.packages=*

分别是生产者和消费者对应的配置,很简单。

3.发送消息
Spring-Kakfa中可以使用ReactiveKafkaProducerTemplate发送消息。
首先,我们需要创建一个ReactiveKafkaProducerTemplate实例。(目前SpringBoot会自动创建KafkaTemplate实例,但不会创建ReactiveKafkaProducerTemplate实例)。

@Configuration
public class KafkaConfig {
    @Autowired
    private KafkaProperties properties;

    @Bean
    public ReactiveKafkaProducerTemplate reactiveKafkaProducerTemplate() {
        SenderOptions options = SenderOptions.create(properties.getProducer().buildProperties());
        ReactiveKafkaProducerTemplate template = new ReactiveKafkaProducerTemplate(options);
        return template;
    }
}

KafkaProperties实例由SpringBoot自动创建,读取上面配置文件中对应的配置。

接下来,就可以使用ReactiveKafkaProducerTemplate发送消息了

    @Autowired
    private ReactiveKafkaProducerTemplate template;

    public static final String WAREHOUSE_TOPIC = "warehouse";
    public Mono<Boolean> add(Warehouse warehouse) {
        Mono<SenderResult<Void>> resultMono = template.send(WAREHOUSE_TOPIC, warehouse.getId(), warehouse);
        return resultMono.flatMap(rs -> {
            if(rs.exception() != null) {
                logger.error("send kafka error", rs.exception());
                return Mono.just(false);
            }
            return Mono.just(true);
        });
    }

ReactiveKafkaProducerTemplate#send方法返回一个Mono(这是Spring Reactor中的核心对象),Mono中携带了SenderResult,SenderResult中的RecordMetadata、exception存储该记录的元数据(包括offset、timestamp等信息)以及发送操作的异常。

4.消费消息
Spring-Kafka使用ReactiveKafkaConsumerTemplate消费消息。

@Service
public class WarehouseConsumer {
    @Autowired
    private KafkaProperties properties;

    @PostConstruct
    public void consumer() {
        ReceiverOptions<Long, Warehouse> options = ReceiverOptions.create(properties.getConsumer().buildProperties());
        options = options.subscription(Collections.singleton(WarehouseService.WAREHOUSE_TOPIC));
        new ReactiveKafkaConsumerTemplate(options)
                .receiveAutoAck()
                .subscribe(record -> {
                    logger.info("Warehouse Record:" + record);
                });
    }
}

这里与之前使用@KafkaListener注解实现的消息监听者不同,不过也非常简单,分为两个步骤:
(1)ReceiverOptions#subscription方法将ReceiverOptions关联到kafka主题
(2)创建ReactiveKafkaConsumerTemplate,并注册subscribe的回调函数消费消息。
提示:receiveAutoAck方法会自动提交消费组offset。

Spring-Cloud-Stream

Spring-Cloud-Stream是Spring提供的用于构建消息驱动微服务的框架。
它为不同的消息中间件产品提供一种灵活的,统一的编程模型,可以屏蔽底层不同消息组件的差异,目前支持RabbitMQ、Kafka、RocketMQ等消息组件。

这里简单展示Spring-Cloud-Stream中实现Kafka响应式交互的示例,不深入介绍Spring-Cloud-Stream的应用。

1.引入spring-cloud-starter-stream-kafka的引用

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-kafka</artifactId>
    </dependency>

2.添加配置

spring.cloud.stream.kafka.binder.brokers=172.17.0.2:9092,172.17.0.3:9092,172.17.0.4:9092
spring.cloud.stream.bindings.warehouse2-out-0.contentType=application/json
spring.cloud.stream.bindings.warehouse2-out-0.destination=warehouse2
# 消息格式
spring.cloud.stream.bindings.warehouse3-in-0.contentType=application/json
# 消息目的地,可以理解为Kafka主题
spring.cloud.stream.bindings.warehouse3-in-0.destination=warehouse2
# 定义消费者消费组,可以理解为Kafka消费组
spring.cloud.stream.bindings.warehouse3-in-0.group=warehouse2-consumers
# 映射方法名
spring.cloud.function.definition=warehouse2;warehouse3

Spring-Cloud-Stream 3.1版本之后,@EnableBinding、@Output等StreamApi注解都标记为废弃,并提供了一种更简洁的函数式编程模型。
该版本后,用户不需要使用注解,只要在配置文件中指定需要绑定的方法,Spring-Cloud-Stream会为用户将这些方法与底层消息组件绑定,用户可以直接调用这些方法发送消息,或者接收到消息时Spring-Cloud-Stream会调用这些方法消费消息。

通过以下格式定义输入、输出函数的相关属性:
输出(发送消息):<functionName> + -out- + <index>
输入(消费消息):<functionName> + -in- + <index>
对于典型的单个输入/输出函数,index始终为0,因此它仅与具有多个输入和输出参数的函数相关。
Spring-Cloud-Stream支持具有多个输入(函数参数)/输出(函数返回值)的函数。

spring.cloud.function.definition配置指定需要绑定的方法名,不添加该配置,Spring-Cloud-Stream会自动尝试绑定返回类型为Supplier/Function/Consumer的方法,但是使用该配置可以避免Spring-Cloud-Stream绑定混淆。

3.发送消息
用户可以编写一个返回类型为Supplier的方法,并定时发送消息

    @PollableBean
    public Supplier<Flux<Warehouse>> warehouse2() {
        Warehouse warehouse = new Warehouse();
        warehouse.setId(333L);
        warehouse.setName("天下第一仓");
        warehouse.setLabel("一级仓");

        logger.info("Supplier Add : {}", warehouse);
        return () -> Flux.just(warehouse);
    }

定义该方法后,Spring-Cloud-Stream每秒调用一次该方法,生成Warehouse实例,并发送到Kafka。
(这里方法名warehouse3已经配置在spring.cloud.function.definition中。)

通常场景下,应用并不需要定时发送消息,而是由业务场景触发发送消息操作, 如Rest接口,
这时可以使用StreamBridge接口

    @Autowired
    private StreamBridge streamBridge;

    public boolean add2(Warehouse warehouse) {
        return streamBridge.send("warehouse2-out-0", warehouse);
    }

暂时未发现StreamBridge如何实现响应式交互。

4.消费消息
应用要消费消息,只需要定义一个返回类型为Function/Consumer的方法即可。如下

    @Bean
    public Function<Flux<Warehouse>, Mono<Void>> warehouse3() {
        Logger logger = LoggerFactory.getLogger("WarehouseFunction");
        return flux -> flux.doOnNext(data -> {
            logger.info("Warehouse Data: {}", data);
        }).then();
    }

注意:方法名与<functionName> + -out- + <index>/<functionName> + -in- + <index>
spring.cloud.function.definition中的配置需要保持一致,以免出错。

SpringCloudStream文档:https://docs.spring.io/spring...

文章完整代码:https://gitee.com/binecy/bin-...

如果您觉得本文不错,欢迎关注我的微信公众号,系列文章持续更新中。您的关注是我坚持的动力!
Reactive Spring实战 -- 响应式Kafka交互

点赞
收藏
评论区
推荐文章
京东面试真题解析,手撕面试官
第1章快速入门1.1Kafka简介1.2以Kafka为中心的解决方案1.3Kafka核心概念1.4Kafka源码环境第2章生产者2.1KafkaProducer使用示例2.2KafkaProducer分析ProducerInterceptors&cProducerInterceptorKafka集群元数据Serializ
Stella981 Stella981
3年前
Flink读取Kafka数据,进行汇总
今天介绍用Flink读取Kafka生成的数据,并进行汇总的案例第一步:环境准备,kafka,flink,zookeeper。我这边是用的CDH环境,kafka跟zookeeper都安装完毕,并测试可以正常使用第二步:用kafka创建一个生产者进行消息生产./kafkaconsoleproducer.shbroker
可莉 可莉
3年前
10 道 Kafka 基础面试,不看答案你会几道题
写在前面这次给大家分享的是Kafka的10道面试题,就难度我觉得是比较基础的,但考的知识点还比较全面,可以用来考察自己掌握Kafka基础的程度。1\.Kafka是什么?Kafka起初是一个多分区、多副本且基于ZooKeeper协调的分布式消息系统,现已被定位为一个分布
Stella981 Stella981
3年前
Kafka安装教程(详细过程)
安装前期准备:1,准备三个节点(根据自己需求决定)2,三个节点上安装好zookeeper(也可以使用kafka自带的zookeeper)3,关闭防火墙chkconfig iptablesoff一、下载安装包Kafka官网下载安装包http://kafka.apache.org/downloads.html我们下载第二种(已
Stella981 Stella981
3年前
Kafka数据迁移MaxCompute最佳实践
前提条件搭建Kafka集群进行数据迁移前,您需要保证自己的Kafka集群环境正常。本文使用阿里云EMR服务自动化搭建Kafka集群,详细过程请参见:Kafka快速入门。本文使用的EMRKafka版本信息如下:EMR版本:EMR3.12.1集群类型:Kafka软件信息:Ganglia3.7.2ZooKeeper
Wesley13 Wesley13
3年前
KAFKA官方教程笔记
 因为kafka配置较多,所以单独写一篇博客来记录。       1,broker配置   主要的配置项有三个broker.id集群内唯一log.dir数据目录zookeeper.connectzookeeper连接地址Topiclevelconfigurationsanddefaultsa
Stella981 Stella981
3年前
Kafka源码系列之Broker的IO服务及业务处理
Kafka源码系列之Broker的IO服务及业务处理一,kafka角色Kafka源码系列主要是以kafka0.8.2.2源码为例。以看spark等源码的经验总结除了一个重要的看源码的思路:先了解部件角色和功能角色,然后逐个功能请求序列画图分析,最后再汇总。那么,下面再啰嗦一下,kafka的角色。kafka在生产中的使用,如下
Stella981 Stella981
3年前
FusionInsight大数据开发
Kafka应用开发1.了解Kafka应用开发适用场景2.熟悉Kafka应用开发流程3.熟悉并使用Kafka常用API4.进行Kafka应用开发Kafka的定义Kafka是一个高吞吐、分布式、基于发布订阅的消息系统Kafka有如下几个特点:1.高吞吐量2.消息持久化到磁
Stella981 Stella981
3年前
Kafka 原理详解
Kafka原理详解1kakfa基础概念说明Broker:消息服务器,就是我们部署的一个kafka服务Partition:消息的水平分区,一个Topic可以有多个分区,这样实现了消息的无限量存储Replica:消息的副本,即备份消息,存储在其他的broker上,当leader挂掉
胖大海 胖大海
2年前
Centos7 kafka安装与配置
前言,我这边使用的3A服务器centos7.9系统里进行操作的,使用kafka需要安装JDK,zookeeper一:从官网下载安装包http://archive.apache.org/dist/kafka/二:安装和配置kafka1.解压kafka压缩包并放到/usr/local下tarxzfkafka2.100.8.1.1.tar.gz2.配置serv
Kafka核心逻辑介绍 | 京东云技术团队
1、概念Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica)分布式消息系统(kafka2.8.0版本之后接触了对zk的依赖,使用自己的kRaft做集群管理,新增内部主体@metadata存储