1、13.SpringCloud config分布式配置中心

1、概述

1、分布式系统面临的配置问题

  • 微服务意味着要将单体应用中的业务拆分成一个个子服务, 每个服务的粒度相对较小,因此系统中会出现大量的服务。于每个服务都需要必要的配置信息才能运行,所以一套集中式的、 动态的配置管理设施是必不可少的。
  • SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自 己带着一个application.yml,.上百个配置文件的管理…/(ToT)/~~

    2、是什么

    image.png

  • SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。

  • 怎么玩

    • SpringCloud Config分为服务端和客户端两部分。
      • 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
      • 客户端则是通过指定的配置中心来管理应用资源,以吸与业务相关的配置内容,并在启动的时候从配置中心获取和枷载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容

        3、作用

  • 集中管理配置文件

  • 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
  • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  • 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
  • 将配置信息以REST接口的形式暴露

  • 前提是已经配置好了ssh秘钥免密登录等。不然会连接不上

    1、添加三个文件到main

  • config-dev.yaml

    1. config:
    2. info: "main branch,daijunyi/spring-learn-config/config-dev.yaml version=1"
  • config-prod.yaml

    config:
      info: "main branch,daijunyi/spring-learn-config/config-prod.yaml version=1"
    
  • config-test.yaml

    config:
      info: "main branch,daijunyi/spring-learn-config/config-test.yaml version=1"
    

    2、创建分支dev,并且修改三个文件

  • config-dev.yaml

    config:
      info: "dev branch,daijunyi/spring-learn-config/config-dev.yaml version=1"
    
  • config-prod.yaml

    config:
      info: "dev branch,daijunyi/spring-learn-config/config-prod.yaml version=1"
    
  • config-test.yaml

    config:
      info: "dev branch,daijunyi/spring-learn-config/config-test.yaml version=1"
    

    2、创建cloud-config-center-3344

  • 新建Module模块cloud-config-center-3344它既为Cloud的配置中心模块cloudConfig Center

    1、修改pom

    ```xml <?xml version=”1.0” encoding=”UTF-8”?> <project xmlns=”http://maven.apache.org/POM/4.0.0

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    

      <artifactId>cloud2020</artifactId>
      <groupId>com.daijunyi</groupId>
      <version>1.0-SNAPSHOT</version>
    

    4.0.0

    cloud-config-center-3344

    org.springframework.cloud spring-cloud-config-server org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.daijunyi cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test

<a name="TBxZY"></a>
#### 2、修改yaml
```yaml
server:
  port: 3344
spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: git@github.com:daijunyi/spring-learn-config.git
          search-paths:
            - spring-learn-config
      label: main
eureka:
  client:
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka,http://www.eureka7002.com:7002/eureka

3、主启动类

package com.daijunyi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

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

4、测试

image.png

4、/{application}-{profile}[/{label}]

  • http://config-3344.com:3344/config/dev/main
  • image.png

    {
      "name": "config",
      "profiles": ["dev"],
      "label": "main",
      "version": "5ef26fcb3bdde7a18a9da386ea63b5d5b3fda30c",
      "state": null,
      "propertySources": [{
          "name": "git@github.com:daijunyi/spring-learn-config.git/config-dev.yaml",
          "source": {
              "config.info": "master branch,daijunyi/spring-learn-config/config-dev.yaml version=1"
          }
      }]
    }
    

    5、重要配置细节总结

    image.png

    3、Config客户端配置与测试

    1、bootstap.yml

    1、简介

  • app1icaiton.yml是用尸级的资源配直项

  • bootstrap.yml是系统级的,优先级更加高
  • Spring Cloud会创建一个”Bootstrap Context”,作为Spring应用的Application Context’的父上下文。初始化的时候,Bootstrap Context’负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。
  • Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。Bootstrap context和’ Application Context有着不同的约定,所以新增了一 个bootstrap.ymI文件, 保证Bootstrap Context和Application Context配置的分离。
  • 要将Client模块下的application.ym文件改为bootstrap.yml,这是很关键的,因为bootstrap.yml是比application.yml先加载的。bootstrap.ymI优先级高于 application.yml

    2、创建cloud-config-client-3355

    1、修改pom文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
          <artifactId>cloud2020</artifactId>
          <groupId>com.daijunyi</groupId>
          <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>
    
      <artifactId>cloud-config-client-3355</artifactId>
    
      <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-config</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
          </dependency>
          <dependency>
              <groupId>com.daijunyi</groupId>
              <artifactId>cloud-api-commons</artifactId>
              <version>${project.version}</version>
          </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.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <optional>true</optional>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-test</artifactId>
              <scope>test</scope>
          </dependency>
      </dependencies>
    </project>
    

    2、创建bootstrap.yml文件

    添加配置 ```yaml server: port: 3355

spring: application: name: config-client cloud: config: label: main name: config profile: dev uri: http://localhost:3344 eureka: client: service-url: defaultZone: http://www.eureka7001.com:7001/eureka,http://www.eureka7002.com:7002/eureka

<a name="cG81S"></a>
#### 3、添加启动类
```java
package com.daijunyi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConfigClient3355 {

    public static void main(String[] args) {
        SpringApplication.run(ConfigClient3355.class,args);
    }
}

4、添加业务类

package com.daijunyi.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigController {

    @Value("${config.info}")
    private String config;

    @GetMapping("/config/info")
    public String getConfigInfo(){
        return config;
    }
}

5、测试

访问http://localhost:3355/config/info
image.png

3、问题随时而来,分布式配置的动态刷新

  • 假如这个时候我们的服务不停止,我们修改了config-dev.yaml的配置信息version为2 看看 信息会不会变
  • 访问http://config-3344.com:3344/main/config-dev.yaml
    • image.png
    • 变成了2
  • 访问http://localhost:3355/config/info
    • image.png
    • 发现没有变化
  • 但是我们重启3355这个服务就会发现变化

    • 但是我们不可能每次配置变化了来重启服务

      4、Config客户端之动态刷新

  • 避免每次更新配置都要重启客户端微服务3355

    1、动态刷新

    1、修改3355模块引入actuator

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

    2、修改bootstrap配置

  • 健康监测简单粗暴的开启所有

    management:
    endpoints:
      web:
        exposure:
          include: "*"
    

    3、业务类添加@RefreshScope

    @RefreshScope
    @RestController
    public class ConfigController {
    
      @Value("${config.info}")
      private String config;
    
      @GetMapping("/config/info")
      public String getConfigInfo(){
          return config;
      }
    }
    

    4、测试

  • 修改github version为3

  • 访问http://config-3344.com:3344/main/config-dev.yaml
    • image.png
  • 访问http://localhost:3355/config/info
    • image.png
    • 还是2
  • 这个时候我们不重启去通知

    • 可以使用postman 发送一个post请求,得是post请求
    • 或者使用curt发送一个post请求,到客户端
      curl -X POST "http://localhost:3355/actuator/refresh"
      
      请求成功之后返回结果
      [
      "config.client.version",
      "config.info"
      ]
      
  • 这个时候我们再去请求http://localhost:3355/config/info

    • image.png
  • 但是这种方式还是觉得麻烦 办自动刷新配置。

    2、如果是修改端口号

  • 发现也只能是重启之后才能生效。

    2、SpringCloud Bus 消息总线

    1、概述

    1、上一讲解的加深和扩充,一言以蔽之

  • 分布式自动刷新配置功能

  • Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新

    2、是什么

  • Bus支持两种消息代理:RabbitMQ和Kafka

image.png

3、作用

image.png

4、为何被称为总线

1、什么是总线

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题, 并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题 上的实例都知道的消息。

2、基本原理

  • ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。
  • https://www.bilibili.com/video/av55976700?from=search&seid= 15010075915728605208

    2、RabbitMQ环境配置

    1、安装docker方式

  • 运行如下命令

    • 用户名为admin
    • 密码也为admin
      docker run -dit --name Myrabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
      
  • 访问localhost:15672,管理后台页面

  • 5672是服务器端口

    2、其他方式

    1、等待补充

    3、SpringCloud Bus动态刷新全局广播

  • 必须先具备良好的RabbitMQ环境先

  • 演示广播效果,增加复杂度,再以3355为模板再制作一个3366

    1、设计思想

    1、利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置

    image.png

    2、利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置(更加推荐)

    image.png

    3、图二的架构显然更加合适,图一不适合的原因如下

  • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新职责

  • 破坏了微服务各节点的对等性
  • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

    2、给cloud-config-center-3344配置中心服务端添加消息总线支持

    1、修改pom.xml

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

    2、修改yaml

    ```yaml server: port: 3344 spring: application: name: cloud-config-center cloud: config:
    server:
      git:
        uri: git@github.com:daijunyi/spring-learn-config.git
        search-paths:
          - spring-learn-config
    label: main
    
    rabbitmq: host: localhost port: 5672 username: admin password: admin eureka: client: service-url:
    defaultZone: http://www.eureka7001.com:7001/eureka,http://www.eureka7002.com:7002/eureka
    
    management: endpoints: web:
    exposure:
      include: "bus-refresh"
    
<a name="edhmq"></a>
### 3、修改3355,和3377
<a name="eG9DH"></a>
#### 1、修改pom.xml
```xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

2、配置yaml

server:
  port: 3377

spring:
  application:
    name: config-client
  cloud:
    config:
      label: main
      name: config
      profile: dev
      uri: http://localhost:3344
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
eureka:
  client:
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka,http://www.eureka7002.com:7002/eureka

management:
  endpoints:
    web:
      exposure:
        include: "*"

4、测试

1、启动项目各自访问

  • 访问http://localhost:3344/main/config-dev.yaml
    • image.png
  • 访问http://localhost:3355/config/info
    • image.png
  • 访问http://localhost:3377/config/info

    • image.png

      2、修改github中的版本为version:4

  • 访问http://localhost:3344/main/config-dev.yaml

    • image.png
  • 访问http://localhost:3355/config/info
    • image.png
  • 访问http://localhost:3377/config/info

    • image.png

      3、发送post请求到3344

  • 使用curl -X POST http://localhost:3344/actuator/bus-refresh

  • 或者使用postman发送post请求
  • image.png
  • 访问http://localhost:3355/config/info
    • image.png
  • 访问http://localhost:3377/config/info

    • image.png

      4、SpringCloud Bus动态刷新定点通知

  • 不想全部通知,只想定点通知

    • 只通知3355
    • 不通知3377
  • 简单一句话

  • 修改version为5

  • 然后post发送
  • 访问http://localhost:3344/main/config-dev.yaml
    • image.png
  • 访问http://localhost:3355/config/info
    • image.png
  • 访问http://localhost:3377/config/info

    • image.png

      3、SpringCloud Stream消息驱动

      1、消息驱动概述

      1、是什么

  • 屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型

    1、什么是SpringCloudStream

  • 官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。

  • 应用程序通过inputs或者outputs与Spring Cloud Stream中binder对象交互。通过我们配置来binding(绑定),而Spring Cloud Stream的binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream互就可以方便使用消息驱动的方式。
  • 通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
  • Spring Cloud Stream为一些供应商的消息中间件产 品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
  • 目前仅支持RabbitMQ、Kafka.

    2、官网

  • https://spring.io/projects/spring-cloud-stream#overview

  • https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/
  • Spring Cloud Stream中文指导手册

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

    • Message
  • 消息必须走特定的通道
    • 消息通道MessageChannel
  • 消息通道里的消息如何被消费呢,谁负责收发处理

    • 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器订阅

      2、为什么用Cloud Stream

      1、架构
      image.png
  • 比方说我们用到了RabbitMQ和Kafka, 由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区,

  • 这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的, -大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。

    2、stream凭什么可以统一底层差异
  • 在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性通过定义绑定器作为中间层, 完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的Channel通道, 使得应用程序不需要再考虑各种不同的消息中间件实现。

  • image.png

    3、Binder
  • INPUT对应于消费者

  • OUTPUT对应于生产者
  • 在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接 与消息中间件进行信息交互的时候,于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性.通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。Stream对消息 中间件的进一步封装可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka), 使得微服务开发的高度解耦,服务可以关注更多自己的业务流程
  • image.png

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

  • Topic主题进行广播

    • 在RabbitMQ就是Exchange
    • 在kafka中就是Topic

      3、Spring Cloud Stream标准流程套路

      image.png
      image.png
  • Binder

    • 很方便的连接中间件,屏蔽差异
  • Channel
    • 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过对Channel对队列进行配置
  • Source和Sink

    • 简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入

      4、编码API和常用注解

      image.png

      2、案例说明

  • RabbitMQ环境已经OK

  • 工程中新建三个子模块

    • cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
    • cloud-stream-rabbitmq-consumer8802,作为消息接收模块
    • cloud-stream-rabbitmq-consumer8803,作为消息接收模块

      3、消息驱动之生产者

  • 创建cloud-stream-rabbitmq-provider8801

    1、修改pom.xml

    ```xml <?xml version=”1.0” encoding=”UTF-8”?> <project xmlns=”http://maven.apache.org/POM/4.0.0

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    

      <artifactId>cloud2020</artifactId>
      <groupId>com.daijunyi</groupId>
      <version>1.0-SNAPSHOT</version>
    

    4.0.0 cloud-stream-rabbitmq-provider8801

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
          <groupId>com.atguigu.springcloud</groupId>
          <artifactId>cloud-api-commons</artifactId>
          <version>${project.version}</version>
      </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>


    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<a name="MTxPm"></a>
### 2、修改yaml
```yaml
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: admin
                password: admin
      bindings: # 服务的整合处理
        output: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: defaultRabbit  # 设置要绑定的消息服务的具体设置

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka,http://www.eureka7002.com:7002/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地址

3、主启动类

package com.daijunyi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StreamRabbitMQProvider8801 {

    public static void main(String[] args) {
        SpringApplication.run(StreamRabbitMQProvider8801.class,args);
    }
}

4、创建业务类

  • ImessageProveder接口 ```java package com.daijunyi.service;

public interface IMessageProvider { public String send(); }


- 实现类
   - 这里的MessageChannel名称得写成output,不然会报以下错误
   - 存在三个这种类型的实例
   - Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.messaging.MessageChannel' available: expected single matching bean but found 3: output,nullChannel,errorChannel
```java
package com.daijunyi.service.impl;

import com.daijunyi.service.IMessageProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;

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

@EnableBinding(Source.class)
@Slf4j
public class ImessageProviderImpl implements IMessageProvider {

    @Resource
    private MessageChannel output;

    @Override
    public String send() {
        String msg = "发送消息:" + UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(msg).build());
        log.info(msg);
        return msg;
    }
}
  • controller ```java package com.daijunyi.controller;

import com.daijunyi.service.IMessageProvider; import com.daijunyi.service.impl.ImessageProviderImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController public class SendMessageController {

@Resource
private IMessageProvider imessageProvider;

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

}

<a name="ZgsNH"></a>
### 5、测试

- 访问[http://127.0.0.1:8801/send/message](http://127.0.0.1:8801/send/message)
   - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1630927747525-7bfe3144-a83a-4953-b331-5fcb414321ca.png#clientId=u72399269-0ee3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=69&id=u6b7a9736&margin=%5Bobject%20Object%5D&name=image.png&originHeight=138&originWidth=842&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22752&status=done&style=none&taskId=ue07d7b52-316a-4592-88f5-0ab20c14d98&title=&width=421)
- 访问rabbitMQ:[http://localhost:15672](http://localhost:15672/#/)发现有波动

![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1630927776643-63a9c038-df8e-4ef1-bdd8-ed2c2190a32a.png#clientId=u72399269-0ee3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=625&id=u7ea65f24&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1250&originWidth=1928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=189531&status=done&style=none&taskId=u187f11a7-6f41-44d4-b5dc-a183d8d2396&title=&width=964)
<a name="hdQ0p"></a>
## 4、消息驱动之消费者

- 创建cloud-stream-rabbitmq-consumer8802
<a name="UnPk6"></a>
### 1、修改pom
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.daijunyi</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-stream-rabbitmq-consumer8802</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.daijunyi</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

2、修改yaml

server:
  port: 8802

spring:
  application:
    name: cloud-stream-sonsumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: admin
                password: admin
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: defaultRabbit  # 设置要绑定的消息服务的具体设置


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

3、主启动类

package com.daijunyi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StreamRabbitMQConsumer8802 {

    public static void main(String[] args) {
        SpringApplication.run(StreamRabbitMQConsumer8802.class,args);
    }
}

4、业务

  • controller
    • EnableBinding会绑定stream,通过liastener来监听 ```java package com.daijunyi.controller;

import lombok.extern.slf4j.Slf4j; 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;

@EnableBinding(Sink.class) @Slf4j public class ReceiveMessageController {

@StreamListener(Sink.INPUT)
public void receverMessage(Message<String> message){
    String payload = message.getPayload();
    log.info("接受信息:"+payload);
}

}

<a name="Zr1Zb"></a>
## 5、分组消费与持久化
<a name="jI7IK"></a>
### 1、创建cloud-stream-rabbitmq-consumer8803和8802一样

- 这是端口号不一样
<a name="sDyHS"></a>
### 2、启动

- RabbitMQ
- 7001,7002,服务注册(其实可以不用)
- 8801(消息生产者)
- 8802(消息消费)
- 8803(消息消费)
<a name="gEFk8"></a>
### 3、运行后两个问题

- 有重复消费问题
- 消息持久化问题
<a name="mlPN4"></a>
### 4、消费

- 目前是8802/8803同时都收到了,存在重复消费问题
- 分组和持久化属性group
- 比如在如下场景中,订单系统我们做集群部署, 都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。这时我们就可以使用Stream中的消息分组来解决
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1630935482755-35bf5922-1aaa-4392-9b0f-b404ec70336c.png#clientId=u72399269-0ee3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=297&id=ucdcc02a9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=594&originWidth=1148&originalType=binary&ratio=1&rotation=0&showTitle=false&size=161588&status=done&style=none&taskId=u83afa027-f209-4ae3-9491-1f4dfe09a22&title=&width=574)
- 注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。不同组是可以全面消费的(重复消费),同一组内会发生竞争关系,只有其中-个可以消费。
<a name="elMPk"></a>
### 5、分组
<a name="hwCKg"></a>
#### 1、原理

- 微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
<a name="zuFh9"></a>
#### 2、8802/8803都变成不同组,group两个不同

- 修改8802为group djyA
- 修改8803位group djyB
```yaml
spring:
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: admin
                password: admin
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: defaultRabbit  # 设置要绑定的消息服务的具体设置
          group: djyA

image.png这个不同组发送消息还是会发现都接受到了

3、8802/8803都变成相同组,group两个相同

  • 都修改外group : djyA
  • 同一个组的多个微服务实例,每次只会有一个拿到
  • image.png
  • image.png
  • image.png

    6、持久化

  • 通过上述,解决了重复消费问题,再看看持久化

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

image.png

  • 8803重启之后接收到消息

image.png

4、SpringCloud Sleuth分布式请求链路追踪

1、概述

1、为什么会出现这个技术?需要解决哪些问题?

  • 问题
  • 在微服务框架中, 一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何-环出现高延时或错误都会引起整个请求最后的失败。
  • image.png
  • image.png

    2、是什么

  • https://github.com/spring-cloud/spring-cloud-sleuth

  • Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案
  • 在分布式系统中提供追踪解决方案并且兼容支持了zipkin

    3、解决

    image.png

    2、搭建链路监控步骤

    1、zipkin的安装和运行

    1、文档

  • 官网

  • 源码

  • 运行之后访问http://localhost:9411/zipkin/

    2、完整的调用链路

  • 表示一请求链路,一条链路通过Trace ld唯一标识, Span标识发起的请求信息,各span通过parent id关联起来

image.png

3、上图what

image.png

  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
  • span:表示调用链路来源,通俗的理解span就是一次请求信息

    3、案例

    1、修改cloud-provider-payment8001

  • 修改pom

    <!--包含了sleuth+zipkin 用来跟着微服务调用链路-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    
  • 修改yaml

    • 指向zipkin地址
      spring:
      zipkin:
      base-url: http://localhost:9411
      
  • 添加点业务逻辑

      @GetMapping("/payment/zipkin")
      public String paymentZipkin()
      {
          return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
      }
    

    2、修改cloud-consumer-order80

  • 修改pom

    <!--包含了sleuth+zipkin 用来跟着微服务调用链路-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    
  • 修改yaml

    • 指向zipkin地址
      spring:
      zipkin:
      base-url: http://localhost:9411
      
  • 添加点业务逻辑

      public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";   
    // ====================> zipkin+sleuth
      @GetMapping("/consumer/payment/zipkin")
      public String paymentZipkin()
      {
          String result = restTemplate.getForObject(PAYMENT_URL+"/payment/zipkin/", String.class);
          return result;
      }
    

    3、依次启动eureka7001,eureka7002/8001/80

  • 访问http://127.0.0.1:7001/

    • image.png
  • 访问http://localhost/consumer/payment/zipkin
    • image.png
  • 访问http://localhost:9411
    • image.png
    • image.png
    • image.png
    • image.png