SpringCloud Stream消息驱动

Posted 蛋饼吧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud Stream消息驱动相关的知识,希望对你有一定的参考价值。

本笔记内容为尚硅谷SpringCloud框架开发Stream消息驱动部分

目录

一、消息驱动概述

1、什么是SpringCloudStream

2、设计思想

1.标准MQ

2.为什么用Cloud Stream

3、Spring Cloud Stream标准流程套路

4、编码API和常用注解

二、案例演示

​编辑

1、消息驱动之生产者 

2、消息驱动之消费者

三、分组消费与持久化

1.启动:

2.运行后有两个问题:有重复消费问题、消息持久化问题

3.重复消费

4.如何解决

分组 

持久化


一、消息驱动概述


1、什么是SpringCloudStream

屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型 

官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。

 

应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream中binder对象交互。

通过我们配置来binding(绑定) ,而 Spring Cloud Stream 的 binder对象负责与消息中间件交互。

所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。

 

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。

Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。

目前仅支持RabbitMQ、Kafka。

官网

https://spring.io/projects/spring-cloud-stream#overview

Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已经建立和熟悉的Spring熟语和最佳实践上,包括支持持久化的发布/订阅、消费组以及消息分区这三个核心概念

Spring Cloud Stream Reference Documentation

Spring Cloud Stream中文指导手册:Spring Cloud Stream中文指导手册

2、设计思想

1.标准MQ

生产者/消费者之间靠消息媒介传递信息内容

消息必须走特定的通道:消息通道MessageChannel

消息通道里的消息如何被消费呢,谁负责收发处理:消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅

2.为什么用Cloud Stream

比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。

stream凭什么可以统一底层差异?

    在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离

    通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。

通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程

通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。

Stream中的消息通信方式遵循了发布-订阅模式

Topic主题进行广播

在RabbitMQ就是Exchange

在Kakfa中就是Topic

3、Spring Cloud Stream标准流程套路

Binder:很方便的连接中间件,屏蔽差异

Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置。

Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。

4、编码API和常用注解

二、案例演示


1、消息驱动之生产者 

1.新建Module

cloud-stream-rabbitmq-provider8801

2.POM

<?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>mscloud</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-stream-rabbitmq-provider8801</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
        <!--基础配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

3.YML

server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息;
          defaultRabbit: # 表示定义的名称,用于于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: localhost
                  port: 5672
                  username: guest
                  password: guest
        bindings: # 服务的整合处理
          output: # 这个名字是一个通道的名称
            destination: studyExchange # 表示要使用的Exchange名称定义
            content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
            binder: defaultRabbit # 设置要绑定的消息服务的具体设置

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

4.主启动类StreamMQMain8801

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;

@SpringBootApplication
public class StreamMQMain8801

    public static void main(String[] args)
    
        SpringApplication.run(StreamMQMain8801.class,args);
    

5.业务类

发送消息接口

package com.atguigu.springcloud.service;

public interface IMessageProvider

    public String send() ;

发送消息接口实现类

package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
import javax.annotation.Resource;
import org.springframework.cloud.stream.messaging.Source;

import java.util.UUID;

@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class MessageProviderImpl implements IMessageProvider

    @Resource
    private MessageChannel output; // 消息的发送管道

    @Override
    public String send()
    
        String serial = UUID.randomUUID().toString();
        this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
        System.out.println("***serial: "+serial);

        return serial;
    

Controller

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
public class SendMessageController

    @Resource
    private IMessageProvider messageProvider;

    @GetMapping(value = "/sendMessage")
    public String sendMessage()
    
        return messageProvider.send();
    

 

2、消息驱动之消费者

1.新建Module

cloud-stream-rabbitmq-consumer8802

2.POM

<?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>mscloud</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-stream-rabbitmq-provider8801</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-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--基础配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

3.YML

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置
          group: atguiguA  #同一个组可以避免重复消费
eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8802.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

4.主启动类StreamMQMain8802

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StreamMQMain8802

    public static void main(String[] args)
    
        SpringApplication.run(StreamMQMain8802.class,args);
    

5.业务类

package com.atguigu.springcloud.service;

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;

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListener

    @Value("$server.port")
    private String serverPort;

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message)
    
        System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\\t port: "+serverPort);
    

注意:如果连接liunx会报错连接拒绝的,在老师这个yml基础上在在spring下面配置一下rabbit的ip账号密码,也就是在两个地方配置了rabbitmq 

6.测试8801发送8802接收消息

http://localhost:8801/sendMessage

三、分组消费与持久化


依照8802,clone出来一份运行8803

1.启动:

 

2.运行后有两个问题:有重复消费问题、消息持久化问题

3.重复消费

目前是8802/8803同时都收到了,存在重复消费问题

http://localhost:8801/sendMessage

4.如何解决

分组和持久化属性group(重要)

生产实际案例

比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,

如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。

这时我们就可以使用Stream中的消息分组来解决

 注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。

不同组是可以全面消费的(重复消费),同一组内会发生竞争关系,只有其中一个可以消费。

分组 

1.原理

微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。

不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

2.8802/8803都变成不同组group两个不同

8802修改YML

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置
          group: atguiguA  #同一个组可以避免重复消费
eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8802.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

8803修改YML

server:
  port: 8803

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置
          group: atguiguB #定义组名,同一个组可以避免重复消费
eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8803.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

多数情况,生产者发送消息给某个具体微服务时只希望被消费一次,按照上面我们启动两个应用的例子,虽然它们同属一个应用,但是这个消息出现了被重复消费两次的情况。为了解决这个问题,在Spring Cloud Stream中提供了消费组的概念。

 结论:还是重复消费(要同一个exchange中同一组才可以避免重复消费)

3.8802/8803都变成相同组group两个相同

    8802/8803实现了轮询分组,每次只有一个消费者8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。 

都把组名改为 group: atguiguA

结论:同一个组的多个微服务实例,每次只会有一个拿到

持久化

通过上述,解决了重复消费问题,再看看持久化

  1. 停止8802/8803并去除掉8802的分组group: atguiguA
  2. 8803的分组group: atguiguA没有去掉
  3. 启动8801先发送4条消息到rabbitmq
  4. 先启动8802,无分组属性配置,后台没有打出来消息
  5. 再启动8803,有分组属性配置,后台打出来了MQ上的消息

原理是,exchange数据发送到队列中,由于02重启没有设置分组,会重新创建队列并监听,而03还是监听原来队列。

结束!

springcloud-消息驱动Stream01

消息驱动概述

  • 是什么
  1. 简单的说, 消息驱动屏蔽底层消息中间件的差异, 降低切换成本, 统一消息的编程模型.
  2. 官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架.
    • 应用程序inputs或outputs来与Spring Cloud Stream中binder对象交互, 通过我们配置来binding(绑定), 而Stream的binder对象负责与消息中间件交互. 所以我们只需要搞清楚如何与Stream交互就可以方便使用消息驱动的方式.
    • 通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动.
    • Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现, 引用了发布-订阅, 消费组, 分区的三个核心概念, 但目前仅支持RabbitMQ和Kafka
  3. Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架, 该框架提供了一个灵活的编程模型.
  4. 中文指导手册: https://m.wang1314.com/doc/webapp/topic/20971999.html
  •  设计思想
  1. 标准的MQ
    • 生产者/消费者之间靠消息媒介传递信息内容 -> Message
    • 消息必须走特定的通道 -> MessageChannel
    • 消息通道里的消息如何被消费, 谁负责收发处理? -> 消息通道MessageChannel的子接口SubscribableChannel, 由MessageHandler消息处理器订阅.
  2. 为什么用Stream
    • 中间件的差异问题
      • 比如说RabbitMQ有交换机(exchange), 而Kafka有Topic和Partitions分区.
      • 中间件的差异会造成一定困扰, 我们如果用了两个消息队列的其中一种, 如果后面需要网另一个消息队列迁移, 一大堆东西需要推倒重做, 因为它跟我们的系统耦合了, 而Stream给我们提供了解耦的方式.
    • Stream如何统一底层差异
      • 通过定义绑定器(binder)作为中间层, 完美地实现了应用程序与消息中间件细节之间的隔离.
      • 通过向应用程序暴露统一的Channel通道, 使得应用程序不需要再考虑各种不同的消息中间件实现.
      • Stream对消息中间件的进一步封装, 可以做到代码层面对中间件的无感知, 甚至于动态的切换中间件, 使得微服务开发的高度解耦, 服务可以关注更多自己的业务流程.
    • Binder
      • INPUT对应消费者
      • OUTPUT对应生产者
  3. Stream中的消息通信方式遵循了发布-订阅模式
    • Topic主题进行广播
  • Stream的标准流程

    • Binder: 很方便的连接中间件, 屏蔽差异
    • Channel: 通道, 是队列Queue的一种抽象, 在消息通讯系统中就是实现存储与转发的媒介, 通过Channel对队列进行配置.
    • Source和Sink: 简单的可理解为参照对象是Stream自身, 从Stream发布消息就是输出, 接受消息就是输入.
  • 编码API和常用注解

消息驱动之生产者

  1. 新建Module: cloud-stream-rabbitmq-provider8801, 作为生产者进行发消息模块.
  2. pom中新加
    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
  3. yml
    server:
      port: 8801
    
    spring:
      application:
        name: cloud-stream-provider
      cloud:
        stream:
          binders: # 在此处配置要绑定的rabbitmq的服务信息;
            defaultRabbit: # 表示定义的名称,用于于binding整合
              type: rabbit # 消息组件类型
              environment: # 设置rabbitmq的相关的环境配置
                spring:
                  rabbitmq:
                    host: localhost
                    port: 5672
                    username: guest
                    password: guest
          bindings: # 服务的整合处理
            output: # 这个名字是一个通道的名称
              destination: studyExchange # 表示要使用的Exchange名称定义
              content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
              binder: defaultRabbit  # 设置要绑定的消息服务的具体设置
    
    eureka:
      client: # 客户端进行Eureka注册的配置
        service-url:
          defaultZone: http://localhost:7001/eureka
      instance:
        lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
        lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
        instance-id: send-8801.com  # 在信息列表时显示主机名称
        prefer-ip-address: true     # 访问的路径变为IP地址
  4. 主启动类
    @SpringBootApplication
    public class StreamMQMain8801 {
        public static void main(String[] args) {
            SpringApplication.run(StreamMQMain8801.class, args);
        }
    }
  5. 业务类
    • 发送消息接口
      public interface IMessageProvider {
      
          String send();
      }
    • 发送消息接口实现类
      @EnableBinding(Source.class) //定义消息的推送管道.
      public class IMessageProviderImpl implements IMessageProvider {
      
          @Resource
          private MessageChannel output; //消息发送管道
      
          @Override
          public String send() {
              String serial = UUID.randomUUID().toString();
              output.send(MessageBuilder.withPayload(serial).build());
              System.out.println("***********serial: " + serial);
              return null;
          }
      }
    • controller
      @RestController
      public class SendMessageController {
      
          @Resource
          private IMessageProvider messageProvider;
      
          @GetMapping("/sendMessage")
          public String sendMessage() {
              return messageProvider.send();
          }
      }
  6. 测试
    • 启动7001, 7002, rabbitmq
    • 启动8001后: http://localhost:8801/sendMessage

消息驱动之消费者

  1. 新建Module: cloud-stream-rabbitmq-provider8801
  2. 新加pom
    <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
  3. yml
    server:
      port: 8802
    
    spring:
      application:
        name: cloud-stream-consumer
      cloud:
        stream:
          binders: # 在此处配置要绑定的rabbitmq的服务信息;
            defaultRabbit: # 表示定义的名称,用于于binding整合
              type: rabbit # 消息组件类型
              environment: # 设置rabbitmq的相关的环境配置
                spring:
                  rabbitmq:
                    host: localhost
                    port: 5672
                    username: guest
                    password: guest
          bindings: # 服务的整合处理
            input: # 这个名字是一个通道的名称
              destination: studyExchange # 表示要使用的Exchange名称定义
              content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
              binder: defaultRabbit  # 设置要绑定的消息服务的具体设置
    
    eureka:
      client: # 客户端进行Eureka注册的配置
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
      instance:
        lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
        lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
        instance-id: receive-8802.com  # 在信息列表时显示主机名称
        prefer-ip-address: true     # 访问的路径变为IP地址
  4. 主启动类
    @SpringBootApplication
    public class StreamMQMain8802 {
    
        public static void main(String[] args) {
            SpringApplication.run(StreamMQMain8802.class, args);
        }
    }
  5. 业务类
    @Component
    @EnableBinding(Sink.class)
    public class ReceiveMessageListenerController {
    
        @Value("${server.port}")
        private String serverPort;
    
        @StreamListener(Sink.INPUT)
        public void input(Message<String> message) {
            System.out.println("消费者1号, ----> 接收的消息: " + message.getPayload() + "\\t port" + serverPort);
        }
    }
  6. 测试: 8801发送消息, 8802接收消息
    • http://localhost:8801/sendMessage
    • 在8802的控制台会打出接收的消息.

分组消费与持久化

  1. 根据8802, clone出一份8803: cloud-stream-rabbitmq-consumer8803
  2. 启动
    • RabbitMQ
    • 7001, 7002, 8801, 8802, 8803
  3. 运行后的两个问题
    • 有重复消费问题
    • 消费持久化问题
  • 消费
  1. 发送: http://localhost:8801/sendMessage
    • 8802, 8803两个微服务都收到了消息, 存在重复消费问题.
  2. 要用分组和持久化属性group解决该问题
  3. 实际案例
    • 如下场景, 订单系统我们做集群部署, 都会从RabbitMQ中获取订单信息, 如果一个订单同时被两个服务取到, 会造成数据错误, 我们要避免这种情况.
    • 这时我们可以使用Stream中的消息分组来解决.
    • 注意: Stream中处于同一个group中的多个消费者是竞争关系, 能够保证消息只会被其中一个应用消费一次, 而不同的组是可以重复消费的.
  • 分组
  1. 出现重复消费的原因
    • 默认分组group是不同的, 组流水号不一样, 被认为是不同组
  2. 我们可以自定义配置分到同一个组, 来解决重复消费问题.
  3. 自定义分组操作: 
    • 在yml中添加: spring.cloud.stream.bindings.input.group
    • 所以只需让8802, 8803设置到同一个组, 即可解决重复消费.
      • 采取轮询的方式获取消息.
  • 持久化
  1. 上述解决了重复消费, 再看看持久化..
  2. 停止8802和8803微服务, 并去除掉8802的分组group: bbbbb
  3. 8801先发送4条消息给rabbitmq.
  4. 先启动8802, 无分组属性, 后台无任何消息打出来.
  5. 再启动8803, 有分组属性配置, 后台打出来了MQ上的消息.
    • 配置了分组属性, 会把曾经未获得的消息重新获得并消费, 这就是消息的持久化.

以上是关于SpringCloud Stream消息驱动的主要内容,如果未能解决你的问题,请参考以下文章

springcloud-消息驱动Stream01

SpringCloud-2.0-周阳(15. 消息驱动 - SpringCloud Stream)

SpringCloud 消息驱动(Stream) 和 分布式链路跟踪(Sleuth)

SpringCloud 消息驱动(Stream) 和 分布式链路跟踪(Sleuth)

SpringCloud 消息驱动(Stream) 和 分布式链路跟踪(Sleuth)

(十六)SpringCloud Stream消息驱动