SpringCloud系列(六)消息总线和消息驱动

547 阅读7分钟

这是我参与更文挑战的第 16 天,活动详情查看: 更文挑战

Spring Cloud Bus

一、概述

消息总线是分布式自动刷新配置功能,Spring Cloud Bus 配合 Spring Cloud config 实现配置的动态刷新。 Spring Cloud Bus整合了Java事件处理机制和消息中间件的功能,Bus支持两张消息中间件,RabbitMQ和Kafka。 Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改,事件推送等。也可以当做微服务间的通信通道。

二、环境搭建

给配置中心的服务端发送通知,由服务端传播给客户端。使用RabbitMQ做消息中间件。

在服务端3344添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

服务端3344添加RabbitMQ的配置

...
spring:
  application:
    name: cloud-config-center
  # MQ 配置
  rabbitmq:
    host: 47.95.226.96
    port: 5672
    username: guest
    password: guest
...
# MQ相关配置,暴露bus刷新端点
management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'

客户端3355/3366添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

客户端3355/3366添加MQ配置

spring:
  application:
    name: config-client
  # MQ 配置
  rabbitmq:
    host: 47.95.226.96
    port: 5672
    username: guest
    password: guest

测试:做完以上的配置后,再次修改配置文件的内容,并且向分布式配置中心的服务端发送一次post请求http://localhost:3344/actuator/bus-refresh,即可广播到下面的所有客户端. 原理:ConfigClient实例都监听MQ中同一个topic,当一个服务刷新数据的时候,它会把这个信息放入到topic中,这样监听同一topic的服务就能得到通知,更新自身的配置。

动态定点刷新

每次修改配置文件后刷新指定的服务应用。比如:修改GitHub上的配置文件,只要3355可见。 向 http://localhost:3344/actuator/bus-refresh/定点刷新的服务名称:端口号 发送post请求。 如: http://localhost:3344/actuator/bus-refresh/config-client:3355

消息驱动-Spring Cloud Stream

一、概述

1、为什么引入Stream

现在消息中间件种类繁多,学习起来负担重,并且不同平台使用不同的消息中间件,同一个系统可能存在多个MQ,在实际使用中困难重重。 现在只需要一种适配绑定的方式,自动在不同的MQ之间切换。 屏蔽底层消息中间件的差异,降低切换成本,同意消息编程模型。

2、什么是Spring Cloud Stream

Spring Cloud Stream 是一个构建消息驱动微服务的框架。 应用程序通过inputs(消费者)或者outputs(发送者)来与Stream中的binder对象交互。通过配置binder(绑定),而Stream的binder对象负责与消息中间件交互。所以只需要知道如何与Stream交互就可以方便的使用消息驱动。 通过Spring Integration来连接消息代理中间件以实现消息事件驱动,引用了发布-订阅、消费组、分区三个核心概念。

3、设计思想

没有使用Spring Cloud Stream之前的消息中间件

生产者和消费者之间用消息媒介传递消息内容。消息必须走特定通道。由MessageChannel的子接口SubscribableChannel发送消息,由MessageHandler消息处理器处理所订阅的消费者获取消息。 如果一个系统即使用RabbitMQ还使用了Kafka,由于两个消息中间件的架构上的不同,所以无法直接通信。

使用Spring Cloud Stream后

Spring Cloud Stream 屏蔽底层差异的原因:通过实现定义绑定器(banner)作为中间层,完美的实现了应用程序与消费中间件的隔离。通过向应用程序暴露统一的Channel,使得应用程序不再考虑各种不同的消息中间件实现。 Banner分为inputs(消费者)或者outputs(生产者)

image-20201213010510787

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

Spring Cloud Stream 标准流程和常用注解
  • Banner:绑定器,连接的中间件,屏蔽差异。
  • Channel:通道,类似与队列,在消息通讯系统中实现储存和转发的媒介,通过Channel对队列配置。
  • Source和Sink:输出和输入。
  • @Input:标识输入通道,通过该输入通道接受到的消息进入应用程序。
  • @Output:标识输出通道,发布的消息通过该通道离开应用程序。
  • @StreamListener:监听队列,用于消费组队列的消息接收。
  • @EnableBinding:Channel和exchange绑定在一起。

二、环境搭建

1、生产者

创建cloud-stream-rabbitmq-provider8801模块,导入依赖

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

配置文件

server:
  port: 8801
spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders:  # 配置需要绑定的rabbitmq的服务信息
        defaultRabbit:  # 表示定义名称,用于binding整合
          type: rabbit  # 消息组件类型
          environment:  # rabbitmq相关环境配置
            spring:
              rabbitmq:
                host: 47.95.226.96
                port: 5672
                username: guest
                password: guest
      bindings:         # 服务的整合处理
        output:         # 通道名称
          destination: studyExchange # 表示要使用的Exchange的名称
          content-type: application/json  # 设置消息类型为json
          binder: defaultRabbit   # 设置绑定的消息服务的具体设置,此处爆红不影响启动

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com: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地址

生产消息的Service

@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);
        return null;
    }
}

Controller

@RestController
public class SendMessageController {
    @Resource
    private IMessageProvider iMessageProvider;

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

访问http://localhost:8801/sendMessage即可向RabbitMQ中发送一条消息。

2、消费者

创建cloud-stream-rabbitmq-consumer8802模块,导入依赖

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

配置文件

server:
  port: 8802
spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders:  # 配置需要绑定的rabbitmq的服务信息
        defaultRabbit:  # 表示定义名称,用于binding整合
          type: rabbit  # 消息组件类型
          environment:  # rabbitmq相关环境配置
            spring:
              rabbitmq:
                host: 47.95.226.96
                port: 5672
                username: guest
                password: guest
      bindings:         # 服务的整合处理
        input:         # 通道名称
          destination: studyExchange # 表示要使用的Exchange的名称
          content-type: application/json  # 设置消息类型为json
          binder: defaultRabbit   # 设置绑定的消息服务的具体设置

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com: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地址

用来接收消息的Controller

@RestController
@EnableBinding(Sink.class)		//接收消息的管道
public class ReceiveMessageController {
    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT)	//监听队列
    public void input(Message<String> message) {
        System.out.println("consumer8802:" + message.getPayload() + "\t" + serverPort);
    }
}

三、Stream之重复消费

再创建一个消费组模块cloud-stream-rabbitmq-consumer8803,两个消费者去消费消息会出现两个问题,一个是重复消费问题,一个是消息持久化问题。

重复消费问题:现在向MQ发送一条消息,两个消费者都会消费该消息,存在重复消费问题,如果一个订单消息被两个消费者消费,就会造成数据错误。这时就可以使用Stream中的消费分组来解决,在Stream中处于同一个组的消费者是竞争关系,能够保证消息只会被一个消费者消费。

自定义分组,在8802和8803的配置文件加一个group

bindings:         # 服务的整合处理
  input:         # 通道名称
    destination: studyExchange # 表示要使用的Exchange的名称
    content-type: application/json  # 设置消息类型为json
    binder: defaultRabbit   # 设置绑定的消息服务的具体设置
    group: yylmA

再次发送消息,就只会被一个消费者消费了。

四、Stream之消息持久化

如果生产者在发送消息的时候,消费者的服务器没有启动或者宕机,那么就会错过消息。同样使用上面的Stream分组解决该问题。