Reactive Spring实战 -- 响应式Kafka交互
本文分享如何使用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。
- 生成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需相同。
- 修改配置文件
脚本生成的配置文件只能用于单个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节点,配置格式为@:。
(2)
kafka-storage.sh脚本生成的配置,默认将kafka数据存放在/tmp/kraft-combined-logs/,
我们还需要/tmp/kraft-combined-logs/meta.properties配置中的node.id,使其与server.properties配置中保持一起。
node.id=1
- 启动kafka
使用kafka-server-start.sh脚本启动Kafka节点 
$ ./bin/kafka-server-start.sh ./config/kraft/server.properties
下面测试一下该kafka集群
- 创建主题
 
$ ./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
- 生产消息
 
$ ./bin/kafka-console-producer.sh --broker-list 172.17.0.2:9092,172.17.0.3:9092,172.17.0.4:9092 --topic topic1
- 消费消息
 
$ ./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/kafka/blob/trunk/config/kraft/README.md
Spring中可以使用Spring-Kafka、Spring-Cloud-Stream两个框架实现kafka响应式交互。
下面分别看一下这两个框架的使用。
Spring-Kafka
- 添加引用
添加spring-kafka引用 
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>2.5.8.RELEASE</version>
</dependency>
- 准备配置文件,内容如下
 
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=*
分别是生产者和消费者对应的配置,很简单。
- 发送消息
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等信息)以及发送操作的异常。
- 消费消息
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的应用。
- 引入spring-cloud-starter-stream-kafka的引用
 
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-kafka</artifactId>
    </dependency>
- 添加配置
 
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绑定混淆。
- 发送消息
用户可以编写一个返回类型为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如何实现响应式交互。
- 消费消息
应用要消费消息,只需要定义一个返回类型为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-cloud-stream/docs/3.1.0/reference/html/spring-cloud-stream.html
文章完整代码:https://gitee.com/binecy/bin-springreactive/tree/master/warehouse-service
如果您觉得本文不错,欢迎关注我的微信公众号,系列文章持续更新中。您的关注是我坚持的动力!

Reactive Spring实战 -- 响应式Kafka交互的更多相关文章
- Reactive Spring实战 -- 响应式Redis交互
		
本文分享Spring中如何实现Redis响应式交互模式. 本文将模拟一个用户服务,并使用Redis作为数据存储服务器. 本文涉及两个java bean,用户与权益 public class User ...
 - Reactive Spring实战 -- 响应式MySql交互
		
本文与大家探讨Spring中如何实现MySql响应式交互. Spring Data R2DBC项目是Spring提供的数据库响应式编程框架. R2DBC是Reactive Relational Dat ...
 - Reactive 理解 SpringBoot 响应式的核心-Reactor
		
Reactive 理解 SpringBoot 响应式的核心-Reactor bestcoding 2020-02-23 17:26:43 一.前言 关于 响应式 Reactive,前面的两篇文章谈了不 ...
 - 第二百五十一节,Bootstrap项目实战--响应式轮播图
		
Bootstrap项目实战--响应式轮播图 学习要点: 1.响应式轮播图 本节课我们要在导航条的下方做一张轮播图,自动播放最新的重要动态. 一.响应式轮播图 响应式轮播图 第一步,设置轮播器区域car ...
 - 第二百五十节,Bootstrap项目实战--响应式导航
		
Bootstrap项目实战--响应式导航 学习要点: 1.响应式导航 一.响应式导航 基本导航组件+响应式 第一步,声明导航区域,设置导航默认样式,设置导航条固定在顶部navbar样式class类,写 ...
 - Java9第四篇-Reactive Stream API响应式编程
		
我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...
 - Spring 5 响应式编程
		
要点 Reactor 是一个运行在 Java8 之上的响应式流框架,它提供了一组响应式风格的 API 除了个别 API 上的区别,它的原理跟 RxJava 很相似 它是第四代响应式框架,支持操作融合, ...
 - Reactive(1) 从响应式编程到"好莱坞"
		
目录 概念 面向流设计 异步化 响应式宣言 参考文档 概念 Reactive Programming(响应式编程)已经不是一个新东西了. 关于 Reactive 其实是一个泛化的概念,由于很抽象,一些 ...
 - Reactive Spring实战 -- WebFlux使用教程
		
WebFlux是Spring 5提供的响应式Web应用框架. 它是完全非阻塞的,可以在Netty,Undertow和Servlet 3.1+等非阻塞服务器上运行. 本文主要介绍WebFlux的使用. ...
 
随机推荐
- 使用FileStream读写数据
			
这节讲一下使用FileStream读写数据,这是一个比较基础的流. FileStream类只能处理原始字节,所以它可以处理任何类型的文件. 先看一下它的构造方法: FileStream fs = ne ...
 - 段间跳转之TSS段
			
TR寄存器,TSS描述符,TSS段 TR寄存器与普通的段寄存器一样都有可见部分和不可见部分.TR的可见部分为16位为其段选择子,不可见部分是32位的TSS基地址和16位的大小. TSS描述符存在GDT ...
 - Linux下script命令录制、回放和共享终端操作script -t 2> timing.log -a output.session  # 开始录制
			
Linux下script命令录制.回放和共享终端操作 [日期:2018-09-04] 来源:cnblogs.com/f-ck-need-u 作者:骏马金龙 [字体:大 中 小] 另一篇终端会话共 ...
 - Hutool :一个小而全的 Java 工具类库
			
Hutool 简介 Hutool 是一个小而全的 Java 工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以"甜甜的 ...
 - 3.23 vi/vim:纯文本编辑器
			
vi/vim 是Linux命令行界面下的文字编辑器,几乎所有的Linux系统都安装了vi,只要学会了vi这个编辑工具,就可以在任何Linux系统上使用它.而vim是vi命令的增强版(Vi IMprov ...
 - RAM与FLASH
			
以前一直使用STM32但是对 变量 或 函数 的存储域没做任何了解:只知道你需要存储的东西就放在Flash的后面几页就好了:这次接触到STM8发现编译器里面有特别的存储查看器就打算看看到底是怎么存储的 ...
 - docker-ce 安装
			
配置源 确认版本 添加镜像加速器 https://docs.docker.com/engine/release-notes/19.03/ for centos wget -O /etc/yum.rep ...
 - nginx 配置 conf stream
			
nginx从1.9.0版本开始,新增了ngx_stream_core_module模块,使nginx支持四层负载均衡.默认编译的时候该模块并未编译进去,需要编译的时候添加--with-stream参数 ...
 - Qt 搜索框
			
一.前言 用户需要输入文本时,可使用QLineEdit控件进行编辑输入,缺点是样式相对单一. 在使用百度搜索输入框时,发觉比较人性化,故采用QLineEdt+QPushButton通过css样式实现自 ...
 - Linux(CentOS7)下安装jdk1.8
			
Linux(CentOS7) 下安装 jdk1.8 操作过程. 一.检查是否自带jdk rpm -qa|grep java 如果存在则用下面命令删除,xxx yyy zzz代表查询出来的自带jdk名称 ...