在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,消息中间件解决了应用解耦、异步处理、流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。

  不同中间件内部实现方式是不一样的,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,比如项目中间件为 Kafka,如果我们要替换为 RabbitMQ,这无疑就是一个灾难性的工作,一大堆东西都要重做,因为它跟我们系统的耦合性非常高。这时我们可以使用 Spring Cloud Stream 来整合我们的消息中间件,降低系统和中间件的耦合性。

  

消息中间件的几大应用场景

  

应用解耦

  

  假设公司有几个不同的系统,各系统在某些业务有联动关系,比如 A 系统完成了某些操作,需要触发 B 系统及 C 系统,但是各个系统之间产生了耦合。针对这种场景,用消息中间件就可以完成解耦,当 A 系统完成操作时将数据放进消息队列,B 和 C 系统去订阅消息就可以了,这样各系统只要约定好消息的格式就可以了。

  

  传统模式:

  中间件模式:

  

异步处理

  

  比如用户在电商网站下单,下单完成后会给用户推送短信或邮件,发短信和邮件的过程就可以异步完成。因为下单付款才是核心业务,发邮件和短信并不属于核心功能,且可能耗时较长,所以针对这种业务场景可以选择先放到消息队列中,由其他服务来异步处理。

  

  传统模式:

  中间件模式:

  

流量削峰

  

  比如秒杀活动,一下子进来好多请求,有的服务可能承受不住瞬时高并发而崩溃,针对这种场景,在中间加一层消息队列,把请求先入队列,然后再把队列中的请求平滑的推送给服务,或者让服务去队列拉取。

  

  传统模式:

  中间件模式:

  

日志处理

  

  对于小型项目来说,我们通常对日志的处理没有那么多的要求,但是当用户量,数据量达到一定的峰值之后,问题就会随之而来。比如:

  • 用户日志怎么存放
  • 用户日志存放后怎么利用
  • 怎么在存储大量日志而不对系统造成影响

  等很多其他的问题,这样我们就需要借助消息队列进行业务的上解耦,数据上更好的传输。

  Kafka 最开始就是专门为了处理日志产生的。

  

总结

  

  消息队列,是分布式系统中重要的组件,其通用的使用场景可以简单地描述为:当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。

  当遇到上面几种情况的时候,就要考虑用消息队列了。如果你碰巧使用的是 RabbitMQ 或者 Kafka ,而且同样也在使用 Spring Cloud,那你可以考虑下用 Spring Cloud Stream。

  

什么是 Spring Cloud Stream

  

  Spring Cloud Stream 是用于构建消息驱动微服务应用程序的框架。该框架提供了一个灵活的编程模型,该模型建立在已经熟悉 Spring 习惯用法的基础上,它提供了来自多家供应商的中间件的合理配置,包括 publish-subscribe,消息分组和消息分区处理的支持。

  Spring Cloud Stream 解决了开发人员无感知的使用消息中间件的问题,因为 Stream 对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件,使得微服务开发的高度解耦,服务可以关注更多自己的业务流程。

  

核心概念

  

  

组成 说明
Middleware 中间件,支持 RabbitMQ 和 Kafka。
Binder 目标绑定器,目标指的是 Kafka 还是 RabbitMQ。绑定器就是封装了目标中间件的包。如果操作的是 Kafka 就使用 spring-cloud-stream-binder-kafka,如果操作的是 RabbitMQ 就使用 spring-cloud-stream-binder-rabbit
@Input 注解标识输入通道,接收(消息消费者)的消息将通过该通道进入应用程序。
@Output 注解标识输出通道,发布(消息生产者)的消息将通过该通道离开应用程序。
@StreamListener 监听队列,消费者的队列的消息接收。
@EnableBinding 注解标识绑定,将信道 channel 和交换机 exchange 绑定在一起。

  

工作原理

  

  通过定义绑定器作为中间层,实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种不同的消息中间件的实现。当需要升级消息中间件,或者是更换其他消息中间件产品时,我们需要做的就是更换对应的 Binder 绑定器而不需要修改任何应用逻辑。

  

  

  该模型图中有如下几个核心概念:

  • Source:当需要发送消息时,我们就需要通过 Source.java,它会把我们所要发送的消息进行序列化(默认转换成 JSON 格式字符串),然后将这些数据发送到 Channel 中;
  • Sink:当我们需要监听消息时就需要通过 Sink.java,它负责从消息通道中获取消息,并将消息反序列化成消息对象,然后交给具体的消息监听处理;
  • Channel:通常我们向消息中间件发送消息或者监听消息时需要指定主题(Topic)和消息队列名称,一旦我们需要变更主题的时候就需要修改消息发送或消息监听的代码。通过 Channel 对象,我们的业务代码只需要对应 Channel 就可以了,具体这个 Channel 对应的是哪个主题,可以在配置文件中来指定,这样当主题变更的时候我们就不用对代码做任何修改,从而实现了与具体消息中间件的解耦;
  • Binder:通过不同的 Binder 可以实现与不同的消息中间件整合,Binder 提供统一的消息收发接口,从而使得我们可以根据实际需要部署不同的消息中间件,或者根据实际生产中所部署的消息中间件来调整我们的配置。

  

环境准备

  

  stream-demo 聚合工程。SpringBoot 2.2.4.RELEASESpring Cloud Hoxton.SR1

  • RabbitMQ:消息队列

  • eureka-server:注册中心

  • eureka-server02:注册中心

  

  

入门案例

  

  点击链接观看:Stream 入门案例视频(获取更多请关注公众号「哈喽沃德先生」)

  

消息生产者

  

创建项目

  

  在 stream-demo 项目下创建 stream-producer 子项目。

  

添加依赖

  

  要使用 RabbitMQ 绑定器,可以通过使用以下 Maven 坐标将其添加到 Spring Cloud Stream 应用程序中:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

  或者使用 Spring Cloud Stream RabbitMQ Starter:

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

  

  完整依赖如下:

<?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">
<modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId>
<artifactId>stream-producer</artifactId>
<version>1.0-SNAPSHOT</version> <!-- 继承父依赖 -->
<parent>
<groupId>com.example</groupId>
<artifactId>stream-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent> <!-- 项目依赖 -->
<dependencies>
<!-- netflix eureka client 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- spring cloud stream binder rabbit 绑定器依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency> <!-- spring boot test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </project>

  

配置文件

  

  配置 RabbitMQ 消息队列和 Stream 消息发送与接收的通道。

server:
port: 8001 # 端口 spring:
application:
name: stream-producer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址
cloud:
stream:
bindings:
# 消息发送通道
# 与 org.springframework.cloud.stream.messaging.Source 中的 @Output("output") 注解的 value 相同
output:
destination: stream.message # 绑定的交换机名称 # 配置 Eureka Server 注册中心
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
service-url: # 设置服务注册中心地址
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

  

发送消息

  

  MessageProducer.java

package com.example.producer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component; /**
* 消息生产者
*/
@Component
@EnableBinding(Source.class)
public class MessageProducer { @Autowired
private Source source; /**
* 发送消息
*
* @param message
*/
public void send(String message) {
source.output().send(MessageBuilder.withPayload(message).build());
} }

  

启动类

  

  StreamProducerApplication.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
public class StreamProducerApplication { public static void main(String[] args) {
SpringApplication.run(StreamProducerApplication.class);
} }

  

消息消费者

  

创建项目

  

  在 stream-demo 项目下创建 stream-consumer 子项目。

  

添加依赖

  

  要使用 RabbitMQ 绑定器,可以通过使用以下 Maven 坐标将其添加到 Spring Cloud Stream 应用程序中:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

  或者使用 Spring Cloud Stream RabbitMQ Starter:

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

  

  完整依赖如下:

<?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">
<modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId>
<artifactId>stream-consumer</artifactId>
<version>1.0-SNAPSHOT</version> <!-- 继承父依赖 -->
<parent>
<groupId>com.example</groupId>
<artifactId>stream-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent> <!-- 项目依赖 -->
<dependencies>
<!-- netflix eureka client 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- spring cloud stream binder rabbit 绑定器依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency> <!-- spring boot test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </project>

  

配置文件

  

  配置 RabbitMQ 消息队列和 Stream 消息发送与接收的通道。

server:
port: 8002 # 端口 spring:
application:
name: stream-consumer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址
cloud:
stream:
bindings:
# 消息接收通道
# 与 org.springframework.cloud.stream.messaging.Sink 中的 @Input("input") 注解的 value 相同
input:
destination: stream.message # 绑定的交换机名称 # 配置 Eureka Server 注册中心
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
service-url: # 设置服务注册中心地址
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

  

接收消息

  

  MessageConsumer.java

package com.example.consumer;

import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.stereotype.Component; /**
* 消息消费者
*/
@Component
@EnableBinding(Sink.class)
public class MessageConsumer { /**
* 接收消息
*
* @param message
*/
@StreamListener(Sink.INPUT)
public void receive(String message) {
System.out.println("message = " + message);
} }

  

启动类

  

  StreamConsumerApplication.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
public class StreamConsumerApplication { public static void main(String[] args) {
SpringApplication.run(StreamConsumerApplication.class);
} }

  

测试

  

单元测试

  

  MessageProducerTest.java

package com.example;

import com.example.producer.MessageProducer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = {StreamProducerApplication.class})
public class MessageProducerTest { @Autowired
private MessageProducer messageProducer; @Test
public void testSend() {
messageProducer.send("hello spring cloud stream");
} }

  

访问

  

  启动消息消费者,运行单元测试,消息消费者控制台打印结果如下:

message = hello spring cloud stream

  RabbitMQ 界面如下:

  

自定义消息通道

  

创建消息通道

  

  参考源码 Source.javaSink.java 创建自定义消息通道。

  自定义消息发送通道 MySource.java

package com.example.channel;

import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel; /**
* 自定义消息发送通道
*/
public interface MySource { String MY_OUTPUT = "my_output"; @Output(MY_OUTPUT)
MessageChannel myOutput(); }

  自定义消息接收通道 MySink.java

package com.example.channel;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel; /**
* 自定义消息接收通道
*/
public interface MySink { String MY_INPUT = "my_input"; @Input(MY_INPUT)
SubscribableChannel myInput(); }

  

配置文件

  

  消息生产者。

server:
port: 8001 # 端口 spring:
application:
name: stream-producer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址
cloud:
stream:
bindings:
# 消息发送通道
# 与 org.springframework.cloud.stream.messaging.Source 中的 @Output("output") 注解的 value 相同
output:
destination: stream.message # 绑定的交换机名称
my_output:
destination: my.message # 绑定的交换机名称

  

  消息消费者。

server:
port: 8002 # 端口 spring:
application:
name: stream-consumer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址
cloud:
stream:
bindings:
# 消息接收通道
# 与 org.springframework.cloud.stream.messaging.Sink 中的 @Input("input") 注解的 value 相同
input:
destination: stream.message # 绑定的交换机名称
my_input:
destination: my.message # 绑定的交换机名称

  

代码重构

  

  消息生产者 MyMessageProducer.java

package com.example.producer;

import com.example.channel.MySource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component; /**
* 消息生产者
*/
@Component
@EnableBinding(MySource.class)
public class MyMessageProducer { @Autowired
private MySource mySource; /**
* 发送消息
*
* @param message
*/
public void send(String message) {
mySource.myOutput().send(MessageBuilder.withPayload(message).build());
} }

  

  消息消费者 MyMessageConsumer.java

package com.example.consumer;

import com.example.channel.MySink;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component; /**
* 消息消费者
*/
@Component
@EnableBinding(MySink.class)
public class MyMessageConsumer { /**
* 接收消息
*
* @param message
*/
@StreamListener(MySink.MY_INPUT)
public void receive(String message) {
System.out.println("message = " + message);
} }

  

测试

  

单元测试

  

  MessageProducerTest.java

package com.example;

import com.example.producer.MyMessageProducer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = {StreamProducerApplication.class})
public class MessageProducerTest { @Autowired
private MyMessageProducer myMessageProducer; @Test
public void testMySend() {
myMessageProducer.send("hello spring cloud stream");
} }

  

访问

  

  启动消息消费者,运行单元测试,消息消费者控制台打印结果如下:

message = hello spring cloud stream

  RabbitMQ 界面如下:

  

配置优化

  

  Spring Cloud 微服务开发之所以简单,除了官方做了许多彻底的封装之外还有一个优点就是约定大于配置。开发人员仅需规定应用中不符约定的部分,在没有规定配置的地方采用默认配置,以力求最简配置为核心思想。

简单理解就是:Spring 遵循了推荐默认配置的思想,当存在特殊需求时候,自定义配置即可否则无需配置。

  

  在 Spring Cloud Stream 中,@Output("output")@Input("input") 注解的 value 默认即为绑定的交换机名称。所以自定义消息通道的案例我们就可以重构为以下方式。

  

创建消息通道

  

  参考源码 Source.javaSink.java 创建自定义消息通道。

  自定义消息发送通道 MySource02.java

package com.example.channel;

import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel; /**
* 自定义消息发送通道
*/
public interface MySource02 { String MY_OUTPUT = "default.message"; @Output(MY_OUTPUT)
MessageChannel myOutput(); }

  自定义消息接收通道 MySink02.java

package com.example.channel;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel; /**
* 自定义消息接收通道
*/
public interface MySink02 { String MY_INPUT = "default.message"; @Input(MY_INPUT)
SubscribableChannel myInput(); }

  

配置文件

  

  消息生产者。

server:
port: 8001 # 端口 spring:
application:
name: stream-producer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址

  

  消息消费者。

server:
port: 8002 # 端口 spring:
application:
name: stream-consumer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址

  

代码重构

  

  消息生产者 MyMessageProducer02.java

package com.example.producer;

import com.example.channel.MySource02;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component; /**
* 消息生产者
*/
@Component
@EnableBinding(MySource02.class)
public class MyMessageProducer02 { @Autowired
private MySource02 mySource02; /**
* 发送消息
*
* @param message
*/
public void send(String message) {
mySource02.myOutput().send(MessageBuilder.withPayload(message).build());
} }

  

  消息消费者 MyMessageConsumer02.java

package com.example.consumer;

import com.example.channel.MySink02;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component; /**
* 消息消费者
*/
@Component
@EnableBinding(MySink02.class)
public class MyMessageConsumer02 { /**
* 接收消息
*
* @param message
*/
@StreamListener(MySink02.MY_INPUT)
public void receive(String message) {
System.out.println("message = " + message);
} }

  

测试

  

单元测试

  

  MessageProducerTest.java

package com.example;

import com.example.producer.MyMessageProducer02;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = {StreamProducerApplication.class})
public class MessageProducerTest { @Autowired
private MyMessageProducer02 myMessageProducer02; @Test
public void testMySend02() {
myMessageProducer02.send("约定大于配置");
} }

  

访问

  

  启动消息消费者,运行单元测试,消息消费者控制台打印结果如下:

message = 约定大于配置

  RabbitMQ 界面如下:

  

短信邮件发送案例

  

  一个消息驱动微服务应用可以既是消息生产者又是消息消费者。接下来模拟一个短信邮件发送的消息处理过程:

  • 原始消息发送至 source.message 交换机;
  • 消息驱动微服务应用通过 source.message 交换机接收原始消息,经过处理分别发送至 sms.messageemail.message 交换机;
  • 消息驱动微服务应用通过 sms.messageemail.message 交换机接收处理后的消息并发送短信和邮件。

  

创建消息通道

  

  发送原始消息,接收处理后的消息并发送短信和邮件的消息驱动微服务应用。

package com.example.channel;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel; /**
* 自定义消息通道
*/
public interface MyProcessor { String SOURCE_MESSAGE = "source.message";
String SMS_MESSAGE = "sms.message";
String EMAIL_MESSAGE = "email.message"; @Output(SOURCE_MESSAGE)
MessageChannel sourceOutput(); @Input(SMS_MESSAGE)
SubscribableChannel smsInput(); @Input(EMAIL_MESSAGE)
SubscribableChannel emailInput(); }

  

  接收原始消息,经过处理分别发送短信和邮箱的消息驱动微服务应用。

package com.example.channel;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel; /**
* 自定义消息通道
*/
public interface MyProcessor { String SOURCE_MESSAGE = "source.message";
String SMS_MESSAGE = "sms.message";
String EMAIL_MESSAGE = "email.message"; @Input(SOURCE_MESSAGE)
MessageChannel sourceOutput(); @Output(SMS_MESSAGE)
SubscribableChannel smsOutput(); @Output(EMAIL_MESSAGE)
SubscribableChannel emailOutput(); }

  

配置文件

  

  约定大于配置,配置文件只修改端口和应用名称即可,其他配置一致。

spring:
application:
name: stream-producer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址

  

spring:
application:
name: stream-consumer # 应用名称
rabbitmq:
host: 192.168.10.101 # 服务器 IP
port: 5672 # 服务器端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机地址

  

消息驱动微服务 A

  

发送消息

  

  发送原始消息 10086|10086@email.comsource.message 交换机。

package com.example.producer;

import com.example.channel.MyProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component; /**
* 消息生产者
*/
@Component
@EnableBinding(MyProcessor.class)
public class SourceMessageProducer { private Logger logger = LoggerFactory.getLogger(SourceMessageProducer.class); @Autowired
private MyProcessor myProcessor; /**
* 发送原始消息
*
* @param sourceMessage
*/
public void send(String sourceMessage) {
logger.info("原始消息发送成功,原始消息为:{}", sourceMessage);
myProcessor.sourceOutput().send(MessageBuilder.withPayload(sourceMessage).build());
} }

  

接收消息

  

  接收处理后的消息并发送短信和邮件。

package com.example.consumer;

import com.example.channel.MyProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component; /**
* 消息消费者
*/
@Component
@EnableBinding(MyProcessor.class)
public class SmsAndEmailMessageConsumer { private Logger logger = LoggerFactory.getLogger(SmsAndEmailMessageConsumer.class); /**
* 接收消息 电话号码
*
* @param phoneNum
*/
@StreamListener(MyProcessor.SMS_MESSAGE)
public void receiveSms(String phoneNum) {
logger.info("电话号码为:{},调用短信发送服务,发送短信...", phoneNum);
} /**
* 接收消息 邮箱地址
*
* @param emailAddress
*/
@StreamListener(MyProcessor.EMAIL_MESSAGE)
public void receiveEmail(String emailAddress) {
logger.info("邮箱地址为:{},调用邮件发送服务,发送邮件...", emailAddress);
} }

  

消息驱动微服务 B

  

接收消息

  

  接收原始消息 10086|10086@email.com 处理后并发送至 sms.messageemail.message 交换机。

package com.example.consumer;

import com.example.channel.MyProcessor;
import com.example.producer.SmsAndEmailMessageProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component; /**
* 消息消费者
*/
@Component
@EnableBinding(MyProcessor.class)
public class SourceMessageConsumer { private Logger logger = LoggerFactory.getLogger(SourceMessageConsumer.class); @Autowired
private SmsAndEmailMessageProducer smsAndEmailMessageProducer; /**
* 接收原始消息,处理后并发送
*
* @param sourceMessage
*/
@StreamListener(MyProcessor.SOURCE_MESSAGE)
public void receive(String sourceMessage) {
logger.info("原始消息接收成功,原始消息为:{}", sourceMessage);
// 发送消息 电话号码
smsAndEmailMessageProducer.sendSms(sourceMessage.split("\\|")[0]);
// 发送消息 邮箱地址
smsAndEmailMessageProducer.sendEmail(sourceMessage.split("\\|")[1]);
} }

  

发送消息

  

  发送电话号码 10086 和邮箱地址 10086@email.comsms.messageemail.message 交换机。

package com.example.producer;

import com.example.channel.MyProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component; /**
* 消息生产者
*/
@Component
@EnableBinding(MyProcessor.class)
public class SmsAndEmailMessageProducer { private Logger logger = LoggerFactory.getLogger(SmsAndEmailMessageProducer.class); @Autowired
private MyProcessor myProcessor; /**
* 发送消息 电话号码
*
* @param smsMessage
*/
public void sendSms(String smsMessage) {
logger.info("电话号码消息发送成功,消息为:{}", smsMessage);
myProcessor.smsOutput().send(MessageBuilder.withPayload(smsMessage).build());
} /**
* 发送消息 邮箱地址
*
* @param emailMessage
*/
public void sendEmail(String emailMessage) {
logger.info("邮箱地址消息发送成功,消息为:{}", emailMessage);
myProcessor.emailOutput().send(MessageBuilder.withPayload(emailMessage).build());
} }

  

测试

  

单元测试

  

  MessageProducerTest.java

package com.example;

import com.example.producer.SourceMessageProducer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = {StreamProducerApplication.class})
public class MessageProducerTest { @Autowired
private SourceMessageProducer sourceMessageProducer; @Test
public void testSendSource() {
sourceMessageProducer.send("10086|10086@email.com");
} }

  

访问

  

  消息驱动微服务 A 控制台打印结果如下:

电话号码为:10086,调用短信发送服务,发送短信...
邮箱地址为:10086@email.com,调用邮件发送服务,发送邮件...

  

  消息驱动微服务 B 控制台打印结果如下:

原始消息接收成功,原始消息为:10086|10086@email.com
电话号码消息发送成功,消息为:10086
邮箱地址消息发送成功,消息为:10086@email.com

  

  RabbitMQ 界面如下:

下一篇我们讲解 Stream 如何实现消息分组和消息分区,记得关注噢~

  本文采用 知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议

  大家可以通过 分类 查看更多关于 Spring Cloud 的文章。

  

  

Spring Cloud 系列之 Stream 消息驱动(一)的更多相关文章

  1. Spring Cloud 系列之 Stream 消息驱动(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Stream 消息驱动(一) 本篇文章讲解 Stream 如何实现消息分组和消息分区. 消息分组 如果有多个消息消费者 ...

  2. Spring Cloud 系列之 Bus 消息总线

    什么是消息总线 消息代理中间件构建一个共用的消息主题让所有微服务实例订阅,当该消息主题产生消息时会被所有微服务实例监听和消费. 消息代理又是什么?消息代理是一个消息验证.传输.路由的架构模式,主要用来 ...

  3. Spring Cloud 系列之 Consul 配置中心

    前面我们已经学习过 Spring Cloud Config 了: Spring Cloud 系列之 Config 配置中心(一) Spring Cloud 系列之 Config 配置中心(二) Spr ...

  4. spring cloud 2.x版本 Spring Cloud Stream消息驱动组件基础教程(kafaka篇)

    本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka-ri ...

  5. Spring Cloud 系列之 Spring Cloud Stream

    Spring Cloud Stream 是消息中间件组件,它集成了 kafka 和 rabbitmq .本篇文章以 Rabbit MQ 为消息中间件系统为基础,介绍 Spring Cloud Stre ...

  6. Spring Cloud系列(二) 介绍

    Spring Cloud系列(一) 介绍 Spring Cloud是基于Spring Boot实现的微服务架构开发工具.它为微服务架构中涉及的配置管理.服务治理.断路器.智能路由.微代理.控制总线.全 ...

  7. SpringCloud学习之Stream消息驱动【自定义通道】(十一)

    如果不清楚本篇内容的,请务必先去看完上一篇再看本篇,否则阅读起来可能会有部分障碍和困难: 上一篇文章<SpringCloud学习之Stream消息驱动[默认通道](十)>我们简单用自定义通 ...

  8. SpringCloud(七)Stream消息驱动

    Stream消息驱动 概述 屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型 官网:https://cloud.spring.io/spring-cloud-static/spring-cl ...

  9. Spring Cloud 系列之 Sleuth 链路追踪(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Sleuth 链路追踪(一) 本篇文章讲解 Sleuth 基于 Zipkin 存储链路追踪数据至 MySQL,Elas ...

随机推荐

  1. 空间复杂度(Space Complexity)

    空间复杂度(Space Complexity) 算法得存储量包括: 1.程序本身所占空间. 2.输入数据所占空间. 3.辅助变量所占空间. 输入数据所占空间只取决于问题本身,和算法无关,则只需分析除输 ...

  2. 实验十一 MySQLl备份与恢复1

    实验十一 MySQL备份与恢复 一.  实验内容: 1. 使用SQL语句导入和导出表数据 2. 使用客户端工具备份还原数据库 3. 使用日志文件恢复数据库 二.  实验项目:学生成绩数据库 创建用于学 ...

  3. Java 为 Excel 中的行设置交替背景色

    在制作Excel表格时,通过将数据表中上下相邻的两行用不同的背景色填充,可以使各行的数据看起来更清楚,避免看错行,同时也能增加Excel表格的美观度.本文将介绍如何在Java程序中为 Excel 奇数 ...

  4. Unity 游戏框架搭建 2019 (二十五) 类的第一个作用 与 Obselete 属性

    在上一篇我们整理到了第七个示例,我们今天再接着往下整理.我们来看第八个示例: #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; ...

  5. MySQL(Linux)编码问题——网站刚刚上线就被光速打脸

    MySQL(Linux)编码问题--刚刚上线就被光速打脸 MySql默认编码问题 总结了一下,大致是这样的 修改数据库配置 在URL上加载参数 MySql默认编码问题 说到这里真的想哭,改了无数bug ...

  6. python:用cv2简单实现图片的水平、垂直翻转

    原图片的本地地址:D:/360Downloads/test.jpg 代码实现: # 导入cv2模块 import cv2 # 给出本地图片的地址 img_dir="D:/360Downloa ...

  7. eclipse 使用 快捷键

    ctrl + t :查看类的子类和实现类 ctrl + o 查看类实现的方法 ctrl + 1 相当于idea的 alt + enter 补全变量 syso 点  alt + / System.out ...

  8. python3 进程间通信之socket.socketpair()

    python3 进程间通信之socket.socketpair() socket.socketpair()是什么鬼东西? socket.socketpair()函数仅返回两个已经连接的套接字对象,参数 ...

  9. hive常用函数五

    复合类型构建操作 1. Map类型构建: map 语法: map (key1, value1, key2, value2, …) 说明:根据输入的key和value对构建map类型 举例: hive& ...

  10. For循环详解

    for语句 学过c语言都对循环结构不陌生,尤其是for循环,他是C语言中最有特色的循环语句,使用最为灵活. 形式 结构:for(表达式1:表达式2:表达式3){循环体结构} 每部分的作用 表达式1:一 ...