2021-09-07 SpringCloud(H版)+SpringCloud alibaba_02

目录

一、GateWay

 1、Gateway三大核心概念

 2、Gateway工作流程

3、入门配置

 1、pom,配置文件

2、路由映射

4、通过微服务名实现动态路由

1、增加配置

5、 Predicate(断言)的使用

1、After,Before,Between

6、Getaway的Filter(过滤器)

1、GatewayFilter

2、GlobalFilter

 3、自定义过滤器

二、SpringCloud Config(分布式配置中心)

1、概述

 2、服务端从配置中心读取

1、 pom,配置文件

2、测试

4、配置的读取规则

5、客户端从配置中心读取

 1、pom

2、bootstrap.yml

3、方法

 4、测试

5、问题

6、客户端动态刷新配置

2、@RefreshScope注解

3、测试

4、解决方案

三、SpringCloud Bus(消息总线)

 1、概述

2、作用

3、 什么是总线

2、Bus使用--RabbitMQ配置

 3、新建模块

4、RabbitMQ实现广播的设计思想

5、动态刷新广播通知(Bus整合RabbitMQ)

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

2、添加RabbitMQ相关配置

3.修改3355,3366

4、测试

6、动态刷新定点通知

四、SpringCloud Stream(消息驱动)

1、什么是SpringCloudStream

2、设计思想

 3、Binder

4、Spring Cloud Stream标准流程和常用注解

 5、生产者

1、pom,配置文件,主启动类

2、业务类

 6、消费者

1、pom,配置文件,主启动类

2、业务类

7、分组消费与持久化

1、问题

 2、解决方法

​3、如何分组

4、消息持久化原理

五、SpringCloud Sleuth(分布式请求链路跟踪)

1、zipkin的使用(微服务链路监控系统)

 2、业务代码

 1、配置文件

2、业务类

3、测试


一、GateWay

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,
那就是SpringCloud Gateway一句话:gateway是原zuul1.x版的替代

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等

 作用有

 1、Gateway三大核心概念

 2、Gateway工作流程

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
 
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
 
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

3、入门配置

新建模块

 1、pom,配置文件

    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>

将其注册


server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka
 

2、路由映射

cloud-provider-payment8001模块为例做路由映射

我们目前不想暴露8001端口,希望在8001外面套一层9527

增加配置文件如下

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由

以上配置表示访问9527下的/lb/**服务或/get/**服务都会转到8001的对应服务

测试:

    //调用时返回服务器端口
    @GetMapping("/payment/lb")
    public String getServerPort(){
        return serverPort;
    }

测试此方法

成功

但是这样配置路由有一定问题,将路由地址写死,并且不利于后期扩容,难以维护

4、通过微服务名实现动态路由

以此解决上面的问题 

默认情况下Gateway会根据注册中心注册的服务列表,
以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

启动7001、8001、8002

1、增加配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-payment-service  #lb负载均衡+微服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-payment-service   #lb负载均衡+微服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
      #开启从注册中心动态创建路由功能,利用微服务名进行路由
      discovery:
        locator:
          enabled: true

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

如此,网关会在客户端通过负载均衡策略将请求分配给服务端的两个服务器

 

5、 Predicate(断言)的使用

启动配置好的网关后,控制台如下

 而配置文件中的断言采用的是Path

 官方自带的断言有11种

翻译: Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
 
Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。
 
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。 

1、After,Before,Between

符合规定时间的条件才可以进行路由

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
            - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]         # 断言,路径相匹配的进行路由
            - Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]         # 断言,路径相匹配的进行路由


eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

通过java8中的方法可以得到时间格式

package demo;

import java.time.ZonedDateTime;

public class T2 {
    public static void main(String[] args) {
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println(now);
//        2021-09-07T11:23:21.097183900+08:00[GMT+08:00]
    }
}

。。。。。。

6、Getaway的Filter(过滤器)


路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
 
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

1、GatewayFilter

配置方案有30+种,参考官网文档

Spring Cloud Gateway

2、GlobalFilter

参考

 3、自定义过滤器

有两个主要接口GlobalFilter,Ordered,用于做全局日志记录,统一网关鉴权

ServerWebExchange ,这个参数是一个整合参数,可以获取服务中的许多关键信息,便于我们进行过滤

package com.atguigu.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("*******进入到过滤器*******");
//        exchange类似于HttpServletRequest和HttpServletResponse
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null){
            log.info("用户名不能为空!");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        //交给下一步
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

测试:

请求带有uname,可以成功 

当请求不符合过滤器条件时

 访问失败,打开控制台

 我们手动设置的响应状态码也正常显示,成功过滤

4、对路径进行重写

比方请求如下路径

 真实后端路径中不带api,而前端携带以便区分,那么按照如下规则

在这里插入图片描述

 请求来到网关会判断满足/api/这个前缀要求,然后上会从nacos注册中心找到指定微服务的地址/xxxxxx,实际上这个请求会被转到http://xxxxxx:8080/api/captcha.jpg,即将网关地址转换成指定的微服务地址,然后再直接拼接上后边的地址。这就有可能导致微服务地址不一致,进而报404

此时需要根据微服务实际的URL地址重写路径,根据Spring官网文档,通过网关的过滤器,使用正则表达式可以重写路径

这样就实现了网关对路由的重写

二、SpringCloud Config(分布式配置中心)

1、概述

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

如何操作

SpringCloud Config分为服务端和客户端两部分。

服务端

也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口

客户端

则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。


微服务意味着要将单应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。

springCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理。比如数据库的信息,我们可以写到一个统一的地方。

 2、服务端从配置中心读取

在码云(gitee)上创建一个仓库,内有三种环境配置文件

 新建工程

 

1、 pom,配置文件

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </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>

 如果仓库是私有,且采用http方式,需要配置用户名密码

server:
  port: 3344

spring:
  application:
    name:  cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/hyto/springcloud-config.git #GitEE上面的git仓库名字
          ####搜索目录
          search-paths:
            - springcloud-config
          username: 码云用户名
          password: 码云密码
          force-pull: true #强行拉取
      ####读取分支
      label: master

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

2、测试

修改本地hosts文件,将config-3344.com映射本地的127.0.0.1,浏览器访问

 不同环境配置文件都可以访问

4、配置的读取规则

 我们采用master分支

 总结

  •  /{name}-{profiles}.yml
  • /{label}-{name}-{profiles}.yml

label:分支(branch)
name :服务名
profiles:环境(dev/test/prod)

成功实现了用SpringCloud Config通过GitHub获取配置信息

5、客户端从配置中心读取

新建模块

 1、pom

客户端依赖名不同于服务端

<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>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>

2、bootstrap.yml

1、 applicaiton.yml是用户级的资源配置项
2、bootstrap.yml是系统级的,优先级更加高

        Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的`Application Context`的父上下文。初始化的时候,`Bootstrap Context`负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的`Environment`。

        `Bootstrap`属性有高优先级,默认情况下,它们不会被本地配置覆盖。 `Bootstrap context`和`Application Context`有着不同的约定,所以新增了一个`bootstrap.yml`文件,保证`Bootstrap Context`和`Application Context`配置的分离。


要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,
因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址k

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

3、方法

主启动类

package com.atguigu.springcloud;

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

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

controller

package com.atguigu.springcloud.controller;

import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;
@RestController
public class ConfigClientController
{
    @Value("${config.info}")
    private String configInfo;

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

controller中方法要获取仓库中配置文件的信息

 4、测试

3344自测

 3355客户端通过该3344测试

  成功

5、问题

假设运维在gitee上修改了我们的配置文件

 刷新3344

 刷新3355

 ctrl+F5强制刷新也不能更新,这就是产生的问题,除非重启,否则3355不会更新,这会带来非常大的麻烦

6、客户端动态刷新配置

解决以上的问题

修改3355模块

1、修改pom,yml

<!--pom引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

2、@RefreshScope注解

加在controller上代表开启刷新

@RestController
@RefreshScope
public class ConfigClientController
{
    @Value("${config.info}")
    private String configInfo;

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

3、测试

重启3355,再次修改仓库文件

 这次重启3355只拉取到上上一次的更新,本次重启后的更新又没有生效

4、解决方案

手动动态刷新需要运维人员发送Post请求刷新3355

curl -X POST "http://localhost:3355/actuator/refresh"

 成功,但是手动动态刷新仍然有一定限制,当服务膨胀后,需要频繁进行刷新请求,消息总线bus可配合config解决动态刷新这个问题

三、SpringCloud Bus(消息总线)

上一章节的加深和扩充,一言以蔽之,为了实现以下功能

 1、概述

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

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

 Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,
它整合了Java的事件处理机制和消息中间件的功能。


2、作用

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道

3、 什么是总线

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


基本原理

        ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。

2、Bus使用--RabbitMQ配置

1、使用docker运行RabbitMQ镜像

docker run -d -p 15672:15672 -p 5672:5672 【镜像id】

2、访问15672端口,ip地址为虚拟机ip,默认超级管理员用户密码都为guest

 3、新建模块

以3355为模板新建模块

pom,yml,主启动类同3355

controller

package com.atguigu.springcloud.conrtoller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope
public class ConfigClientController {
    @Value("${server.port}")
    private String serverPort;

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

    @GetMapping("/configInfo")
    public String configInfo()
    {
        return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo;
    }
}

4、RabbitMQ实现广播的设计思想

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

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

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

  • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
  • 破坏了微服务各节点的对等性。
  • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

5、动态刷新广播通知(Bus整合RabbitMQ)

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

<!--添加消息总线RabbitMQ支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

2、添加RabbitMQ相关配置

server:
  port: 3344

spring:
  #rabbitmq配置
  rabbitmq:
    host: 192.168.0.100
    port: 5672
    username: guest
    password: guest 
  application:
    name:  cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/hyto/springcloud-config.git #GitEE上面的git仓库名字
          ####搜索目录
          search-paths:
            - springcloud-config
          username: hyto
          password: 1999225a
          force-pull: true
      ####读取分支
      label: master

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
##rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints: #暴露bus刷新配置的端点
    web:
      exposure:
        include: 'bus-refresh'

注意:暴露的端点:bus-refresh是执行刷新的固定端点

3.修改3355,3366

添加依赖

<!--添加消息总线RabbitMQ支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

添加配置文件

#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
  rabbitmq:
    host: 192.168.0.100
    port: 5672
    username: guest
    password: guest

4、测试

启动

 3344自测

 3355

3366 修改仓库内容

 发送刷新请求到3344

  curl -X POST "http://localhost:3344/actuator/bus-refresh"

 这时3355,3366都生效

 查看交换机

6、动态刷新定点通知

只通知3355发生了变化,而不通知3366

公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}

修改仓库文件

 命令行输入以下指令

curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"

即可只刷新3355的信息

四、SpringCloud Stream(消息驱动)

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、设计思想

标准MQ

但是当多种MQ同时使用,比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区,

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

 3、Binder

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

 Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。

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

Topic主题进行广播,在RabbitMQ就是Exchange,在Kakfa中就是Topic

4、Spring Cloud Stream标准流程和常用注解

 Binder:

很方便的连接中间件,屏蔽差异

Channel:

通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置

Source和Sink:

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

 5、生产者

1、pom,配置文件,主启动类

<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.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</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>
server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 192.168.0.100
                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 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址
package com.atguigu.springcloud;

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

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

2、业务类

service

public interface IMessageProvider {
    //发送消息
    public String send();
}

实现类

package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.service.IMessageProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;

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

@Slf4j
@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());
        log.info("serial---->"+serial);
        return null;
    }
}

Controller

package com.atguigu.springcloud.service.controller;

import com.atguigu.springcloud.service.IMessageProvider;
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 messageProvider;

    @GetMapping("/sendMsg")
    public String sendMsg(){
        return messageProvider.send();
    }
}

测试

 消息发送成功

 6、消费者

1、pom,配置文件,主启动类

pom同8801,主启动类同8801

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 192.168.0.100
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          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 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8802.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址




 配置文件bindings下的input标签,不同于生产者的output标签,注意!

2、业务类

Controller

package com.atguigu.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
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;
import org.springframework.stereotype.Component;

@Component
@EnableBinding(Sink.class)//对应生产者的Source
public class ReceiveMessageListener {

    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message){
        System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
    }
}

测试:

发送消息

 8802顺利接收消息,通信成功

7、分组消费与持久化

1、问题

对照8802创建8803,启动8801,8802,8803,7001

 8801发送一条信息,但是8802,8803都收到了这条消息,发生了消息重复消费的问题

        比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,
那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。
这时我们就可以使用Stream中的消息分组来解决

注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
不同组是可以全面消费的(重复消费),
同一组内会发生竞争关系,只有其中一个可以消费。 

 2、解决方法

问题:消息被重复消费

原因:默认8802,8803被分配到了两个队列,而不同队列,是可以重复消费的,因此需要分组

3、如何分组

 只需修改配置文件

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 192.168.0.103
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置
          group: youngA

在stream的配置中,加上group即可分组,8802,8803此时都是一组

 此时发送消息

 两条消息被8802,8803各接收一条,解决了消息重复消费的问题

4、消息持久化原理

执行如下操作,先关闭8802,8803,删除8802的group分组属性,然后8801发送四条消息,然后启动8802,发现没有group的8802并没有去接收这四条消息,然后启动8803,带有group属性的8803接收到了这四条消息

  

  这说明分组属性对于消息队列的持久化也起到作用

 我们可以看见,分组的队列会有D标签,代表持久化(durable)

然后我们关闭8802,8803

 此时消息队列只剩下8803,由此得到group确实会持久化我们的队列,而没有分组时只产生了一个临时的随机队列,用完就会被删除

五、SpringCloud Sleuth(分布式请求链路跟踪)

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

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

 zipkin下载地址

Central Repository: io/zipkin/java/zipkin-server/2.12.9

1、zipkin的使用(微服务链路监控系统)

SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可

java -jar zipkin-server-2.12.9-exec.jar

此命令即可执行该jar包,如下

 成功启动

访问

http://localhost:9411/zipkin/

即可进入管理页面

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

 Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识

span:表示调用链路来源,通俗的理解span就是一次请求信息

 2、业务代码

利用80和8001实验,加入依赖

<!--包含了sleuth+zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

 1、配置文件

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver              # mysql驱动包 com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: root
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      #采样率值介于 0 到 1 之间,1 则表示全部采集
      probability: 1

 加入zipkin和sleuth配置

2、业务类

8001Controller

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

80Controller

    // ====================> zipkin+sleuth
    @GetMapping("/consumer/payment/zipkin")
    public String paymentZipkin()
    {
        String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
        return result;
    }

3、测试

80调用几次

 可以根据zipkin的筛选条件对我们微服务调用的信息进行查看分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值