介绍
目前stream封装了RabbitMq、kafka和redis消息组件,基于SpringBoot实现自动化配置,用户只需要使用stream的api,就可以通用的使用stream支持的各个消息组件。
原理
主要的概念是Channel和Binder。
生产者:消息发送到Channel,然后经过Binder转化成对应的消息组件操作。
消费者:通过binder获取到消息然后转化成对应的stream消息到Channel,然后消费者消费Channel消息。
实践
使用RabbitMq消息组件,以下是生产者代码
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<groupId>com.example</groupId>
<artifactId>streamtest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>streamtest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件application.yml
server:
port: 9000
spring:
application:
name: stream-producer
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
创建一个接口定义通道
package com.example.streamtest;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
public interface SendMessageInterface {
/**
* 创建一个输出通道,用于发送消息
* @return
*/
@Output("my_msg")
MessageChannel sendMsg();
}
Controller层发送消息
package com.example.streamtest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SendController {
@Autowired
private SendMessageInterface sendMessageInterface;
@RequestMapping("/send")
public String sendMsg(){
Message message = MessageBuilder.withPayload("this is message".getBytes()).build();
sendMessageInterface.sendMsg().send(message);
return "ok";
}
}
springboot入口类
package com.example.streamtest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
@SpringBootApplication
@EnableBinding(SendMessageInterface.class)
public class StreamtestApplication {
public static void main(String[] args) {
SpringApplication.run(StreamtestApplication.class, args);
}
}
启动项目
打开rabbit管理系统http://localhost:15672
可以发现创建了名为my_msg的topic
创建消费者项目
以下是消费者的代码
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<groupId>com.example</groupId>
<artifactId>streamconsumertest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>streamconsumertest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件application.yml
server:
port: 9001
spring:
application:
name: stream-consumer
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
创建接口定义通道
package com.example.streamconsumertest;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
public interface ReceiveMsgInterface {
/**
* 从通道获取消息
* @return
*/
@Input("my_msg")
SubscribableChannel receiveMeg();
}
创建监听消息类
package com.example.streamconsumertest;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@StreamListener("my_msg")
public void listener(String msg){
System.out.println("消费消息:"+msg);
}
}
springboot入口类
package com.example.streamconsumertest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
@SpringBootApplication
@EnableBinding(ReceiveMsgInterface.class)
public class StreamconsumertestApplication {
public static void main(String[] args) {
SpringApplication.run(StreamconsumertestApplication.class, args);
}
}
启动消费者项目
在RabbitMq管理系统可以看到创建了一个消息队列
这个消息队列名称是strema自动生成的。
用浏览器访问http://localhost:9000/send发送消息
可以看到消费者项目的控制台打印了一条消息
说明成功消费到了消息。
多个消费者重复消费同一个消息问题
修改消费者项目为可以启动多个
然后修改消费者项目端口号为9002,然后再次启动一次项目,这样消费者项目就开启了两个,一个端口号是9001,一个端口号是9002.
在RabbitMq管理系统中可以看见又创建了一个队列
在浏览器再次访问http://localhost:9000/send发送消息
发现两个消费者都消费到了消息。
但是一般现在的微服务为了实现高可用都会进行集群部署,消息被重复消费两次是不合理的,为了解决这个问题。
可以修改消费者配置文件application.yml
server:
port: 9002
spring:
application:
name: stream-consumer
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
cloud:
stream:
bindings:
my_msg: #指定管道名称
group: myGroup #指定该应用实例属于myGroup消费组
这样就指定了一个消费者组,类似kafka的消费者组概念,一个消费者组中只能有一个消费者消费同一个消息。
再次启动两个消费者项目。
可以在RabbitMq管理系统中看到
只创建了一个队列,而且这个队列就算关闭项目之后也会继续存在,不像之前stream创建的随机名称的队列,会在关闭项目之后队列消失。
在浏览器再次访问http://localhost:9000/send发送消息
可以发现两个消费者中只有一个消费者消费到了消息,多次发送消息之后,可以发现,多个消费者是轮询着消费消息的。
更改项目环境为kafka
不需要更改代码,只需要更改配置文件
修改生产者和消费者pom依赖修改stream-rabbit为stream-kafka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
修改生产者配置文件
server:
port: 9000
spring:
application:
name: stream-producer
cloud:
stream:
# 设置成使用kafka
kafka:
binder:
# Kafka的服务端列表,默认localhost
brokers: 192.168.1.8:9092,192.168.1.9:9092,192.168.1.10:9092
# Kafka服务端连接的ZooKeeper节点列表,默认localhost
zkNodes: 192.168.1.8:2181,192.168.1.9:2181,192.168.1.10:2181
minPartitionCount: 1
autoCreateTopics: true
autoAddPartitions: true
修改消费者配置文件
server:
port: 9001
spring:
application:
name: stream-consumer
cloud:
instance-count: 1
instance-index: 0
stream:
kafka:
binder:
# Kafka的服务端列表,默认localhost
brokers: 192.168.1.8:9092,192.168.1.9:9092,192.168.1.10:9092
# Kafka服务端连接的ZooKeeper节点列表,默认localhost
zkNodes: 192.168.1.8:2181,192.168.1.9:2181,192.168.1.10:2181
auto-add-partitions: true
auto-create-topics: true
min-partition-count: 1
bindings:
input:
destination: my_msg
group: myGroup
consumer:
autoCommitOffset: false
concurrency: 1
partitioned: false