SpringCloud(H版以及Alibaba版本)学习笔记(二)

本笔记学习自B站尚硅谷Springcloud时所记录

笔记内容包括了:Springcloud的H版以及Alibaba版本

  • H版具体内容包括:Eureka、Zookeeper、Consul、Ribbon、OpenFeign、Hystrix、Gateway、Config、Bus、Stream、Sleuth等技术的使用;
  • Alibaba版本包括:Nacos、Sentinel、Seata等技术的使用

点击直达

七、微服务组件之服务官网Gateway

1、基本概述

  • 谈谈你对微服务网关的理解
  • 你对微服务网关选的是哪一个?
  • 为什么要用Gateway?它比zuul好在哪?

Gateway是什么?

  • Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。
  • Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
  • SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式。
  • SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty
  • Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

Gateway能干嘛?

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控

微服务架构中网关在哪个位置?
在这里插入图片描述

2、Gteway非阻塞异步模型

2.1、为什么有了zuul还要选择Gateway?

  • 一方面是因为Zuul1.0已经进入了维护阶段,功能已经大大落后了需求,而且Gateway是SpringCloud团队研发的,值得信赖;
  • Gateway是基于异步非阻塞模型上进行开发的,性能方面不用担心,虽然Netflix早就发布了Zuul2.x,但是Netflix相关组件基本上都宣布进入了维护期,综合多方面考虑Gateway是非常理想的网关选择。

2.2、SpringCloud Gateway的特性:

  • 基于基于Spring Framework 5Project ReactorSpring Boot 2.0进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定Predicate (断言)和Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成Spring Cloud 服务发现功能;
  • 易于编写的Predicate (断言)和Filter (过滤器);
  • 请求限流功能;
  • 支持路径重写。

2.3、SpringCloud Gateway与Zuul的区别

  • 在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul。
  • Zuul 1.x,是一个基于阻塞I/O的API Gateway。
  • Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第-次加载较慢的情况,使得Zuul的性能相对较差。
  • Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul .x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
  • Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API
  • Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

2.4、Zuul1.x模型说明

Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Serviet IO处理模型。

Servlet的生命周期?servlet由servlet container进行生命周期管理。

  • container启动时构造servlet对象并调用servlet init()进行初始化;
  • container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service);
  • container关闭时调用servlet destory()销毁servlet。
    在这里插入图片描述

上述模式的缺点:

Servlet是一个简单的网络IO模型,当请求进入Servlet container时,Servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(如抽风用Jmeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。

结论: 所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即Spring实现了处理所有request请求的一个servlet (DispatcherServlet)并由该servlet阻塞式处理处理。所以SpringCloud Zuul无法摆脱servlet模型的弊端。

2.5、Gateway模型说明

传统的Web框架,比如说: Struts2,SpringMVC等都是基于Servlet APl与Servlet容器基础之上运行的。

但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring 5必须让你使用Java 8)。

Spring WebFlux是Spring 5.0 引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

3、Gateway的工作流程

3.1、三大核心概念

  • Route(路由): 路由是构建网关的基本模块,它由ID、目标URI、一系列的断言和过滤器组成,如断言为true则匹配该路由;

  • Predicate(断言): 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;

  • Filter(过滤): 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

在这里插入图片描述

  • web请求通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
  • predicate就是我们的匹配条件;
  • 而fliter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

3.2、Gateway工作流程

在这里插入图片描述

Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.

中文解释

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

核心逻辑:路由转发 + 执行过滤器链。

4、Gateway9527的搭建

第一步: 新建一个Module,名为:Module - cloud-gateway-gateway9527
第二步: 改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>cloud2021</artifactId>
        <groupId>com.oldou.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Module - cloud-gateway-gateway9527</artifactId>

    <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.oldou.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>
</project>

第三步: 改yml文件

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

第四步: 主启动类

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

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

提问:9527如何做路由映射?

  • 首先我们先查看cloud-provider-payment8001服务的controller的访问地址,分别是:
    • localhost:8001/pay/lb、localhost:8001/pay/get/{id}
  • 怎么做到不想暴露8001端口,希望在8001端口外面套一层8527?如下所示

第五步: 打开cloud-gateway-gateway9527服务的yml文件,添加以下配置:

server:
  port: 9527

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

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

第六步: 启动测试

  • 启动cloud-eureka-server7001cloud-provider-payment8001cloud-gateway-gateway9527三个服务
  • 访问Eureka:http://eureka7001.com:7001/,网关和8001都注册进来了。
    在这里插入图片描述
  • 访问添加网关前:http://localhost:8001/pay/get/1
    在这里插入图片描述
  • 访问添加网关后:http://localhost:9527/pay/get/1,端口替换也能访问成功。
    在这里插入图片描述
    在这里插入图片描述
    当浏览器输入http://localhost:9527/pay/get/1进行访问时,首先会判断在网关中能不能通过localhost:9527访问到地址为localhost:8001的(可以),接下来判断predicates(断言),判断8001下应该有一个/pay/get/**这样的地址,如果路由上的为true就访问成功,反之失败。

5、Gateway配置路由的两种方式

第一种方式: 在配置文件yml中配置

  • 就是上一节中的配置,在网关服务的yml中添加以下配置:
spring:
 application:
   name: cloud-gateway
 #############################新增网关配置###########################
 cloud:
   gateway:
     routes:
       - id: pay_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
         uri: http://localhost:8001          #匹配后提供服务的路由地址
         predicates:
           - Path=/pay/get/**         # 断言,路径相匹配的进行路由

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

第二种方式:代码中注入RouteLocator的Bean

  • 第一步:新建一个com.oldou.springcloud.config.GateWayConfig
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GateWayConfig {
    /**
     * 配置一个id为path_route_oldou的路由规则
     * 当访问【localhost:9527/guonei】时,网关会自动将请求转发到【http://news.baidu.com/guonei】
     * @param routeLocatorBuilder /
     * @return /
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        //这里的配置,实现的效果为:当访问【localhost:9527/guonei】时,网关会将请求转发到【http://news.baidu.com/guonei】
        routes.route("path_route_oldou",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
}
  • 第二步:重启Gateway服务,访问:localhost:9527/guonei
    在这里插入图片描述

6、Gateway配置动态路由

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

准备: 启动Eureka7001、然后cloud-provicer-payment8001/8002

改POM: 确定网关服务9527中有以下依赖

<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

修改配置: 修改网关服务9527的yml文件

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

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

需要注意的是:

  • uri的协议是lb,表示启用Gateway的负载均衡公民
  • lb://serviceName是springcloud gateway在微服务中自动为我们创建的负载均衡uri

测试:

  • 访问:http://localhost:9527/pay/lb,不断刷新,发现8001/8002端口不断切换。

7、Predicate的使用

7.1、Route Predicate Factories这个是什么?

Spring Cloud Gateway matches routes as part of the Spring WebFlux HandlerMapping infrastructure. Spring Cloud Gateway includes many built-in route predicate factories. All of these predicates match on different attributes of the HTTP request. You can combine multiple route predicate factories with logical and statements.

中文解释:

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

7.2、常用的Route Predicate Factory介绍以及使用

介绍本次的测试方法:

  • 我们需要启动EurekaMain7001、PaymentMain8001/8002、网关服务
  • 打开cmd,我们使用curl进行测试

The After Route Predicate Factory

  • 概述:该工厂需要配置一个条件时间,当请求的时间在该时间之后则此路由匹配。
  • 网关服务中的yml配置,在predicates中添加- After=带时区的时间
    spring:
      cloud:
        gateway:
          routes:
            - id: pay_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service #匹配后提供服务的路由地址
              predicates:   # 断言
                - Path=/pay/get/**         # 路径相匹配的进行路由
                # 请求时间在这个时间后才能起效
                - After=2021-07-15T15:39:56.741+08:00[Asia/Shanghai] 
    
    当请求时间满足在【亚洲上海时区的2021年7月15日15点39分56秒】之后生效
  • 当前时区的时间获取方法为:
    public static void main(String[] args){
           ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
           System.out.println(zbj);
       }
    
  • 测试:
    • 在cmd窗口中,输入curl localhost:9527/pay/get/1,访问成功
      在这里插入图片描述
    • 当我们把yml中的时间改成当前时间往后加一个小时,重启网关服务的时候,再次访问就会报错,这是因为请求的时间不满足设置的时间条件,因此该请求被拦截了。
      在这里插入图片描述

The Before Route Predicate Factory

  • 概述:该设置于上述相反,是设置一个时间,当请求的时间在设置的时间之前则此路由匹配。
  • yml的配置介绍:
    spring:
      cloud:
        gateway:
          routes:
            - id: pay_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service #匹配后提供服务的路由地址
              predicates:   # 断言
                - Path=/pay/get/**         # 路径相匹配的进行路由
                # 请求时间在这个时间前才能起效
                - Before=2021-07-15T15:39:56.741+08:00[Asia/Shanghai] 
    

The Between Route Predicate Factory

  • 概述:设置一个时间区间【A,B】,当请求时间在该区间范围内则此路由匹配。 。
  • 配置介绍:
    spring:
      cloud:
        gateway:
          routes:
            - id: pay_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service #匹配后提供服务的路由地址
              predicates:   # 断言
                - Path=/pay/get/**         # 路径相匹配的进行路由
                # 请求时间在这个时间区间以内才能起效
                - Between=2021-07-15T15:39:56.741+08:00[Asia/Shanghai], 2021-08-15T15:39:56.741+08:00[Asia/Shanghai] 
    

The Cookie Route Predicate Factory

  • 概述:设置请求必须要带有对应的Cookie则此路由匹配。
  • yml配置:
    spring:
     cloud:
       gateway:
         routes:
         - id: cookie_route
           uri: https://example.org
           predicates:
           - Cookie=username, root   # 设置请求必须带有【username=root】这样的Cookie才能成功
    
  • 测试:
    • cmd输入:curl localhost:9527/pay/get/1 访问失败
    • 输入:curl localhost:9527/pay/get/1 --cookie "username=root"
      在这里插入图片描述

The Header Route Predicate Factory

  • 概述:请求头中的id需要满足正则表达式【这里的正则需要为正数】则此路由匹配,两个属性值,一个属性名,一个正则表达式。
  • yml配置
    spring:
      cloud:
        gateway:
          routes:
          - id: header_route
            uri: https://example.org
            predicates:
            - Header=X-Request-Id, \d+ #请求头中X-Request-Id需要为正数
    
  • 测试:
    • 访问:curl http://localhost:9527/pay/get/1 -H "X-Request-Id:123" 访问ok
    • 访问:curl http://localhost:9527/pay/get/1 -H "X-Request-Id:-123" 失败

后面几个只介绍配置

The Host Route Predicate Factory

  • 配置示例:- Host=**.oldou.com
  • 概述:如果请求的Host报头值为www.oldou.combeta.oldou.comcool.oldou.com,则此路由匹配。
  • 测试:curl localhost:9527/pay/get/1 -H "Host:www.oldou.com"

The Method Route Predicate Factory

  • 配置示例:- Method=GET,POST
  • 概述:如果请求方法是GET或POST,则此路由匹配。

The Path Route Predicate Factory

  • 概述:路径匹配

The Query Route Predicate Factory

  • 概述:查询路由,包含查询参数,则路由匹配。、
  • - Query=username, \d+ 要有参数名username并且值要为正数才能路由
  • curl localhost:9527/pay/lb?username=31

The RemoteAddr Route Predicate Factory

  • 示例:- RemoteAddr=192.168.1.10
  • 概述:如果请求的远端地址是192.168.1.10,则此路由匹配。

7.3、总结

总而言之:Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行匹配处理,当所有的Route都满足了,就返回true。

8、Filter的使用

概念: 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in GatewayFilter Factories.

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

过滤器的作用:

  • 全局日志记录
  • 统一网关鉴权
  • 等等

过滤器的配置

常用的GatewayFilter: AddRequestParameter GatewayFilter

自定义全局过滤器GlobalFilter

  • 实现的效果为:要求访问的请求中必须带有uname参数,并且参数不能为空,否则访问失败。

在网关服务中,添加以下代码

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;

import java.util.Date;

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("***********come in MyLogGateWayFilter:  "+new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if(uname==null){
            log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        // 合法用户就放行
        return chain.filter(exchange);
    }

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

访问测试

  • 访问:http://localhost:9527/pay/lb
    在这里插入图片描述
  • 访问:http://localhost:9527/pay/lb?uname=1111
    在这里插入图片描述

八、微服务组件之分布式配置中心Config

1、基本概念

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

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

  • 因此SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理.……

Config是什么?
在这里插入图片描述

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

SpringCloud Config的作用

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

目标: 由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式。

2、Config配置总控中心搭建

前期准备:

  • Github的官网

  • 需要在Github上创建一个账号,然后新建一个名为springcloud-config的新Repository。
    在这里插入图片描述

  • 创建仓库之后会有一个地址,我的地址为:https://github.com/oldou/springcloud-config.git

  • 保证本地装有Git的前提下,将仓库clone下来:

    • 我的目录为:E:\Github\微服务的git仓库
    • 使用命令:git clone https://github.com/oldou/springcloud-config.git
  • 此时目录就被clone下来了,我新建了四个文件夹,如下所示:
    在这里插入图片描述

  • 对三个配置文件做以下内容添加:
    config-dev.tml:

    config:
      info: "master branch,springcloud-config/config-dev.yml version=7"
    

    config-prod.yml:

    config:
      info: "master branch,springcloud-config/config-prod.yml version=1"
    

    config-test.yml:

    config:
      info: "master branch,springcloud-config/config-test.yml version=1" 
    
  • 使用以下命令依次上传到Github

    • git add .
    • git commit -m "提交的信息"
    • git push -u origin master 这一步之后会弹出一个框输入Github的账号和密码
  • 上传成功:
    在这里插入图片描述

创建config服务的步骤
第一步: 新建一个Module,名为:cloud-config-center-3344
第二步: 改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>cloud2021</artifactId>
        <groupId>com.oldou.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-config-center-3344</artifactId>
    <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>

</project>

第三步: 改yml文件

server:
  port: 3344

spring:
  application:
    name:  cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://github.com/oldou/springcloud-config.git  #GitHub上面的git仓库地址
          ####搜索目录
          search-paths:
            - springcloud-config  # github上的仓库名
          request-connect-timeout: 5000
          username:   #git仓库帐号
          password: #git仓库密码
          force-pull: true
      ####读取分支
      label: master

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

第四步: 主启动类

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

@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {

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

第五步: windows下修改hosts文件,增加映射[C:\Windows\System32\drivers\etc]

127.0.0.1 config-3344.com

第六步: 先启动Eureka7001,然后在启动cloud-config-center-3344

  • 测试通过Config微服务是否可以从GitHub上获取配置内容

  • 访问:http://config-3344.com:3344/master/config-dev.yml
    在这里插入图片描述
    **配置读取规则 **

  • 第一种: /{label}/{application}-{profile}.yml(推荐)

    • master分支
      http://config-3344.com:3344/master/config-dev.yml
      http://config-3344.com:3344/master/config-test.yml
      http://config-3344.com:3344/master/config-prod.yml
      
    • dev分支
      http://config-3344.com:3344/dev/config-dev.yml
      http://config-3344.com:3344/dev/config-test.yml
      http://config-3344.com:3344/dev/config-prod.yml
      
  • 第二种方式: /{application}-{profile}.yml

    http://config-3344.com:3344/config-dev.yml
    http://config-3344.com:3344/config-test.yml
    http://config-3344.com:3344/config-prod.yml
    http://config-3344.com:3344/config-xxxx.yml(不存在的配置)
    
  • 第三种方式: /{application}/{profile}[/{label}]

    http://config-3344.com:3344/config/dev/master
    http://config-3344.com:3344/config/test/master
    http://config-3344.com:3344/config/test/dev
    
  • 说明

    /{name}-{profiles}.yml
    /{label}-{name}-{profiles}.yml
    
    label:分支(branch)
    name:服务名
    profiles:环境(dev/test/prod)
    

3、Config客户端配置与测试

第一步: 新建一个Module,名为:cloud-config-client-3355
第二步: 改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>cloud2021</artifactId>
        <groupId>com.oldou.springcloud</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>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>

第三步: 新增bootstrap.yml文件

  • 区别: applicaiton.yml是用户级的资源配置项,而bootstrap.yml是系统级的,优先级更加高
  • 注意:
    • 当创建一个boorstrap.yml文件时,Spring Cloud会创建一个Bootstrap Context,作为Spring应用的Application Context的父上下文。
    • 初始化的时候,BootstrapContext负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的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
    
    

第四步: 主启动类

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);
    }
}

第五步: 业务类

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
public class ConfigClientController {
    @Value("${config.info}")
    private String configInfo;

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

第六步: 测试

  • 启动Config配置中心3344微服务并自测:http://config-3344.com:3344/master/config-prod.yml
  • 启动3355服务作为Client准备访问:http://localhost:3355/configInfo

成功实现了客户端3355访问SpringCloud Config3344服务通过GitHub获取配置信息。

分布式配置的动态刷新问题:

  • 问题的产生:当Linux运维修改GitHub上的配置文件内容做调整,例如config-dev.yml做了修改操作,我们刷新3344,发现ConfigServer配置中心立刻响应,修改后的内容会立即出来,但是我们刷新3355,却发现ConfigClient客户端没有任何响应,3355没有变化,除非自己重启或者重新加载。
  • 难道每次运维修改配置文件,客户端都需要重启吗?这明显不科学,因此我们需要配置动态刷新。

4、Config动态刷新之手动版

在这里插入图片描述

目标: 避免每次更新配置都要重启客户端微服务3355,因此我们需要配置手动版的动态刷新。

第一步: 修改cloud-config-client-3355服务
第二步: 改pom文件,确定服务中有以下依赖:

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

第三步: 修改YML,添加暴露监控端口配置:

# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

第四步: 业务类Controller增加@RefreshScope刷新注解

@RestController
@RefreshScope   //添加此注解
public class ConfigClientController {
    @Value("${config.info}")
    private String configInfo;

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

第五步: 重启3355服务进行测试

  • 首先我们先去Github上修改config-dev.yml配置文件,将版本号改成8;
  • 访问3344服务,http://localhost:3344/master/config-dev.yml,版本号是能实时刷新的,这是因为3344是直接访问Github,没问题;
  • 访问3355服务,http://localhost:3355/configInfo,发现版本号还是7,没有实时改变,这是因为我们这样配置还缺少一个步骤
  • 需要运维工程师发送一个355服务的POST刷新请求
    curl -X POST "http://localhost:3355/actuator/refresh"
    
    在这里插入图片描述
  • 再次访问3355服务,http://localhost:3355/configInfo,测试OK

后续遗留问题

假如有多个微服务客户端3355/3366/3377…等等服务,每个微服务都要执行这样—次post刷新请求,相当的麻烦,可否存在广播类型的,一次通知,处处生效的方式呢?接下来我们往后走。

九、微服务组件之消息总线Bus

在这里插入图片描述

  • SpringCloud Bus配合SpringCloud Config使用可实现配置的动态刷新。
  • Config和Bus是绝配,要用就一起用。

1、Bus消息总线的基本概念

在这里插入图片描述

  • SpringCloud Bus是用来将分布式系统的节点与轻量级消息系统连接起来的框架
  • 它整合了Java 事件处理机制和消息中间件的功能
  • Springcloud Bus目前只支持RabbitMQ和Kafka

什么是总线?

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

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

2、Bus之RabbitMQ环境配置

本次介绍的是Windows上安装RabbitMQ,如果需要Linux上安装MQ,请查看我的其他文章。

安装步骤:

  • 安装Erlang,下载地址:http://erlang.org/download/otp_win64_21.3.exe,直接安装即可

  • 安装RabbitMQ,下载地址:https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.3/rabbitmq-server-3.8.3.exe

  • 安装完毕之后,我们打开cmd进入到RabbitMQ安装目录下的sbin目录,如:D:\Environmentt\rabbitmq\rabbitmq_server-3.8.3\sbin

  • 输入以下命令启动管理功能

    d: #切换到D盘
    cd D:\Environmentt\rabbitmq\rabbitmq_server-3.8.3\sbin  #进入到MQ安装目录的sbin
    rabbitmq-plugins enable rabbitmq_management  #启动MQ
    

    在这里插入图片描述

  • 启动成功之后,访问http://localhost:15672/,账号密码为:guest/guest
    在这里插入图片描述

  • 当然了,不想用CMD启动的话,可以直接点击MQ的可视化插件
    在这里插入图片描述

3、Bus动态刷新之全局广播的设计思想和选型

准备:

  • 搭建好RabbitMQ,确定localhost:15672能够访问成功。
  • 演示广播效果,增加复杂度,再以3355为模板再制作一个3366

第一步: 新建一个Module,名为:cloud-config-client-3366
第二步: 改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>cloud2021</artifactId>
        <groupId>com.oldou.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-config-client-3366</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>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>

第三步: 新增bootstrap.yml文件

server:
  port: 3366

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

# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

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

第四步: 主启动类

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

@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3366 {

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

}

第五步: 业务类

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+"\n\n configInfo: "+configInfo;
    }
}

设计方案介绍

  • 方案一:利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
    在这里插入图片描述
  • 方案二:利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
    在这里插入图片描述

选择: 明显的,方案二更合适,方案一不合适的具体原因如下所示:

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

4、Bus动态刷新之全局广播配置实现

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

  • 第一步: 修改pom文件,添加以下依赖:
    <!--添加消息总线RabbitNQ支持-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-bus-amap</artifactId>
    </dependency>
    <dependency>
    	<groupId>org-springframework.boot</groupId>
    	<artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 第二步: 改yml文件,添加MQ以及bus的配置
    #rabbitmq相关配置<--------------------------
    rabbitmq:
      host: localhost
      port: 5672
      username: guest
      password: guest
    ##rabbitmq相关配置,暴露bus刷新配置的端点<--------------------------
    management:
      endpoints: #暴露bus刷新配置的端点
        web:
          exposure:
            include: 'bus-refresh'
    

cloud-config-client-3355客户端添加消息总线支持

  • 第一步: 修改pom文件,添加以下依赖:
    <!--添加消息总线RabbitNQ支持-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-bus-amap</artifactId>
    </dependency>
    <dependency>
    	<groupId>org-springframework.boot</groupId>
    	<artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 第二步: 改yml文件,添加MQ以及bus的配置
    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
        #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口<----------------------
      rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
    
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    #服务注册到eureka地址
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    

cloud-config-client-3366客户端添加消息总线支持

  • 第一步: 修改pom文件,添加以下依赖:

    <!--添加消息总线RabbitNQ支持-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-bus-amap</artifactId>
    </dependency>
    <dependency>
    	<groupId>org-springframework.boot</groupId>
    	<artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 第二步: 改yml文件,添加MQ以及bus的配置

    server:
      port: 3366
    
    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
      #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口<-----------------------
      rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
    
    # 暴露监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    #服务注册到eureka地址
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    

测试

  • 启动:

    • cloud-eureka-server7001
    • cloud-config-center-3344
    • cloud-config-client-3355
    • cloud-config-client-3366
  • 修改Github上配置文件内容,增加版本号

  • 发送POST请求
    curl -X POST "http://localhost:3344/actuator/bus-refresh"

  • —次发送,广播通知,处处生效

  • 访问以下请求:

    • http://config-3344.com:3344/config-dev.yml
    • http://localhost:3355/configlnfo
    • http://localhost:3366/configInfo
  • 发现版本号都改变了。

5、Bus动态刷新之定点通知

实现效果: 当我们修改配置文件之后,不想全部通知,只想定点通知

  • 只通知3355
  • 不通知3366

指定具体某一个服务实例生效而不是全部;

公式: config配置总控中心ip:端口/actuator/bus-refresh/{destination}

  • config服务主机ip:端口,我们这个是在本地运行的,因此为localhost:3344
  • destination:为指定需要更新配置的服务或实例名:端口号,

举例:

  • 我们这里以刷新运行在3355端口上的config-client(配置文件中设定的应用名称)为例,只通知3355,不通知3366
  • 运维执行命令为:curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355
    在这里插入图片描述

6、Bus通知总结

在这里插入图片描述

十、微服务组件之消息驱动Stream

1、Stream的基本概念

为什么Stream会被引入?

  • 我们常见的MQ有:ActiveMQ、RabbitMQ、RacketMQ、Kafka这四种,而我们的项目中难免会遇见使用到其中两种MQ的情况,那么我们需要考虑有没有一种新的技术诞生,让我们不再关注具体MQ的细节,我们只需要用一种适配绑定的方式,自动的给我们在各种MQ内切换。(类似于Hibernate)

  • SpringCloud Stream可以屏蔽了底层消息中间件的差异,降低切换成本,统一消息的一种编程模型

Spring Cloud Stream是什么?

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

为什么要用SpringCloud Stream?
在这里插入图片描述

  • 假如我们在做项目开发时,需要用到RabbitMQ和Kafka,但是由于这两种消息中间件在架构上的不同,例如:RabbitMQ的Exchange(交换机),Kafka的Topic和Partiyions分区
  • 这些中间件的差异导致实际项目开发中给我们造成了一定的困扰,例如:我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移、替换,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候Spring Cloud Stream给我们提供了—种解耦合的方式。

2、Stream的设计思想

标准的MQ模型
在这里插入图片描述

  • 生产者/消费者之间靠消息媒介传递信息内容;
  • 消息必须走特定的通道 - 消息通道 Message Channel;
  • 消息通道里的消息如何被消费呢,谁负责收发处理 - 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅。

Stream怎么统一底层差异?

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

Binder对象

  • input对应于消费者
  • output对应于生产者
    在这里插入图片描述
    Stream中的消息通信方式遵循了发布-订阅模式
  • Topic主题进行广播
  • 在RabbitMQ就是Exchange
  • 在Kakfa中就是Topic

3、Stream编码常用注解简介

Spring Cloud Stream标准流程套路

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

  • 官方模型:
    在这里插入图片描述

  • 大致模型:
    在这里插入图片描述

  • Binder:很方便的连接中间件,屏蔽差异。

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

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

编码API和常用注解说明

组成说明
Middleware中间件,目前只支持RabbitMQ和Kafka
BinderBinder是应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现
@Input注解标识输入通道,通过该输乎通道接收到的消息进入应用程序
@Output注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener监听队列,用于消费者的队列的消息接收
@EnableBinding指信道channel和exchange绑定在一起

案例说明

  • 准备RabbitMQ环境(上面有说明)
  • 工程中新建三个子模块
    • cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
    • cloud-stream-rabbitmq-consumer8802,作为消息接收模块
    • cloud-stream-rabbitmq-consumer8803,作为消息接收模块

4、Stream消息驱动之生产者

第一步: 新建一个Module,名为:cloud-stream-rabbitmq-provider8801
第二步: 改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>cloud2021</artifactId>
        <groupId>com.oldou.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-stream-rabbitmq-provider8801</artifactId>
    <dependencies>
        <!--如果是整合Kafka,将rabbit换成kafka就可以了-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</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.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</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>

第三步: 改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 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

第四步: 主启动类

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);
    }
}

第五步: 业务类

  • 消息推送接口
/**
 * 定义消息生产者的消息发送接口
 */
public interface IMessageProvider {
    public String send();
}
  • 消息推送实现类
@Slf4j
@EnableBinding(Source.class) //定义消息推送的管道源
public class IMessageProviderImpl 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;
    }
}

//注意,这里的Source是大致模型图中的Source,官方模型图中的outputs
  • controller
import com.oldou.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(value = "/sendMessage")
    public String sendMessage() {
        return messageProvider.send();
    }
}

第六步: 测试

  • 启动 7001eureka
  • 启动RabbitMQ,命令为:rabbitmq-plugins enable rabbitmq_management
  • 访问:http://localhost:15672/
  • 启动cloud-stream-rabbitmq-provider8801,访问http://localhost:8801/sendMessage
    在这里插入图片描述
    在这里插入图片描述

4、Stream消息驱动之消费者

第一步: 新建一个Module,名为:cloud-stream-rabbitmq-consumer8802
第二步: 改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>cloud2021</artifactId>
        <groupId>com.oldou.springcloud</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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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-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>

第三步: 改yml文件

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: # 客户端进行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地址

第四步: 主启动类

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

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

第五步: 业务类

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

@Slf4j
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {

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

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

/** 注解说明
    - @Enablebinding : 指定信道channel和exchange绑定在一起,生产者是Source,消费者是Sink
    - @StreamListener : 监听队列,用于消费者的队列的消息接收,监听的是输入源Sink.INPUT

 **/

第六步: 测试

  • 启动EurekaMain7001、StreamMQMain8801、StreamMQMain8802、MQ

  • 8801发送8802接收消息,访问:http://localhost:8801/sendMessage

  • 8001发送的消息,8002能够实时的监控并拿到数据
    在这里插入图片描述

5、Stream之消息重复消费问题

准备: 我们依照8802,克隆出来一份名为:cloud-stream-rabbitmq-consumer8803服务。

测试:

  • 启动以下服务
    • RabbitMQ
    • 服务注册 - cloud-eureka-server7001
    • 消息生产 - cloud-stream-rabbitmq-provider8801
    • 消息消费 - cloud-stream-rabbitmq-provider8802
    • 消息消费 - cloud-stream-rabbitmq-provider8803
  • 访问:http://localhost:8801/sendMessage

出现问题:

  • 有重复消费问题
    • 我们发现,当8001发送一个消息时,8002和8003都接收到了,存在重复消费问题。
    • 如何解决:分组和持久化属性group(重要)
  • 消息持久化问题

生产实际案例

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

6、Stream之group解决消息重复消费问题

原理:

  • 微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。
  • 不同的组是可以重复消费的,同一个组内会发生竞争关系,只有其中一个可以消费

假设:自定义配置分组能够解决重复消费问题

  • 自定义配置分为同一个组,解决重复消费问题
  • 8802/8803都变成不同组,两个group都不同:【group: A_Group、B_Group】

修改配置

  • 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 # 设置要绑定的消息服务的具体设置
              group: GROUP_A   #----->设置8002的消费组名
    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地址
    
    
  • 8003服务
    server:
      port: 8803
    
    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: GROUP_B    #----->设置8003的消费组名
    
    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地址
    
    

结论: 重启测试发现还是重复消费,自定义配置分组不能解决重复消费问题

解决办法: 8802/8803实现了轮询分组,每次只有一个消费者,8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。
修改配置: 8802/8803都变成相同组,group两个相同,都变成GROUP_A

  • 8802修改YML,group: GROUP_A
  • 8803修改YML,group: GROUP_A

结论: 同一个组的多个微服务实例,每次只会有一个拿到,因此解决重复消费的办法,需要配置同一个消费者,使用轮询方式解决。

7、Stream之消息持久化

当我们的消息消费者没有运行的前提下,我们的消息生产者生产了N个消息到消息队列MQ,一段时间后,我们的消费者启动了,但是由于缺少某项配置拿不到消息,造成了消息数据的丢失,因此针对这一问题,我们需要来了解一下消息的持久化。

目的: 通过上节解决了重复消费问题,接下来我们再看看消息的持久化问题。

准备:

  • 停止8802/8803服务的运行
  • 去除掉8802的分组group: GROUP_A,注意:8803的分组group: A_Group没有去掉
  • 保证8802和8803是没有运行的前提下,使用8801先发送4条消息到RabbitMQ。

测试:

  • 先启动8802,无分组属性配置,后台没有打印出任何消息。
  • 再启动8803,有分组属性配置,后台打出来了MQ上的消息。(消息持久化体现)

结论: group这个属性,在解决消息重复消费以及消息持久化方面具有非常重要的意义

十一、微服务组件之链路追踪Sleuth

1、Sleuth的概述

在这里插入图片描述

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

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

Sleuth是什么?
在这里插入图片描述

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

2、Sleuth之zipkin搭建安装

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

下载地址:

  • https://repo1.maven.org/maven2/io/zipkin/java/zipkin-server/
  • zipkin-server-2.12.9-exec.jar

运行zipkin:

  • 找到自己zipkin的下载目录,在文件的目录处输入cmd快速切换并打开windows命令窗口
    在这里插入图片描述
  • 运行下载的zipkin的jar包,java -jar zipkin-server-2.12.9-exec.jar,运行成功如图所示:
    在这里插入图片描述
    运行控制台
  • 访问:http://localhost:9411/zipkin/
    在这里插入图片描述

调用链路的术语

  • 表示一请求链路,一条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来
    在这里插入图片描述

  • —条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来。
    在这里插入图片描述

  • 整个链路的依赖关系如下:
    在这里插入图片描述

  • 名词解释

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

3、Sleuth之链路监控展现

cloud-provider-payment8001服务提供者
第一步: 找到cloud-provider-payment8001服务提供者
第二步: 改pom文件,添加以下依赖

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

第三步: 修改yml文件

spring:
  application:
    name: cloud-payment-service

  zipkin: #<-------------------------------------关键 
      base-url: http://localhost:9411
  sleuth: #<-------------------------------------关键
    sampler:
    #采样率值介于 0 到 1 之间,1 则表示全部采集
    probability: 1
....

第四步: 增加业务类PaymentController接口

@RestController
@Slf4j
public class PaymentController {
    
    ...
    
 	@GetMapping("/pay/zipkin")
    public String paymentZipkin() {
        return "hi ,i'am paymentzipkin server fall back,welcome to here, O(∩_∩)O哈哈~";
    }    
}

找到服务消费者(调用方)cloud-consumer-order80

  • 改pom文件,添加以下依赖
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    
  • yml文件
    spring:
        application:
            name: cloud-order-service
        zipkin:
          base-url: http://localhost:9411
        sleuth:
          sampler:
            probability: 1
    
  • 业条类OrderController
// ====================> zipkin+sleuth
    @GetMapping("/consumer/pay/zipkin")
    public String paymentZipkin()
    {
        String result = restTemplate.getForObject("http://localhost:8001"+"/pay/zipkin/", String.class);
        return result;
    }

测试

  • 依次启动eureka7001/8001/80服务
  • 80调用8001几次测试下
  • 打开浏览器访问: http://localhost:9411
    在这里插入图片描述
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值