Spring Cloud(13)——Spring Cloud Stream 消息驱动

Spring Cloud(13)——Spring Cloud Stream 消息驱动

1、Stream概述

Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。它可以基于 Spring Boot 来创建独立的、可用于生产的 Spring 应用程序。Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,并引入了发布-订阅、消费组、分区这三个核心概念。

通过使用 Spring Cloud Stream,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。但是目前 Spring Cloud Stream 只支持 RabbitMQ 和 Kafka 的自动化配置。

消息的中间件有(RabbitMQ, Kafka, ActiveMQ,RocketMQ).本文以RabbitMQ作为中间件。

Spring Cloud Stream 官网:https://spring.io/projects/spring-cloud-stream

Spring Cloud Stream 中文文档:https://m.wang1314.com/doc/webapp/topic/20971999.html

Stream 的设计思想

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

Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至与动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程。

在这里插入图片描述

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

Spring Cloud Stream 标准流程套路

在这里插入图片描述

Binder:很方便的连接中间件

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

Source 和 Sink:参照Stream,发布消息就是输出,接收消息就是输入

2、Stream 编码API和常用注解

在这里插入图片描述

3、消息驱动之生产者

1、创建cloud-stream-rabbitmq-provider8801模块

2、导入pom依赖

<dependencies>


    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <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-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.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

3、编写yml配置文件

server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  cloud:
    stream:
      binders: #配置要绑定的rabbitmq的服务信息
        defaultRabbit:
          type: rabbit  #消息组件类型
      bindings:
        output:
          destination: studyExchange
          content-type: application/json  #设置消息类型,本次为json,文本类型则设置text/plain
          binder: {defaultRabbit}

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2  #设置心跳时间间隔
    lease-expiration-duration-in-seconds: 5  # 最大间隔时间
    instance-id: send-8801.com #自定义主机名称
    prefer-ip-address: true  # 显示IP

4、创建主启动类

@SpringBootApplication
@EnableEurekaClient
public class StreamMQProvider8801 {
    public static void main(String[] args) {
        SpringApplication.run(StreamMQProvider8801.class,args);
    }
}

5、编写业务

  1. 编写业务类接口

    public interface IMessageProvider {
        
        public String send();
    }
    

2.接口实现类 ,定义消息的推送管道

package com.cheng.springcloud.service.impl;

import com.cheng.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Service;

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

@EnableBinding(Source.class)  //定义消息的推送管道
public class IMessageProviderImpl implements IMessageProvider {

    @Resource
    private MessageChannel output;  //消息发送通道,队列Queue的一种抽象

    @Override
    public String send() {
        String s = UUID.randomUUID().toString();
        output.send(MessageBuilder
                .withPayload(s)
                .build());
        System.out.println(s);

        return null;
    }
}
  1. 编写controller
@RestController
public class IMessageController {

    @Resource
    private IMessageProvider iMessageProvider;

    @GetMapping(value = "/sendMessage")
    public String sendMessage(){
        return iMessageProvider.send();
    }
}

6、测试

  1. 启动cloud-eureka-server7001模块

  2. 启动RabbitMQ 访问请求;http://localhost:15672

  3. 启动cloud-stream-rabbitmq-provider8801

  4. 访问请求:http://localhost:8801/sendMessage,查看后台输出

在这里插入图片描述

  1. 查看rabbitmq上面的流量波峰的变动:

在这里插入图片描述

消息驱动之生产者搭建成功!

4、消息驱动之消费者

1、创建cloud-stream-rabbitmq-consumer8802模块

2、导入pom依赖

<dependencies>

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

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <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.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

3、编写yml配置文件

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  cloud:
    stream:
      binders: #配置要绑定的rabbitmq的服务信息
        defaultRabbit:
          type: rabbit  #消息组件类型
      bindings:
        input:  #接收消息通道
          destination: studyExchange   #要使用交换机的名称
          content-type: application/json  #设置消息类型,本次为json,文本类型则设置text/plain
          binder: {defaultRabbit}

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2  #设置心跳时间间隔
    lease-expiration-duration-in-seconds: 5  # 最大间隔时间
    instance-id: receive-8802.com #自定义主机名称
    prefer-ip-address: true  # 显示IP

4、创建主启动类

@SpringBootApplication
@EnableEurekaClient
public class StreamMQConsumer8802 {
    public static void main(String[] args) {
        SpringApplication.run(StreamMQConsumer8802.class,args);
    }
}

5、编写业务类

package com.cheng.springcloud.controller;

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 IMessageConsumerController {

    @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、测试

启动cloud-stream-rabbitmq-consumer8802模块

访问http://localhost:7001/,可以看到8801和8802两个模块都注册进注册中心了

在这里插入图片描述

现在用8801消息生产者发送消息,测试8802消息消费者是否能接受到:

执行请求:http://localhost:8801/sendMessage

8801消息生产者成功发送消息:

在这里插入图片描述

8802消息消费者成功接受到8801发送的消息:

在这里插入图片描述

5、分组消费与持久化

依据cloud-stream-rabbitmq-consumer8802模块,clone一个cloud-stream-rabbitmq-consumer8803模块

启动运行:存在两个问题

  1. 消息消费者8802和8803同时接受到消息,存在重复消费问题
  2. 消息持久化问题

如何解决:分组(group)

在Stream中,出于同一个group中的多个消费者是竞争关系,不同组是可以重复消费的,同一组会发生竞争关系,只有一个可以消费。

自定义配置分组

将消费者8802和8803模块变成不同组

在消费者8802模块的yml中配置group属性:

input:  #接收消息通道
  destination: studyExchange   #要使用交换机的名称
  content-type: application/json  #设置消息类型,本次为json,文本类型则设置text/plain
  binder: {defaultRabbit}
  group: wanliA

同样,也在消费者8803模块的yml中配置group属性:

input:  #接收消息通道
  destination: studyExchange   #要使用交换机的名称
  content-type: application/json  #设置消息类型,本次为json,文本类型则设置text/plain
  binder: {defaultRabbit}
  group: wanliB

查看RabbitMQ中的Exchanges配置,

在这里插入图片描述

自定义分组成功!

有上面可知,给group分配不同的值,即可分为不同消费组。那要想把多个微服务实例分为同一组,只需给group分配相同的值。

把消费者8802和8803模块yml配置文件中group的值都设为 wanli

再启动运行:

执行请求:http://localhost:8801/sendMessage 两次,即发送两条消息

发送成功,

在这里插入图片描述

查看消息消费者8802和8803模块接收的情况:

消费者8802

在这里插入图片描述

消费者8803

在这里插入图片描述

实现了轮流接收消息,避免了重复消费。

持久化

消息的持久化:如果给消息消费者配置相应的分组,那么即使当消息消费者宕机时,消息提供者发送消息,在消息消费者启动应用成功之后也能接收到消息提供者发来的消息。

测试:

  1. 关闭消费者8802和8803模块

  2. 去掉消费者8802里的group消费组属性,保留8003模块的group消费组属性

  3. 执行请求:http://localhost:8801/sendMessage 4次,给RabbitMQ发送四条消息

    发送成功:

在这里插入图片描述

  1. 启动消费者8802和8803模块,查看两个服务的接受情况:

    消费者8802:

    没有接收到任何消息

    消费者8803:全部接收

在这里插入图片描述

如果给消息消费者配置相应的分组,那么即使当消息消费者宕机时,消息提供者发送消息,在消息消费者启动应用成功之后也能接收到消息提供者发来的消息。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万里顾—程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值