SpringCloud Stream构建消息驱动微服务

微服务目的

微服务目的是为了更好的进行分布式系统开发,拆分单体应用为多个服务每个服务都是一个独立运行的项目(松耦合)。

Spring Cloud Stream 的几个概念

Spring Cloud Stream is a framework for building message-driven microservice applications.
总结一句话就是:屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型

官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。
在这里插入图片描述

应用程序通过 inputs 或者 outputs 来与 SpringCloud Stream 中binder 交互,通过我们的配置来 binding ,而 SpringCloud Stream 的 binder 负责与中间件进行交互。所以,我们只需要搞清楚如何与 SpringCloud Stream 交互就可以使用消息驱动。

为什么要用SpringCloud Stream

比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同, 像RabbitMQ有exchange, kafka有 Topic和Partitions分区

在这里插入图片描述

这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰, 我们如果用了两个消息队列的其中一种, 后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的, 一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候SpringCloud Stream给我们提供了一种解耦合的方式。

结合 RabbitMQ 使用

示例源码下载:https://download.csdn.net/download/weixin_44790046/12527597

示例所用工程介绍

04_cloud-eureka-server-port7001 => Eureka服务注册中心1
05_cloud-eureka-server-port7002 => Eureka服务注册中心2
以上两个工程是一个简易的Eureka集群环境,两个注册中心相互守望
在这里插入图片描述
在这里插入图片描述
cloud-api-commons => 存放一些公共代码,工具类
cloud-stream-rabbitmq-provider-port8801 => 消息发送方
cloud-stream-rabbitmq-consumer-port8802 => 消息接收方1
cloud-stream-rabbitmq-consumer-port8803 => 消息接收方2

8801消息发送方yml配置详解

#当前服务端口号
server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider #服务名称
  cloud:
    stream:
      binders:  #在此处配置要绑定的rabbitmq的服务信息
        defaultRabbit: #表示定义的名称,用于与binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 192.168.200.***
                port: 5672
                username: ***
                password: ****
                virtual-host: /huaxin #指定使用的虚拟主机
      bindings: #服务的整合处理
        output: #默认通道的名称
          destination: cloud-stream-rabbit  #exchange名称,交换模式默认是topic
          content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain”
          default-binder: defaultRabbit  #设置要绑定的消息服务的具体设置

        outputOrder: #自定义的通道的名称
          destination: customExchange  #exchange名称,交换模式默认是topic
          content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain”
          default-binder: defaultRabbit  #设置要绑定的消息服务的具体设置

#eureka客户端配置
eureka:
  client:
    register-with-eureka: true  #表示是否将自己注册进EurekaServer默认为true。
    fetchRegistry: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #defaultZone: http://localhost:7001/eureka #单机版(一般不用)
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版(注册到两个服务中心)
  instance:
    instance-id: cloud-stream-provider8801  #修改在Eureka显示的主机名称
    prefer-ip-address: true #服务访问路径显示ip地址
    lease-renewal-interval-in-seconds: 2  #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-expiration-duration-in-seconds: 5 #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务

8802、8003消息发送方yml配置详解

#当前服务端口号
server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer #服务名称
  cloud:
    stream:
      binders:  #在此处配置要绑定的rabbitmq的服务信息
        defaultRabbit: #表示定义的名称,用于与binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 192.168.200.***
                port: 5672
                username: ****
                password: ***
                virtual-host: /huaxin #指定使用的虚拟主机
      bindings: #服务的整合处理
        input: #默认输入通道的名称
          destination: cloud-stream-rabbit  #exchange名称,交换模式默认是topic
          group: myQueue #指定组名称(持久化),8802、8803都在这个组,同一个组的多个微服务实例,每次只会有一个拿到,这样避免了重复消费
          content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain”
          default-binder: defaultRabbit  #设置要绑定的消息服务的具体设置

        inputOrder: #自定义输入通道的名称
          destination: customExchange  #exchange名称,交换模式默认是topic
          group: myCustomQueue #指定组名称(持久化),8802、8803都在这个组,同一个组的多个微服务实例,每次只会有一个拿到,这样避免了重复消费
          content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain”
          default-binder: defaultRabbit  #设置要绑定的消息服务的具体设置

#eureka客户端配置
eureka:
  client:
    register-with-eureka: true  #表示是否将自己注册进EurekaServer默认为true。
    fetchRegistry: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #defaultZone: http://localhost:7001/eureka #单机版(一般不用)
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版(注册到两个服务中心)
  instance:
    instance-id: cloud-stream-consumer8802  #修改在Eureka显示的主机名称
    prefer-ip-address: true #服务访问路径显示ip地址
    lease-renewal-interval-in-seconds: 2  #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-expiration-duration-in-seconds: 5 #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务

消息发送通道

SpringCloud Stream 基本用法,需要定义一个接口,下面是Stream内置的一个消息发送接口。
在这里插入图片描述

注解 @Outout 对应的方法,需要返回 MessageChannel,并且传入一个参数值。
这就声明了一个输出通道的名称为output
在这里插入图片描述

消息发送方发送消息具体实现

package huaxin.service.impl;

import huaxin.service.MessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.support.MessageBuilder;

import javax.annotation.Resource;
import java.util.UUID;

/**
 * <br>Source默认的输出通道
 * @author 孙启新
 * <br>FileName: MessageProviderImpl
 * <br>Date: 2020/06/16 12:24:26
 */
@EnableBinding({Source.class})    //定义消息的推送管道
public class MessageProviderImpl implements MessageProvider {
    /**
     * 注入默认的消息发送管道
     */
    @Resource(name = "output")  //指定要引入的bean名称
    private MessageChannel output;

    /**
     * 发送消息,默认输出通道
     *
     * @return
     */
    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(serial).build());
        System.out.println("*****默认输出通道serial: "+serial);
        return serial;
    }
}

以上代码就完成了最基本的消息生产者部分。

消息接收

与上面类似,需要定义一个接口,下面是Stream内置的一个消息接收接口。
在这里插入图片描述

注解 @Input 对应的方法,需要返回 SubscribableChannel,并且传入一个参数值。
这就声明了一个输入通道的名称为input
在这里插入图片描述

消息接收方接收消息具体实现

package huaxin.controller;

import huaxin.config.CustomOrderProcessor;
import org.springframework.beans.factory.annotation.Value;
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.stereotype.Component;

/**
 * <br>Sink默认的输入通道
 * @author 孙启新
 * <br>FileName: ReceiveMessageListenerController
 * <br>Date: 2020/06/16 13:43:24
 */
@Component
@EnableBinding({Sink.class})  //定义消息的接收管道
public class ReceiveMessageListenerController {
    @Value("${server.port}")
    private String serverPort;

    /**
     * <br>@StreamListener: 监听队列,用于消费者的队列的消息接收,默认输入通道
     *
     * @param message
     */
    @StreamListener(Sink.INPUT)
    public void input(Message<String> message) {
        System.out.println("默认输入通道消费者1号,接受到的消息:"+message.getPayload()+"\t port:"+serverPort);
    }
}

8803消费者2与8802消费者1代码相同。
以上代码就完成了最基本的消息消费者部分。

测试:

生成者发送消息
在这里插入图片描述
消费者1、2接收情况
在这里插入图片描述
在这里插入图片描述

自定义消息发送接收

SpringCloud Stream 内置了两个接口,分别定义了 binding 为 “input” 的输入通道,和 “output” 的输出通道,而在我们实际使用中,往往是需要定义各种输入输出通道。使用方法也很简单。

定义一个接口(生产方)

package huaxin.config;

import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;

/**
 * 不使用SpringCloudStream默认的input输入通道,output输出通道
 *  <p>自定义各种输入输出通道</p>
 * @author 孙启新
 * <br>FileName: CustomOrderProcessor
 * <br>Date: 2020/06/16 14:55:40
 */
public interface CustomOrderProcessor {
    String OUTPUT_ORDER = "outputOrder";
    /**
     * 自定义输出的通道
     * @return
     */
    @Output(OUTPUT_ORDER)
    MessageChannel outputOrder();
}

一个接口中,可以定义无数个输入输出通道,可以根据实际业务情况划分。上述的接口,为了测试简单只定义了一个订单输出通道。

使用时,需要在 @EnableBinding 注解中,添加自定义的接口。
在这里插入图片描述

注意:

@Resource注入自定义的通道时需指定名称
在这里插入图片描述

MessageProviderImpl实现类中再添加一个方法

 /**
     * 发送消息,自定义输出通道
     *
     * @return
     */
    @Override
    public String customSend() {
        String serial = UUID.randomUUID().toString();
        customOutput.send(MessageBuilder.withPayload(serial).build());
        System.out.println("*****自定义输出通道serial: "+serial);
        return serial;
    }

最后yml配置文件中配置一下我们的自定义通道即可(上面yml已配)
在这里插入图片描述
以上代码就完成了最基本的自定义的消息生产者部分。

定义一个接口(消费方)

package huaxin.config;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;

/**
 * 不使用SpringCloudStream默认的input输入通道,output输出通道
 *  <p>自定义各种输入输出通道</p>
 * @author 孙启新
 * <br>FileName: CustomOrderProcessor
 * <br>Date: 2020/06/16 14:55:40
 */
public interface CustomOrderProcessor {
    String INPUT_ORDER = "inputOrder";

    /**
     * 自定义输入的通道
     * @return
     */
    @Input(INPUT_ORDER)
    SubscribableChannel inputOrder();
}

一个接口中,可以定义无数个输入输出通道,可以根据实际业务情况划分。上述的接口,为了测试简单只定义了一个订单输入通道。

同样,使用时,需要在 @EnableBinding 注解中,添加自定义的接口。
在这里插入图片描述
使用 @StreamListener 监听队列的时候,需要指定 CustomOrderProcessor.INPUT_ORDER
在这里插入图片描述
ReceiveMessageListenerController类中添加监听方法


    /**
     * <br>@StreamListener: 监听队列,用于消费者的队列的消息接收,自定义的输入通道
     *
     * @param message
     */
    @StreamListener(CustomOrderProcessor.INPUT_ORDER)
    public void CustomInput(Message<String> message) {
        System.out.println("自定义输入通道消费者2号,接受到的消息:"+message.getPayload()+"\t port:"+serverPort);
    }

最后yml配置文件中配置一下我们的自定义通道即可(上面yml已配)
在这里插入图片描述
以上代码就完成了最基本的自定义的消费者部分。
8803消费者2与8802消费者1代码相同。

测试:

生成者发送消息
在这里插入图片描述
两个消费者接受消息
在这里插入图片描述
在这里插入图片描述

分组与持久化

上述代码已经实现了分组与持久化,只需要在消费者端的 binding 添加配置项 spring.cloud.stream.bindings.[channelName].group = XXX 。对应的队列就是持久化,并且名称为:[channelName].XXX。、
在这里插入图片描述
在这里插入图片描述
如果不配置group的hauxin,SpringCloud Stream 会在 RabbitMQ 中创建一个临时的队列,程序关闭,对应的连接关闭的时候,该队列也会消失。而在实际使用中,我们需要一个持久化的队列,所以group分组属性在消息重复消费和消息持久化消费 避免消息丢失是非常重要的属性。

猜你喜欢

转载自blog.csdn.net/weixin_44790046/article/details/106788858