一.简介
消息中间件对于服务解耦、流量削峰、消息延迟都是非常常用的技术手段。各种消息中间件支持的协议、使用方式各有不同,对于整合多种消息中间件将非常繁琐。SpringCloudStream通过增加绑定器中间层,隔离消息中间件与应用程序的耦合,通过向应用程序暴露统一的Channel通道,使应用程序不需要考虑不同的消息中间件。
SpringCloudStream架构:
二.实现
1.新建模块stream-provider Stream生产者
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud</artifactId>
<groupId>com.vincent</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-provider</artifactId>
<dependencies>
<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>
</dependency>
</dependencies>
</project>
2.stream-provider 配置
server:
port: 8070
spring:
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
cloud:
stream:
bindings:
output:
destination: test-stream-exechange
contentType: application/json
3.stream-provider 启动类
package com.stream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@SpringBootApplication
@EnableBinding(Source.class)
@RestController
public class StreamProviderApp {
public static void main(String[] args) {
SpringApplication.run(StreamProviderApp.class,args);
}
@Resource
private MessageChannel output;
@GetMapping("/send")
public void send() {
Map<String,Object> data = new HashMap<>();
data.put("timestamp",System.currentTimeMillis());
this.output.send(MessageBuilder.withPayload(data).build());
}
}
@EnableBinding(Source.class) 启用消息发送Channel,其Source 接口中定义了发送管道配置。且spring.cloud.stream.bindings.output 的Channel名称就是接口中定义的output名称。
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.messaging;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
/**
* Bindable interface with one output channel.
*
* @see org.springframework.cloud.stream.annotation.EnableBinding
* @author Dave Syer
* @author Marius Bogoevici
*/
public interface Source {
String OUTPUT = "output";
@Output(Source.OUTPUT)
MessageChannel output();
}
启动启动类并登录RabbitMQ WEB控制台,将能看到test-stream-exechange 的Exchange。
4.新建stream-consumer消费端模块
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud</artifactId>
<groupId>com.vincent</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-consumer</artifactId>
<dependencies>
<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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.stream.StreamConsumerApp</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
5.stream-consumer消费端配置
server:
port: 8080
spring:
cloud:
stream:
bindings:
input:
destination: test-stream-exechange
contentType: application/json
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
6.stream-consumer 启动类
package com.stream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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.Message;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableBinding(Sink.class)
@RestController
public class StreamConsumerApp {
public static void main(String[] args) {
SpringApplication.run(StreamConsumerApp.class,args);
}
@StreamListener("input")
public void handleMessage(Message<String> message){
System.out.println(message.getPayload());
}
}
@EnableBinding(Sink.class) 表示启用消息输入端,其Sink接口定义的使消息输入端。
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.messaging;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
* Bindable interface with one input channel.
*
* @see org.springframework.cloud.stream.annotation.EnableBinding
* @author Dave Syer
* @author Marius Bogoevici
*/
public interface Sink {
String INPUT = "input";
@Input(Sink.INPUT)
SubscribableChannel input();
}
启动2个stream-consumer 服务(另一个服务 java -jar target\stream-consumer-0.0.1-SNAPSHOT.jar --server.port=8081)并查看RabbitMQ WEB 控制台将有2个队列与Exchange 绑定。
7.通过stream-provider http://localhost:8070/send 产生消息,stream-consumer模块控制台都将产生数据输出。
三.自定义消息通道
如果不使用spring-cloud-starter-stream-rabbit 提供的默认Source / Sink 通道,可以使用自定义的方式,只需要注意定义的通道名称和依赖注入的名称,并参照Source / Sink 定义即可轻松自定义通道。并在配置启动类上使用@EnableBinding 开启通道配置即可。
如下是Source 接口定义源码,输出通道需要返回MessageChannel,且@Output() 注解参数名称是容器中Bean 名称,@Input 定义输入通道,且返回是SubscribableChannel。
/*
* Copyright 2015-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Indicates that an output binding target will be created by the framework.
*
* @author Dave Syer
* @author Marius Bogoevici
* @author Artem Bilan
*/
@Qualifier
@Target({ ElementType.FIELD, ElementType.METHOD,
ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Output {
/**
* Specify the binding target name;
* used as a bean name for binding target
* and as a destination name by default.
* @return the binding target name
*/
String value() default "";
}
四.分组与持久化
上面程序的stream-consumer有多个实例就会有多个RabbitMQ Queue,导致同一个服务对同一个消息会被多次消费,并且队列属于临时消息,消息可能会没被消费者消费而丢失。通过对服务消费者配置为同一个分组即可实现一个分组内的一个服务实例消费信息,且消息是持久化的。
1.配置stream-consumer 消息分组,即配置spring.cloud.stream.bindings.input.group
server:
port: 8080
spring:
cloud:
stream:
bindings:
input:
destination: test-stream-exechange
contentType: application/json
group: test-group
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
2.分别在8080 / 8081 端口启动stream-consumer ,并查看RabbitMQ Queue 信息只会出现一个队列。
3.访问http://localhost:8070/send 生产消息可以观察到只有其中一个stream-consumer 会消费一个消息。
4.当停止所有stream-consumer服务后,RabbitMQ 中的队列依然存在,不会被删除。
且当我们生产消息后消息会被持久化:
在启动stream-consumer服务后消息被消费后才会被删除: