SpringCloud Stream是一个用来为微服务应用构建消息驱动能力的框架。它可以基于SpringBoot来创建独立的、可用于生产的spring应用程序。它通过使用Spring Integration来连接消息代理中间件来实现消息事件驱动。Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,并且引入了发布-订阅、消费组以及消息分区这三个核心概念。简单的说,Spring Cloud Stream本质上就是整合了Spring Boot和Spring Integration,实现了一套轻量级的消息驱动的微服务框架。通过使用Spring Cloud Stream,可以有效地简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。
消费组:在很多情况下,消息生产者将消息发送给某个具体的微服务时,只希望被消费一次,我们可以通过设置spring.cloud.stream.bindings.input.group属性为应用指定一个组名,这样这个应用的多个实例在接收到消息的时候,只有一个成员真正接收到消息并进行处理。
消费分区:通过引入消费组的概念,我们已经能解决消息被多次消费的情况,但我们无法跟踪是哪个实例进行了处理,对于某些服务(如监控服务),我们需要跟踪其执行的过程,那么我们可以为消息设置一个特征的ID来标识,使得具有这些特征ID的消息每次都是被同一个消费者接收和处理。
本文的目的在于结合RabbitMQ与Stream来处理消息通信,采取自定义编写Sink(input)和Source(output)来设置多通道消息和消费组、消费分区等操作实现基本的消息驱动的微服务架构。
首先给出项目结构
1 更新pom.xml配置
4.0.0
yunlingfly
springcloudstream
0.0.1-SNAPSHOT
jar
springcloudstream
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.10.RELEASE
UTF-8
UTF-8
1.8
Dalston.SR4
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-stream-rabbit
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
2 编写启动类
package yunlingfly.springcloudstream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableScheduling
public class SpringcloudstreamApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudstreamApplication.class, args);
}
}
3 配置application.yml
info:
name: 一个springcloudstream
version: 0.0.1
server:
port: 8764
spring:
rabbitmq:
port: 5672
username: xxxx
password: xxxxxx
host: localhost
cloud:
stream:
# 设置当前消费者的总的实例个数和当前实例的索引
instance-count: 2
instance-index: 0
bindings:
# 以下是设置输入通道及分组和输出通道及类型
input:
destination: somestream
content-type: application/json
group: stream1
#开启消息分区
consumer:
partitioned: true
input2:
destination: somestream2
content-type: application/json
group: stream2
output:
destination: somestream
content-type: application/json
producer:
#设置分区键的表达式规则和设置消息分区数量
partitionKeyExpression: payload
partitionCount: 2
output2:
destination: somestream2
content-type: application/json
4 编写消息发出者的接口
package yunlingfly.springcloudstream.sender;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Component;
@Component
public interface SinkSenderInter {
String OUTPUT="output";
String OUTPUT2="output2";
@Output(OUTPUT) // 在这里设置输出通道,配置在application.yml里
MessageChannel output();
@Output(OUTPUT2)
MessageChannel output2();
}
5 编写消息发出者的两个实现类(使用@Scheduled周期性发送消息)与使用到的POJO类
SinkSender:
package yunlingfly.springcloudstream.sender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;
import yunlingfly.springcloudstream.pojo.Person;
@EnableBinding(value = {SinkSenderInter.class})
public class SinkSender {
@Autowired
private SinkSenderInter sinkSenderInter;
@Scheduled(initialDelay = 1000, fixedRate = 6000) //设置初次执行延迟和执行频率
public void sendmessage(){
Person p=new Person();
p.setMessage("some message");
p.setName("芸灵fly");
sinkSenderInter.output().send(MessageBuilder.withPayload(p).build());
}
}
SinkSender2:
package yunlingfly.springcloudstream.sender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(value = {SinkSenderInter.class})
public class SinkSender2 {
@Autowired
private SinkSenderInter sinkSenderInter;
@Scheduled(initialDelay = 2000, fixedRate = 5000)
public void sendmessage2(){
sinkSenderInter.output2().send(MessageBuilder.withPayload("some message test from output2通道").build());
}
}
Person(POJO):
package yunlingfly.springcloudstream.pojo;
public class Person {
private String name;
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
6 编写消息接收者的接口
package yunlingfly.springcloudstream.receiver;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;
@Component
public interface SinkReceiverInter {
String INPUT="input";
String INPUT2="input2";
@Input(INPUT) //配置在application.yml里了
SubscribableChannel input();
@Input(INPUT2)
SubscribableChannel input2();
}
7 编写消息接收者的两个实现类
SinkReceiver:
package yunlingfly.springcloudstream.receiver;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import yunlingfly.springcloudstream.pojo.Person;
import java.util.Date;
@EnableBinding(value = {SinkReceiverInter.class})
public class SinkReceiver {
@StreamListener(SinkReceiverInter.INPUT)
public void receiver(Person payload){
System.out.println("时间:"+new Date()+"接收到消息:"+payload.getMessage()+payload.getName());
}
}
SinkReceiver2:
package yunlingfly.springcloudstream.receiver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.SubscribableChannel;
import java.util.Date;
@EnableBinding(value = {SinkReceiverInter.class})
public class SinkReceiver2 {
@StreamListener(SinkReceiverInter.INPUT2)
public void receiver(Object payload){
System.out.println("时间:"+new Date()+"接收到消息:"+payload);
}
}
8 为了方便演示,也使用了自带默认的Source(output)来编写了Controller层的代码
package yunlingfly.springcloudstream.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableBinding(Source.class)
public class HelloController {
@Autowired
@Qualifier("output")
MessageChannel output; // 直接使用默认的Source来输出
@RequestMapping(value = "/stream",method = RequestMethod.GET)
public String hello(){
// 发送一条消息
output.send(MessageBuilder.withPayload("大家好").build());
return "success";
}
}
9 (这一步可以不做)如果要看到消费者组和分区的效果,请新建一个和上文一样的pom.xml和启动类,再将Receiver层的代码一样的引入,application.yml修改为以下(主要改了instance-index属性即可)
server:
port: 8765
spring:
rabbitmq:
port: 5672
username: xxxx
password: xxxxxxx
host: localhost
cloud:
stream:
# 设置当前消费者的总的实例个数和当前实例的索引
instance-count: 2
instance-index: 1
bindings:
input:
destination: somestream
content-type: application/json
group: stream1
#开启消息分区
consumer:
partitioned: true
info:
name: 另一个springcloudstram
version: 0.0.1
10 启动项目即可看到效果
(也可以直接通过RabbitMQ网页控制端向队列输出消息)
先点击Queues选择需要输出的队列
点击Name那一行后如下图输入PayLoad后点击Publish message即可