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

  不同中间件内部实现方式是不一样的,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,比如项目中间件为 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 应用程序中:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
  4. </dependency>

  或者使用 Spring Cloud Stream RabbitMQ Starter:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
  4. </dependency>

  

  完整依赖如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.example</groupId>
  6. <artifactId>stream-producer</artifactId>
  7. <version>1.0-SNAPSHOT</version>
  8. <!-- 继承父依赖 -->
  9. <parent>
  10. <groupId>com.example</groupId>
  11. <artifactId>stream-demo</artifactId>
  12. <version>1.0-SNAPSHOT</version>
  13. </parent>
  14. <!-- 项目依赖 -->
  15. <dependencies>
  16. <!-- netflix eureka client 依赖 -->
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  20. </dependency>
  21. <!-- spring cloud stream binder rabbit 绑定器依赖 -->
  22. <dependency>
  23. <groupId>org.springframework.cloud</groupId>
  24. <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
  25. </dependency>
  26. <!-- spring boot test 依赖 -->
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-test</artifactId>
  30. <scope>test</scope>
  31. <exclusions>
  32. <exclusion>
  33. <groupId>org.junit.vintage</groupId>
  34. <artifactId>junit-vintage-engine</artifactId>
  35. </exclusion>
  36. </exclusions>
  37. </dependency>
  38. </dependencies>
  39. </project>

  

配置文件

  

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

  1. server:
  2. port: 8001 # 端口
  3. spring:
  4. application:
  5. name: stream-producer # 应用名称
  6. rabbitmq:
  7. host: 192.168.10.101 # 服务器 IP
  8. port: 5672 # 服务器端口
  9. username: guest # 用户名
  10. password: guest # 密码
  11. virtual-host: / # 虚拟主机地址
  12. cloud:
  13. stream:
  14. bindings:
  15. # 消息发送通道
  16. # 与 org.springframework.cloud.stream.messaging.Source 中的 @Output("output") 注解的 value 相同
  17. output:
  18. destination: stream.message # 绑定的交换机名称
  19. # 配置 Eureka Server 注册中心
  20. eureka:
  21. instance:
  22. prefer-ip-address: true # 是否使用 ip 地址注册
  23. instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  24. client:
  25. service-url: # 设置服务注册中心地址
  26. defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

  

发送消息

  

  MessageProducer.java

  1. package com.example.producer;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.cloud.stream.annotation.EnableBinding;
  4. import org.springframework.cloud.stream.messaging.Source;
  5. import org.springframework.messaging.support.MessageBuilder;
  6. import org.springframework.stereotype.Component;
  7. /**
  8. * 消息生产者
  9. */
  10. @Component
  11. @EnableBinding(Source.class)
  12. public class MessageProducer {
  13. @Autowired
  14. private Source source;
  15. /**
  16. * 发送消息
  17. *
  18. * @param message
  19. */
  20. public void send(String message) {
  21. source.output().send(MessageBuilder.withPayload(message).build());
  22. }
  23. }

  

启动类

  

  StreamProducerApplication.java

  1. package com.example;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class StreamProducerApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(StreamProducerApplication.class);
  8. }
  9. }

  

消息消费者

  

创建项目

  

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

  

添加依赖

  

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

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
  4. </dependency>

  或者使用 Spring Cloud Stream RabbitMQ Starter:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
  4. </dependency>

  

  完整依赖如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.example</groupId>
  6. <artifactId>stream-consumer</artifactId>
  7. <version>1.0-SNAPSHOT</version>
  8. <!-- 继承父依赖 -->
  9. <parent>
  10. <groupId>com.example</groupId>
  11. <artifactId>stream-demo</artifactId>
  12. <version>1.0-SNAPSHOT</version>
  13. </parent>
  14. <!-- 项目依赖 -->
  15. <dependencies>
  16. <!-- netflix eureka client 依赖 -->
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  20. </dependency>
  21. <!-- spring cloud stream binder rabbit 绑定器依赖 -->
  22. <dependency>
  23. <groupId>org.springframework.cloud</groupId>
  24. <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
  25. </dependency>
  26. <!-- spring boot test 依赖 -->
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-test</artifactId>
  30. <scope>test</scope>
  31. <exclusions>
  32. <exclusion>
  33. <groupId>org.junit.vintage</groupId>
  34. <artifactId>junit-vintage-engine</artifactId>
  35. </exclusion>
  36. </exclusions>
  37. </dependency>
  38. </dependencies>
  39. </project>

  

配置文件

  

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

  1. server:
  2. port: 8002 # 端口
  3. spring:
  4. application:
  5. name: stream-consumer # 应用名称
  6. rabbitmq:
  7. host: 192.168.10.101 # 服务器 IP
  8. port: 5672 # 服务器端口
  9. username: guest # 用户名
  10. password: guest # 密码
  11. virtual-host: / # 虚拟主机地址
  12. cloud:
  13. stream:
  14. bindings:
  15. # 消息接收通道
  16. # 与 org.springframework.cloud.stream.messaging.Sink 中的 @Input("input") 注解的 value 相同
  17. input:
  18. destination: stream.message # 绑定的交换机名称
  19. # 配置 Eureka Server 注册中心
  20. eureka:
  21. instance:
  22. prefer-ip-address: true # 是否使用 ip 地址注册
  23. instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  24. client:
  25. service-url: # 设置服务注册中心地址
  26. defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

  

接收消息

  

  MessageConsumer.java

  1. package com.example.consumer;
  2. import org.springframework.cloud.stream.annotation.EnableBinding;
  3. import org.springframework.cloud.stream.annotation.StreamListener;
  4. import org.springframework.cloud.stream.messaging.Sink;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * 消息消费者
  8. */
  9. @Component
  10. @EnableBinding(Sink.class)
  11. public class MessageConsumer {
  12. /**
  13. * 接收消息
  14. *
  15. * @param message
  16. */
  17. @StreamListener(Sink.INPUT)
  18. public void receive(String message) {
  19. System.out.println("message = " + message);
  20. }
  21. }

  

启动类

  

  StreamConsumerApplication.java

  1. package com.example;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class StreamConsumerApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(StreamConsumerApplication.class);
  8. }
  9. }

  

测试

  

单元测试

  

  MessageProducerTest.java

  1. package com.example;
  2. import com.example.producer.MessageProducer;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. @SpringBootTest(classes = {StreamProducerApplication.class})
  7. public class MessageProducerTest {
  8. @Autowired
  9. private MessageProducer messageProducer;
  10. @Test
  11. public void testSend() {
  12. messageProducer.send("hello spring cloud stream");
  13. }
  14. }

  

访问

  

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

  1. message = hello spring cloud stream

  RabbitMQ 界面如下:

  

自定义消息通道

  

创建消息通道

  

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

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

  1. package com.example.channel;
  2. import org.springframework.cloud.stream.annotation.Output;
  3. import org.springframework.messaging.MessageChannel;
  4. /**
  5. * 自定义消息发送通道
  6. */
  7. public interface MySource {
  8. String MY_OUTPUT = "my_output";
  9. @Output(MY_OUTPUT)
  10. MessageChannel myOutput();
  11. }

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

  1. package com.example.channel;
  2. import org.springframework.cloud.stream.annotation.Input;
  3. import org.springframework.messaging.SubscribableChannel;
  4. /**
  5. * 自定义消息接收通道
  6. */
  7. public interface MySink {
  8. String MY_INPUT = "my_input";
  9. @Input(MY_INPUT)
  10. SubscribableChannel myInput();
  11. }

  

配置文件

  

  消息生产者。

  1. server:
  2. port: 8001 # 端口
  3. spring:
  4. application:
  5. name: stream-producer # 应用名称
  6. rabbitmq:
  7. host: 192.168.10.101 # 服务器 IP
  8. port: 5672 # 服务器端口
  9. username: guest # 用户名
  10. password: guest # 密码
  11. virtual-host: / # 虚拟主机地址
  12. cloud:
  13. stream:
  14. bindings:
  15. # 消息发送通道
  16. # 与 org.springframework.cloud.stream.messaging.Source 中的 @Output("output") 注解的 value 相同
  17. output:
  18. destination: stream.message # 绑定的交换机名称
  19. my_output:
  20. destination: my.message # 绑定的交换机名称

  

  消息消费者。

  1. server:
  2. port: 8002 # 端口
  3. spring:
  4. application:
  5. name: stream-consumer # 应用名称
  6. rabbitmq:
  7. host: 192.168.10.101 # 服务器 IP
  8. port: 5672 # 服务器端口
  9. username: guest # 用户名
  10. password: guest # 密码
  11. virtual-host: / # 虚拟主机地址
  12. cloud:
  13. stream:
  14. bindings:
  15. # 消息接收通道
  16. # 与 org.springframework.cloud.stream.messaging.Sink 中的 @Input("input") 注解的 value 相同
  17. input:
  18. destination: stream.message # 绑定的交换机名称
  19. my_input:
  20. destination: my.message # 绑定的交换机名称

  

代码重构

  

  消息生产者 MyMessageProducer.java

  1. package com.example.producer;
  2. import com.example.channel.MySource;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.cloud.stream.annotation.EnableBinding;
  5. import org.springframework.messaging.support.MessageBuilder;
  6. import org.springframework.stereotype.Component;
  7. /**
  8. * 消息生产者
  9. */
  10. @Component
  11. @EnableBinding(MySource.class)
  12. public class MyMessageProducer {
  13. @Autowired
  14. private MySource mySource;
  15. /**
  16. * 发送消息
  17. *
  18. * @param message
  19. */
  20. public void send(String message) {
  21. mySource.myOutput().send(MessageBuilder.withPayload(message).build());
  22. }
  23. }

  

  消息消费者 MyMessageConsumer.java

  1. package com.example.consumer;
  2. import com.example.channel.MySink;
  3. import org.springframework.cloud.stream.annotation.EnableBinding;
  4. import org.springframework.cloud.stream.annotation.StreamListener;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * 消息消费者
  8. */
  9. @Component
  10. @EnableBinding(MySink.class)
  11. public class MyMessageConsumer {
  12. /**
  13. * 接收消息
  14. *
  15. * @param message
  16. */
  17. @StreamListener(MySink.MY_INPUT)
  18. public void receive(String message) {
  19. System.out.println("message = " + message);
  20. }
  21. }

  

测试

  

单元测试

  

  MessageProducerTest.java

  1. package com.example;
  2. import com.example.producer.MyMessageProducer;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. @SpringBootTest(classes = {StreamProducerApplication.class})
  7. public class MessageProducerTest {
  8. @Autowired
  9. private MyMessageProducer myMessageProducer;
  10. @Test
  11. public void testMySend() {
  12. myMessageProducer.send("hello spring cloud stream");
  13. }
  14. }

  

访问

  

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

  1. message = hello spring cloud stream

  RabbitMQ 界面如下:

  

配置优化

  

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

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

  

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

  

创建消息通道

  

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

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

  1. package com.example.channel;
  2. import org.springframework.cloud.stream.annotation.Output;
  3. import org.springframework.messaging.MessageChannel;
  4. /**
  5. * 自定义消息发送通道
  6. */
  7. public interface MySource02 {
  8. String MY_OUTPUT = "default.message";
  9. @Output(MY_OUTPUT)
  10. MessageChannel myOutput();
  11. }

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

  1. package com.example.channel;
  2. import org.springframework.cloud.stream.annotation.Input;
  3. import org.springframework.messaging.SubscribableChannel;
  4. /**
  5. * 自定义消息接收通道
  6. */
  7. public interface MySink02 {
  8. String MY_INPUT = "default.message";
  9. @Input(MY_INPUT)
  10. SubscribableChannel myInput();
  11. }

  

配置文件

  

  消息生产者。

  1. server:
  2. port: 8001 # 端口
  3. spring:
  4. application:
  5. name: stream-producer # 应用名称
  6. rabbitmq:
  7. host: 192.168.10.101 # 服务器 IP
  8. port: 5672 # 服务器端口
  9. username: guest # 用户名
  10. password: guest # 密码
  11. virtual-host: / # 虚拟主机地址

  

  消息消费者。

  1. server:
  2. port: 8002 # 端口
  3. spring:
  4. application:
  5. name: stream-consumer # 应用名称
  6. rabbitmq:
  7. host: 192.168.10.101 # 服务器 IP
  8. port: 5672 # 服务器端口
  9. username: guest # 用户名
  10. password: guest # 密码
  11. virtual-host: / # 虚拟主机地址

  

代码重构

  

  消息生产者 MyMessageProducer02.java

  1. package com.example.producer;
  2. import com.example.channel.MySource02;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.cloud.stream.annotation.EnableBinding;
  5. import org.springframework.messaging.support.MessageBuilder;
  6. import org.springframework.stereotype.Component;
  7. /**
  8. * 消息生产者
  9. */
  10. @Component
  11. @EnableBinding(MySource02.class)
  12. public class MyMessageProducer02 {
  13. @Autowired
  14. private MySource02 mySource02;
  15. /**
  16. * 发送消息
  17. *
  18. * @param message
  19. */
  20. public void send(String message) {
  21. mySource02.myOutput().send(MessageBuilder.withPayload(message).build());
  22. }
  23. }

  

  消息消费者 MyMessageConsumer02.java

  1. package com.example.consumer;
  2. import com.example.channel.MySink02;
  3. import org.springframework.cloud.stream.annotation.EnableBinding;
  4. import org.springframework.cloud.stream.annotation.StreamListener;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * 消息消费者
  8. */
  9. @Component
  10. @EnableBinding(MySink02.class)
  11. public class MyMessageConsumer02 {
  12. /**
  13. * 接收消息
  14. *
  15. * @param message
  16. */
  17. @StreamListener(MySink02.MY_INPUT)
  18. public void receive(String message) {
  19. System.out.println("message = " + message);
  20. }
  21. }

  

测试

  

单元测试

  

  MessageProducerTest.java

  1. package com.example;
  2. import com.example.producer.MyMessageProducer02;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. @SpringBootTest(classes = {StreamProducerApplication.class})
  7. public class MessageProducerTest {
  8. @Autowired
  9. private MyMessageProducer02 myMessageProducer02;
  10. @Test
  11. public void testMySend02() {
  12. myMessageProducer02.send("约定大于配置");
  13. }
  14. }

  

访问

  

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

  1. message = 约定大于配置

  RabbitMQ 界面如下:

  

短信邮件发送案例

  

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

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

  

创建消息通道

  

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

  1. package com.example.channel;
  2. import org.springframework.cloud.stream.annotation.Input;
  3. import org.springframework.cloud.stream.annotation.Output;
  4. import org.springframework.messaging.MessageChannel;
  5. import org.springframework.messaging.SubscribableChannel;
  6. /**
  7. * 自定义消息通道
  8. */
  9. public interface MyProcessor {
  10. String SOURCE_MESSAGE = "source.message";
  11. String SMS_MESSAGE = "sms.message";
  12. String EMAIL_MESSAGE = "email.message";
  13. @Output(SOURCE_MESSAGE)
  14. MessageChannel sourceOutput();
  15. @Input(SMS_MESSAGE)
  16. SubscribableChannel smsInput();
  17. @Input(EMAIL_MESSAGE)
  18. SubscribableChannel emailInput();
  19. }

  

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

  1. package com.example.channel;
  2. import org.springframework.cloud.stream.annotation.Input;
  3. import org.springframework.cloud.stream.annotation.Output;
  4. import org.springframework.messaging.MessageChannel;
  5. import org.springframework.messaging.SubscribableChannel;
  6. /**
  7. * 自定义消息通道
  8. */
  9. public interface MyProcessor {
  10. String SOURCE_MESSAGE = "source.message";
  11. String SMS_MESSAGE = "sms.message";
  12. String EMAIL_MESSAGE = "email.message";
  13. @Input(SOURCE_MESSAGE)
  14. MessageChannel sourceOutput();
  15. @Output(SMS_MESSAGE)
  16. SubscribableChannel smsOutput();
  17. @Output(EMAIL_MESSAGE)
  18. SubscribableChannel emailOutput();
  19. }

  

配置文件

  

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

  1. spring:
  2. application:
  3. name: stream-producer # 应用名称
  4. rabbitmq:
  5. host: 192.168.10.101 # 服务器 IP
  6. port: 5672 # 服务器端口
  7. username: guest # 用户名
  8. password: guest # 密码
  9. virtual-host: / # 虚拟主机地址

  

  1. spring:
  2. application:
  3. name: stream-consumer # 应用名称
  4. rabbitmq:
  5. host: 192.168.10.101 # 服务器 IP
  6. port: 5672 # 服务器端口
  7. username: guest # 用户名
  8. password: guest # 密码
  9. virtual-host: / # 虚拟主机地址

  

消息驱动微服务 A

  

发送消息

  

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

  1. package com.example.producer;
  2. import com.example.channel.MyProcessor;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cloud.stream.annotation.EnableBinding;
  7. import org.springframework.messaging.support.MessageBuilder;
  8. import org.springframework.stereotype.Component;
  9. /**
  10. * 消息生产者
  11. */
  12. @Component
  13. @EnableBinding(MyProcessor.class)
  14. public class SourceMessageProducer {
  15. private Logger logger = LoggerFactory.getLogger(SourceMessageProducer.class);
  16. @Autowired
  17. private MyProcessor myProcessor;
  18. /**
  19. * 发送原始消息
  20. *
  21. * @param sourceMessage
  22. */
  23. public void send(String sourceMessage) {
  24. logger.info("原始消息发送成功,原始消息为:{}", sourceMessage);
  25. myProcessor.sourceOutput().send(MessageBuilder.withPayload(sourceMessage).build());
  26. }
  27. }

  

接收消息

  

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

  1. package com.example.consumer;
  2. import com.example.channel.MyProcessor;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.cloud.stream.annotation.EnableBinding;
  6. import org.springframework.cloud.stream.annotation.StreamListener;
  7. import org.springframework.stereotype.Component;
  8. /**
  9. * 消息消费者
  10. */
  11. @Component
  12. @EnableBinding(MyProcessor.class)
  13. public class SmsAndEmailMessageConsumer {
  14. private Logger logger = LoggerFactory.getLogger(SmsAndEmailMessageConsumer.class);
  15. /**
  16. * 接收消息 电话号码
  17. *
  18. * @param phoneNum
  19. */
  20. @StreamListener(MyProcessor.SMS_MESSAGE)
  21. public void receiveSms(String phoneNum) {
  22. logger.info("电话号码为:{},调用短信发送服务,发送短信...", phoneNum);
  23. }
  24. /**
  25. * 接收消息 邮箱地址
  26. *
  27. * @param emailAddress
  28. */
  29. @StreamListener(MyProcessor.EMAIL_MESSAGE)
  30. public void receiveEmail(String emailAddress) {
  31. logger.info("邮箱地址为:{},调用邮件发送服务,发送邮件...", emailAddress);
  32. }
  33. }

  

消息驱动微服务 B

  

接收消息

  

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

  1. package com.example.consumer;
  2. import com.example.channel.MyProcessor;
  3. import com.example.producer.SmsAndEmailMessageProducer;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.cloud.stream.annotation.EnableBinding;
  8. import org.springframework.cloud.stream.annotation.StreamListener;
  9. import org.springframework.stereotype.Component;
  10. /**
  11. * 消息消费者
  12. */
  13. @Component
  14. @EnableBinding(MyProcessor.class)
  15. public class SourceMessageConsumer {
  16. private Logger logger = LoggerFactory.getLogger(SourceMessageConsumer.class);
  17. @Autowired
  18. private SmsAndEmailMessageProducer smsAndEmailMessageProducer;
  19. /**
  20. * 接收原始消息,处理后并发送
  21. *
  22. * @param sourceMessage
  23. */
  24. @StreamListener(MyProcessor.SOURCE_MESSAGE)
  25. public void receive(String sourceMessage) {
  26. logger.info("原始消息接收成功,原始消息为:{}", sourceMessage);
  27. // 发送消息 电话号码
  28. smsAndEmailMessageProducer.sendSms(sourceMessage.split("\\|")[0]);
  29. // 发送消息 邮箱地址
  30. smsAndEmailMessageProducer.sendEmail(sourceMessage.split("\\|")[1]);
  31. }
  32. }

  

发送消息

  

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

  1. package com.example.producer;
  2. import com.example.channel.MyProcessor;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cloud.stream.annotation.EnableBinding;
  7. import org.springframework.messaging.support.MessageBuilder;
  8. import org.springframework.stereotype.Component;
  9. /**
  10. * 消息生产者
  11. */
  12. @Component
  13. @EnableBinding(MyProcessor.class)
  14. public class SmsAndEmailMessageProducer {
  15. private Logger logger = LoggerFactory.getLogger(SmsAndEmailMessageProducer.class);
  16. @Autowired
  17. private MyProcessor myProcessor;
  18. /**
  19. * 发送消息 电话号码
  20. *
  21. * @param smsMessage
  22. */
  23. public void sendSms(String smsMessage) {
  24. logger.info("电话号码消息发送成功,消息为:{}", smsMessage);
  25. myProcessor.smsOutput().send(MessageBuilder.withPayload(smsMessage).build());
  26. }
  27. /**
  28. * 发送消息 邮箱地址
  29. *
  30. * @param emailMessage
  31. */
  32. public void sendEmail(String emailMessage) {
  33. logger.info("邮箱地址消息发送成功,消息为:{}", emailMessage);
  34. myProcessor.emailOutput().send(MessageBuilder.withPayload(emailMessage).build());
  35. }
  36. }

  

测试

  

单元测试

  

  MessageProducerTest.java

  1. package com.example;
  2. import com.example.producer.SourceMessageProducer;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. @SpringBootTest(classes = {StreamProducerApplication.class})
  7. public class MessageProducerTest {
  8. @Autowired
  9. private SourceMessageProducer sourceMessageProducer;
  10. @Test
  11. public void testSendSource() {
  12. sourceMessageProducer.send("10086|10086@email.com");
  13. }
  14. }

  

访问

  

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

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

  

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

  1. 原始消息接收成功,原始消息为:10086|10086@email.com
  2. 电话号码消息发送成功,消息为:10086
  3. 邮箱地址消息发送成功,消息为: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. MySql 分组函数

    #二.分组函数/*功能:用作统计使用,又称为聚合函数或统计函数或组函数 分类:sum 求和.avg 平均值.max 最大值 .min 最小值 .count 计算个数 特点:1.sum.avg一般用于处 ...

  2. 使用onclick/表单submit跳转到其他页面

    使用onclick 如果是本页显示可以直接用location,方法如下: - onclick="javascript:window.location.href='URL'" - o ...

  3. MyBatis 学习笔记(1)

    MyBatis 的基本构成 SqlSessionFactoryBuilder(构造器):它会根据配置信息或者代码来生成 SqlSessionFactory(工厂接口) SqlSessionFactor ...

  4. 浅谈头文件(.h)和源文件(.cpp)的区别

    浅谈头文件(.h)和源文件(.cpp)的区别 本人原来在大一写C的时候,都是所有代码写在一个文件里一锅乱煮.经过自己开始写程序之后,发现一个工程只有一定是由多个不同功能.分门别类展开的文件构成的.一锅 ...

  5. web font各浏览器兼容问题以及格式

    语法: @font-face { font-family: <identifier>; src: <fontsrc> [, <fontsrc>]*; <fon ...

  6. JSP 简介(转载)

    什么是Java Server Pages? JSP全称Java Server Pages,是一种动态网页开发技术.它使用JSP标签在HTML网页中插入Java代码.标签通常以<%开头以%> ...

  7. golang--深入简出,带你用golang的反射撸一个公用后台查询方法

    一些基本方法 本篇不会介绍反射的基本概念和原理等,会从每个常用的方法入手,讲解一些基本和进阶用法,反射不太适合在业务层使用,因为会几何倍的降低运行速度,而且用反射做出来的程序健壮度不高,一旦一个环节没 ...

  8. MyBatis(十):Mybatis缓存的重要内容

    本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出一遍就懂!b站搜索狂神说或点击下面链接 https://space.bilibili.com/95256449?spm_id_from=33 ...

  9. 2020 PHP 初级 / 基础面试题,祝你金三银四跳槽加薪 (适合基础不牢固的 PHPer)

    1.PHP 语言的一大优势是跨平台,什么是跨平台? PHP 的运行环境最优搭配为 Apache+MySQL+PHP,此运行环境可以在不同操作系统(例如 windows.Linux 等)上配置,不受操作 ...

  10. <E> 泛型

    /* * 使用集合存储自定义对象并遍历 * 由于集合可以存储任意类型的对象,当我们存储了不同类型的对象,就有可能在转换的时候出现类型转换异常, * 所以java为了解决这个问题,给我们提供了一种机制, ...