2020SpringCloud学习年终总结——第十二章-stream

Stream前置

  • 为了解决一个完整系统中有多个消息中间件(MQ),它们的切换、维护、开发会很麻烦。希望只需要用一种适配绑定的方式,自动给我们在各种MQ中切换。Stream就是可以屏蔽底层的这些插件,能够操作各种MQ,降低开发难度。
  • 屏蔽底层消息中间件的差异,降低切换成本,同一消息的编程模型。
  • Spring Cloud Stream 是一个构建消息驱动微服务的框架。应用程序通过inputs或者outputs,来与Spring Cloud Stream中的binder对象交互。通过我们配置来binding(绑定),而Spring Cloud Stream的binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。
  • 通过使用Spring Integration来连接消息代理中间件,以实现消息事件驱动。Spring Cloud Stream为一些供应商的消息中间件提供了个性化的自动配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
  • 目前只支持RabbitMQ,Kafka;

版本说明

  • cloud Hoxton.SR1
  • boot 2.2.2RELEASE
  • cloud alibaba 2.1.0 RELEASE
  • java java8
  • Maven 3.5以上
  • Mysql 5.7以上

Stream

消息驱动概述

  • 标准MQ

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

    • 消息必须走特定的通道,消息通道MessageChannel;

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

  • Stream设计思想

    • 通过定义绑定器,作为中间层,完美实现应用程序与消息中间件细节之间的隔离;通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。

      • 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件之间的隔离。
      • Binder,INPUT对应于消费者,OUTPUT对应于生产者。
    • Stream中的消息通信方式遵循了发布-订阅模式。

      • 通过Topic主题进行广播,在RabbitMQ就是Exchange,在Kafka中就是Topic;

在这里插入图片描述

  • Spring Cloud Stream标准流程

在这里插入图片描述

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

在这里插入图片描述

案例说明

  • RabbitMQ环境搭建完成;
  • 工程中新建三个子模块:
    • 子模块:
      • cloud-stream-rabbitmq-provider8801,作为生产者,进行发消息模块;
      • cloud-stream-rabbitmq-consumer8802,消息接收模块
      • cloud-stream-rabbitmq-consumer8803,消息接收模块

消息驱动之生产者

  • cloud-stream-rabbitmq-provider8801,作为生产者,进行发消息模块;

    • 创建、POM、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 #设置心跳的时间间隔(默认是30S)
            lease-expiration-duration-in-seconds: 5 #如果超过5S间隔就注销节点 默认是90s
            instance-id: send-8801.com #在信息列表时显示主机名称
            prefer-ip-address: true #访问的路径变为IP地址
        
        
    • 业务类:

      • 发送消息接口;

      • 发送消息接口实现类;

        • //这里是跟rabbitMQ打交道的,不是被Controller所调用,因此不加@Service
          
      • service

        //这里是跟rabbitMQ打交道的,不是被Controller所调用,因此不加@Service
        @EnableBinding(Source.class)//定义消息的推送管道
        public class MessageProviderImpl 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(value = "/sendMessage")
            public String sendMessage(){
                return messageProvider.send();
            }
        }
        
      • 测试:启动7001、7002、启动rabbitMQ、启动8801、访问http://localhost:8801/sendMessage

消息驱动之消费者

  • 新建Module,cloud-stream-rabbitmq-consumer8802;

  • POM,YML,主启动类,业务类,测试8801发送8802接收消息;

    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:
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
      instance:
        lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30S)
        lease-expiration-duration-in-seconds: 5 #如果超过5S间隔就注销节点 默认是90s
        instance-id: receive-8802.com #在信息列表时显示主机名称
        prefer-ip-address: true #访问的路径变为IP地址
    
    • controller业务类

      @Component
      @EnableBinding(Sink.class)//将信道channel和exchange绑定在一起
      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);
          }
      }
      
    • 测试:8801发送,8802接收消息;http://localhost:8801/sendMessage

分组消费的问题

  • 依照8802,clone出一份8803,cloud-stream-rabbitmq-consumer8803

  • 启动:

    • RabbitMQ;
    • eureka7001,7002;
    • 8801,消费生产;
    • 8802,消费消费
    • 8803,消费消费
  • 运行后的问题:

    • 有重复消费的问题;

      • 比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息。那么如果一个订单同时被两个服务获取到,就会造成数据错误,我们得避免这种情况。这时我们就可以使用Stream中得消息分组来解决。注意:在Stream中处于同一个group中得多个消费者是竞争关系,就能保证消息只会被其中一个应用消费一次。但不同组是可以全面消费(重复消费)的。

在这里插入图片描述

在这里插入图片描述

说明这是两个组别,因为组流水号不一致;
  • 微服务应用要放置在同一个group中,就能够保证消息只会被其中一个应用消费一次。不同组是可以消费的,同一个组内会发送竞争关系,只有一个可以消费。

  • 自定义分组,先分成两个组:

    • group: atguiguA, atguiguB

    • 修改8802和8803的YML,加一个group就行了:

      • 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
        
  • 自定义分组,分成一个组:

    • 为了让8802和8803实现轮询分组,每次只有一个消费者,8801模块发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。
    • 将8802和8803都变成相同的组,group两个组就相同了。group全改为atguiguA即可。

消息持久化的问题

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

需熟练掌握

知识点

tips

  • 目前常用的消息中间件(MQ),有:ActiveMQ、RabbitMQ、RocketMQ、Kafka;
  • RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,或者简单的将作业排队以便让分布式服务器进行处理。
  • 启动rabbitMQ
    • 进入RabbitMQ安装目录下的sbin目录;
    • 输入以下命令,启动管理功能;
      • .\rabbitmq-plugins enable rabbitmq_management
      • 可视化插件;
    • 访问地址查看是否安装成功:http://localhost:15672
    • 输入账号密码并登录,默认是guest guest

代码地址:https://github.com/AJ-Spade/cloud2020/tree/master

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值