SpringCloudStream函数式编程Demo记录

前言

       本Blog根据自己学习SpringCloudStream过程中实践的案例撰写,使用的是新版的函数式编程模型定义binding,共有两个Demo。理解有限,如有不正确的地方,十分欢迎指正。
       Demo1:常规的实现Topic交换机实现根据不同的routingKey路由消息到两个routingKey不同的Queue中。
       Demo2:通过RabbitMQ中的TTL机制和死信队列实现延时队列功能。

一、依赖添加

<!--MQ产品使用的是RabbitMQ,Maven中添加如下依赖-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
    <version>3.2.5</version>
</dependency>

二、MQ服务器连接配置

spring:
  rabbitmq:
    username: guest
    password: guest
    host: localhost
    port: 5672
    virtual-host: /

三、Binder配置

spring:
  cloud:
	stream:
	  # 默认binder使用rabbit,可以配置多个binder,每个binder可以连接不同的MQ产品
	  default-binder: rabbit
	  binders:
	    # 配置一个binder,设置它的类型为"rabbit"(表示MQ产品是RabbitMQ,固定写法),如果使用kafka则type后面写:kafka
	    rabbit:
	      type: rabbit
	      # 环境配置,引用前面配置好的RabbitMQ相关信息,Ip、Port、userName、password等
	      environment:
	        spring:
	          rabbitmq:
	            host: ${spring.rabbitmq.host}
	            port: ${spring.rabbitmq.port}
	            username: ${spring.rabbitmq.username}
	            password: ${spring.rabbitmq.password}
	            virtual-host: ${spring.rabbitmq.virtual-host}

四、Binding配置(重点、难点)

4.1 Demo1

Demo1配置,一个交换机两个队列,实现Exchange根据routingKey路由到不同的队列中
spring:
  cloud:
    stream:
      bindings:
        # 不以函数式编程方式定义,正常我们命名规则为xxxx-out-0,这里由于该Bindings的destination需要被两个消费者订阅,因此采用这种定义方法,可能原来的方式定义也可,不过我这样就实现了,还需继续研究
        # 配置一个生产者binding,在RabbiMQ生成一个名称为delayOutExchange的Exchange
        delayOut:
          destination: delayOutExchange
        # 配置一个消费者Binding,订阅的Exchange是delayOutExchange;在RabbitMQ生成一个名称为delayOutExchange.30s-queue的Queue
        delay30-in-0:
          destination: delayOutExchange
          group: 30s-queue
        # 配置一个消费者Binding,订阅的Exchange是delayOutExchange;在RabbitMQ生成一个名称为delayOutExchange.60s-queue的Queue
        delay60-in-0:
          destination: delayOutExchange
          group: 60s-queue
      # 注意,这里的rabbit和上面的rabbit不是一个概念,上面定义了一个binder,名称是rabbit;这里的rabbit是针对RabbitMQ的类型为rabbit(上面配置的type字段)的所有binding配置
      rabbit:
        bindings: 
          # 配置delayOut这个binding
          delayOut:
            producer:
              # 交换机类型
              exchange-type: topic
              # routingKey存放在发送消息时的header中(说明:发送消息时可以在header中添加键值对,key为routingKey,值自己随便设置,这个值即为routingkey的值,用于后续交换机路由到不同的Queue中)
              routing-key-expression: "headers['routingKey']"
          # 配置delay30-in-0这个binding
          delay30-in-0:
            consumer:
              exchange-type: topic
              # 当前队列绑定的routingKey,交换机会把符合下面这个routingKey表达式的消息路由到队列delayOutExchange.30s-queue中
              binding-routing-key: "A.*"
          # 配置delay60-in-0这个binding
          delay60-in-0:
            consumer:
              exchange-type: topic
              # 当前队列绑定的routingKey,交换机会把符合下面这个routingKey表达式的消息路由到队列delayOutExchange.60s-queue中
              binding-routing-key: "*.D"
	# 使用函数式定义消费者,和Binding中的最前面一部分保持一致,非常重要              
    function:
      definition: delay60;delay30;
Demo1图解

在这里插入图片描述
说明:
① 定义了一个Binding,名字为delayOut,创建一个名称为delayOutExchange的Exchange,该交换机从生产者发送的消息的消息头即headers中以key"routingKey"获取值,作为消息的routingKey,用于决定消息路由到哪一个Queue

② 定义了两个Binding,名称分别为"delay30-in-0""delay60-in-0",连接的Exchange均为"delayOutExchange";分别创建的Queue为"delayOutExchange.30s-queue""delayOutExchange.60-queue"(就是bindings配置中的"destination+group")

③ Queue"delayOutExchange.30s-queue"中绑定的routingKey匹配表达式为:"A.*",Queue"delayOutExchange.60s-queue"中绑定的routingKey匹配表达式为:"*.D""A.*"表示只接收routingKey以A.开头的消息,"*.D"表示只接收routingKey以".D"结尾的消息

举例说明:
	如果消息header中存在键值对数据routingKey="A.B",则消息只会被exchange路由到队列"delayOutExchange.30s-queue"中,即只有消费者01能消费到消息;
	如果消息header中存在键值对数据routingKey="B.D",则消息只会被Exchange路由到队列"delayOutExchange.60s-queue"中,即只有消费者02能消费	到消息;
	如果消息header中存在键值对数据routingKey="A.D",则消息会被Exchange路由到两个队列中,即消费者01和消费者02都能消费到消息
Demo1代码

注意名称:方法名称即Bean名称必须和配置文件中的Binding名称的最前面一部分一致,即"xxx-in-0"中的"xxx"

一、在配置类中添加如下两个Bean,表示两个Binding的消费者

import java.util.function.Consumer;

 @Bean
 public Consumer<String> delay30() {
     return msg -> System.out.println("【delay30】接收到消息:" + msg);
 }

 @Bean
 public Consumer<String> delay60() {
     return msg -> System.out.println("【delay60】接收到消息:" + msg);
 }


二、生产者发送消息到exchange代码

 @GetMapping("/60s/{routingKey}")
  public String sendDelay1mMsg(@PathVariable("routingKey") String routingKey) {
      streamBridge.send(
              "delayOut",
              MessageBuilder.withPayload(routingKey).setHeader("routingKey", routingKey).build());
      return "发送成功";
  }
Demo1效果截图

说明:向上面定义的controller发送请求,并设置routingKey,查看控制台打印

在这里插入图片描述
以上是MQ中创建的Exchange和Queue截图!

在这里插入图片描述
以上是RoutingKey为A.B,匹配"delayOutExchange.30s-queue"Queue的routingKey表达式,因此该队列的消费者消费到了消息。

在这里插入图片描述
以上是RoutingKey为B.D,匹配"delayOutExchange.60s-queue"Queue的routingKey表达式,因此该队列的消费者消费到了消息。

在这里插入图片描述
以上是RoutingKey为A.D,匹配"delayOutExchange.60s-queue"Queue和"delayOutExchange.60s-queue"的routingKey表达式,因此两个队列的消费者均消费到了消息。

4.2 Demo2

Demo2新增配置

说明:Binder配置使用Demo1中的即可,不需要动,在Demo1的基础上。Bindings配置添加如下内容:

spring:
  cloud:
    stream:
      bindings:
        # 这个binding可以理解成我们正常的消息队列,它接收生产者消息
        delay-out-0:
          destination: delay-exchange
          group: delay-30s-queue
          producer:
          	# 这个配置非常重要,没有该配置死信队列无法消费数据!!!(查阅资料得知该值配置的是生产者的组,我没有搞懂具体的作用,目前也是一知半解的状态,但是测试了一下,似乎是只需要配置就行,值具体作用不清楚,但是可以实现我们需要的效果)
            required-groups: 30s-queue
        handleDeadLetter-in-0:
          # 必须与下方的dead-letter-exchange对应
          # 下方配置中的dead-letter-queue-name一致,即配置destination+group
          destination: dead-exchange
          group: 40s-queue
      rabbit:
        bindings:
          # 配置正常消息队列的属性
          delay-out-0:
            producer:
              # 在该队列中的消息的TTL,30秒
              ttl: 30000
              # 必须开启自动绑定死信队列功能
              auto-bind-dlq: true
              # 死信队列交换机
              dead-letter-exchange: dead-exchange
              # 死信队列(这里的值和上面配置的订阅该死信队列消费者binding的 destination+group 一致)
              dead-letter-queue-name: dead-exchange.40s-queue
          handleDeadLetter-in-0:
            consumer:
              # 死信交换机必须是direct类型
              exchange-type: direct
    function:
      # 增加后面俩个函数定义
      definition: delay60;delay30;handleDeadLetter;delay;
Demo2图解

在这里插入图片描述

Demo2代码
	// 新增Bean
    @Bean
    public Consumer<Message<String>> handleDeadLetter() {
        return msg -> {
            String payload = msg.getPayload();
            log.info("【handleDeadLetter】接收到消息:{}", payload);
        };
    }
	// 添加发送数据测试接口
    @GetMapping("/dead/30s/{msg}")
    public String sendToDead30sMsg(@PathVariable("msg") String msg) {
        streamBridge.send("delay-out-0", MessageBuilder.withPayload(msg + "-" + new Date()).build());
        return "发送消息成功,发送时间:" + new Date();
    }
Demo2效果截图:

注意:服务刚启动后发现,MQ中创建了名称为"dead-exchange"的Exchange和名称为"dead-exchange-40s-queue"的Queue;并没有创建我们定义的"delay-out-0"这个Binding所配置的"delay-exchange"Exchange和"delay-exchange.delay-30s-queue"Queue。
在这里插入图片描述
通过浏览器请求向"delay-out-0"Exchange中发送数据:
在这里插入图片描述

在这里插入图片描述
30s之后看控制台输出:
在这里插入图片描述
到此功能实现!
注意:这里为了模拟"死信",针对binding"delay-out-0"中定义的队列delay-exchange.30s-queue,并没有定义消费者,因此到了30s之后,消息在队列中过期,成为"死信"

最后,针对SpringcloudStream对MQ的抽象中,"Binding"的概念个人认为比较重要,还需加强理解。与常规使用RabbitMQ不同,常规使用时,我们更多时候能看到自己定义的Exchange和Queue的Bean对象,然后通过Binding来绑定二者,对于二者的绑定关系我们能更加清晰的理解,然而对于SpringcloudStream中,直接就定义Binding,缺少了Exchange和Queue的定义。相当于抓住的主题改变了,因此更加抽象。这一块还需更多的实践来加深理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值