Spring Cloud Stream 整合MQ 完整版

1、Spring Cloud Stream 概述

官方表述是:Spring Cloud Stream is a framework for building highly scalable event-driven microservices connected with shared messaging systems.意思就是Spring Cloud Stream是一个框架,用于构建与共享消息传递系统相连的高度可伸缩的事件驱动微服务。以下是官方提供的一个应用模型,很明显的看出,应用程序和中间件之间是通过binder以及一系列的input/output进行数据的交互。(特别说明:Spring Cloud Stream (后续简称SCS)目前按照特性写法区分可以分为两大类一个是3.1.x之前的版本,一个是之后的版本,可以说是区别比较大的,本文讲述的是3.2.4的版本)该框架的引入,可以让开发者不再纠结于变更mq框架而需要频繁的改变业务代码以及相关配置,只需要改动相关框架的配置文件,就可以实现不同消息中间件之间自由切换,甚至组合使用。

Spring Cloud Stream 整合MQ 完整版_第1张图片

 2、关键

2.1、Binder

        目前的话呢,Spring Cloud Stream为Kafka和Rabbit MQ提供了Binder实现。当然目前Spring Cloud Alibaba 内部呢也实现了RocketMq的Binder。本文将会讲述RabbitMQ的使用。

        Spring Cloud Stream 提供了一种抽象的Binder用来物理连接外部的中间件。生产者可以是任意向通道发送信息的组件,通道可以使用Binder被绑定到外部的消息中间件。消费者也可以通过任意组件从通道中获取消息。同理,消费者的通道也可以被绑定到外部消息中间件。

2.2、Bindings

        Binding是连接应用程序跟消息中间件的桥梁,用于配置生产者和消费者。基本上一系列的配置都是在这里。

如果有需要可以前往官网看具体内容(中文版,该版本属于老版本内容)

接下来,上代码,一个实战demo

3、代码实现

3.1、POM依赖


    org.springframework.boot
    spring-boot-starter-parent
    2.7.9
    


    org.springframework.cloud
    spring-cloud-stream-binder-rabbit


    org.springframework.cloud
    spring-cloud-dependencies
    2021.0.3
    pom
    import

        在引入依赖的时候需要注意,SCS的包需要和Spring Boot和Spring Cloud做版本对应,否则启动会出现异常问题,具体的版本对应可以参考官网,小编在此贴个图,方便各位。

Spring Cloud Stream 整合MQ 完整版_第2张图片

 3.2、yml配置

        在本文中我们采用的是RabbitMq这一消息中间件,如果小伙伴们用的是其他的,直接替换即可。

server:
  port: 9009
spring:
  # rabbitmq:
  #   host: 127.0.0.1
  #   port: 5672
  #   username: bosen
  #   password: bosen@888
  #   virtual-host: /
  cloud:
    function:
      # 定义消费者
      definition: ackMessage;normal;delayMessage
    stream:
      binders: # 定义rabbit还是kafka,可以写多个
        bosen-rabbit:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: bosen
                password: bosen@888
                virtual-host: /
      bindings:
        delayMessage-in-0:
          destination: delayMessage.exchange.cloud  # mq对应交换机
          content-type: application/json
          consumer:
            acknowledge-mode: auto # manual手动确认 ,auto 自动确认
            concurrency: 10
            # autoStartup: true
          group: delay-group    # 消息组
          binder: bosen-rabbit
        delayMessage-out-0:
          destination: delayMessage.exchange.cloud
          content-type: application/json
          group: delay-group
          binder: bosen-rabbit
        ackMessage-in-0:
          destination: ackMessage.exchange.cloud
          content-type: application/json
          consumer:
            acknowledge-mode: manual # manual手动确认 ,auto 自动确认
            concurrency: 10 # 并发数
          group: ackMessage-group
          binder: bosen-rabbit
        ackMessage-out-0:
          destination: ackMessage.exchange.cloud
          content-type: application/json
          group: ackMessage-group
          binder: bosen-rabbit
        normal-in-0:
          destination: normal.exchange.cloud
          content-type: application/json
          consumer:
            acknowledge-mode: auto # manual手动确认 ,auto 自动确认
            concurrency: 10 # 并发数
          group: normal-group
          binder: bosen-rabbit
        normal-out-0:
          destination: normal.exchange.cloud
          content-type: application/json
          group: normal-group
          binder: bosen-rabbit
      rabbit:
        bindings:
          ackMessage-in-0:
            consumer:
              startOffset: latest
              acknowledge-mode: manual  # manual手动确认 ,auto 自动确认
              concurrency: 10 # 并发数
            autoRebalanceEnabled: true
          delayMessage-in-0:
            consumer:
              startOffset: latest
              delayedExchange: true # 开启延时
              concurrency: 10 # 并发数
            autoRebalanceEnabled: true
              # exchange-type: direct
          delayMessage-out-0:
            producer:
              delayedExchange: true  # 开启延时
              # exchange-type: direct
  application:
    name: bosen-message
  main:
    allow-bean-definition-overriding: true
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

特别说明:

        在上述配置中,可以看到注释了spring.rabbitmq的相关配置,是因为我们使用spring.cloud.stream.binder.**.enviornment属性进行消息中间件的连接配置。可是在我们配置完成之后启动项目会发现,控制台抛出了一个异常,关键内容就是“Rabbit health check failed”以及“Connection refused: connect”,起初我和大部分小伙伴一样,以为是自己连接配置写错了,特地去检查了一遍,反复确认,发现没有任务问题,于是我试着去发消息调用接口,发现是可以正常发送的,消费也正常。于是可以确定不是配置问题。于是呼,开启了程序员的必经之路,看源码,进入到具体报错的类RabbitHealthIndicator下面,在搜索的时候会发现有两个,点击配置文件

Spring Cloud Stream 整合MQ 完整版_第3张图片

 会发现这两个类都是在spring boot actuator这个jar包下面的内容,这个框架是一个健康检测框架,再往下找,会发现在某个配置类下面,需要读取spring.rabbit的配置

Spring Cloud Stream 整合MQ 完整版_第4张图片

此刻我可能在想和你一样的问题,居然是这个问题,那我不要他是不是就可以,答案是必然的,这个框架并不是必要的,看项目需要,如果你不需要,只需要添加配置关闭即可


# 关闭actuator的检测功能,如果要开启,为了避免连接异常,需要指定spring.rabbitmq的相关配置
management:
  health:
    rabbit:
      enabled: false

 如果你还是想用这个框架做健康检测,只需要放开spring.rabbitmq的配置即可,也是可以正常启动。

3.3、生产者

        基本配置写完了,接下来就是关于生产者的写法了,3.1.x之前的版本是通过Source来进行数据的发送的,但是在新版本中,使用的是StreamBridge,在项目中只需要引入这个Bean即可使用

    @Resource
    private StreamBridge streamBridge;


    /**
     *
     * @param mqMessageVO 消息内容
     * @return 结果
     */
    @Override
    public ResponseData sendMsg(SendMQMessageVO mqMessageVO) {
        Message message = MessageBuilder.withPayload(mqMessageVO.getMsg()).build();
        streamBridge.send(mqMessageVO.getBindingName(), message);
        return ResponseData.success();
    }

    /**
     * 发送延迟消息
     * @param mqMessageVO 消息
     * @return 结果
     */
    @Override
    public ResponseData sendDelayMsg(SendMQMessageVO mqMessageVO) {
        Message message = MessageBuilder.withPayload(mqMessageVO.getMsg()).setHeader("x-delay", mqMessageVO.getDelaySeconds() * 1000).build();
        streamBridge.send(mqMessageVO.getBindingName(), message);
        return ResponseData.success();
    }

3.4、消费者

        消费者的bean其实在配置的时候就已经定义了,只要做具体的bean注入就行

    /**
     * mq接收ackMessage消息/手动ack确认
     **/
    @Bean
    Consumer> ackMessage() {
        log.info("ackMessage-初始化订阅");
        return obj -> {
            Channel channel = obj.getHeaders().get(AmqpHeaders.CHANNEL, Channel.class);
            Long deliveryTag = obj.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class);
            try {
                log.info("ackMessage-消息接收成功:" + obj.getPayload());
                //业务逻辑处理
                //ack确认
                channel.basicAck(deliveryTag, false);
            } catch (Exception e) {
                //重新回队列-true则重新入队列,否则丢弃或者进入死信队列。
//                    channel.basicReject(deliveryTag, true);
                log.error(e.getMessage());
            }

        };
    }

    /**
     * mq接收normal消息
     **/
    @Bean
    Consumer normal() {
        log.info("normal-初始化订阅");
        return obj -> {
            log.info("normal-消息接收成功:" + obj);
            //业务逻辑处理
        };
    }


    /**
     * mq接收延时消息
     * Messaging 发送实体消息接收实体消息
     **/
    @Bean
    Consumer> delayMessage() {
        log.info("delay-初始化订阅");
        return obj -> {
            Object payload = obj.getPayload();
            log.info("delay-消息接收成功:" + LocalDateTime.now() + "  " + payload);
            //业务逻辑处理
        };
    }

从上述代码可以看到新版本的SCS的一个特性,那就是函数式编程,这也是相比老版本的一个大的变革。

3.5、接口

        为了方便,小编自己封装了一个api接口用于通用调用

public interface IMQMessageService {
    ResponseData sendMsg(SendMQMessageVO mqMessageVO);

    ResponseData sendDelayMsg(SendMQMessageVO mqMessageVO);
}
    @Resource
    private IMQMessageService imqMessageService;

    /**
     * 发送普通消息Rabbitmq
     * bindingName 绑定队列名称
     * @param mqMessageVO 消息内容
     */
    @PostMapping("/sendMessage")
    public ResponseData sendMessage(@RequestBody @Valid SendMQMessageVO mqMessageVO) {
        return imqMessageService.sendMsg(mqMessageVO);
    }

    /**
     * 发送延迟消息
     *
     * @param mqMessageVO  消息实体
     * @return 结果
     */
    @PostMapping("/sendDelayedMessage")
    public ResponseData sendDelayedMessage(@RequestBody @Valid SendMQMessageVO mqMessageVO) {
        return imqMessageService.sendDelayMsg(mqMessageVO);// 延迟时间(秒)
    }
@Data
public class SendMQMessageVO implements Serializable {
    private static final long serialVersionUID = 945882305703451537L;

    /**
     * exchange名称,SCS框架中的
     */
    @NotBlank(message = "exchange名称不能为空")
    private String bindingName;

    /**
     * 消息类型
     */
    @NotNull(message = "消息类型不能为空")
    private Integer msgType;

    /**
     * 消息
     */
    @NotBlank(message = "消息内容不能为空")
    private String msg;

    /**
     * 延迟时长
     */
    private Integer delaySeconds;
}

3.6、测试

        在这里我就直接贴curl,有兴趣的小伙伴可以拿去测试

curl --location --request GET 'localhost:9009/mq/sendDelayedMessage?message=hello&bindingName=delayMessage-out-0'

Spring Cloud Stream 整合MQ 完整版_第5张图片

 

你可能感兴趣的:(java,spring,cloud)