天天看点

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

Spring Cloud

  • 1· Eureka注册中心
    • 什么是Eureka
    • 基础架构
    • Eureka原理图
    • 编写EurekaServer
      • 添加eureka依赖
      • 编写启动类
      • 编写配置yml配置文件
    • 服务注册
      • 服务提供端添加依赖
      • 在启动类上开启Eureka客户端功能
      • 编写配置
    • 服务发现
      • 在客户端添加依赖
      • 在启动类添加开启Eureka客户端发现的注解
      • 编写配置
      • 修改代码,用DiscoveryClient类的方法,根据服务名称,获取服务实例
    • 失效剔除和自我保护
      • 服务下线
      • 失效剔除
      • 自我保护
        • 关闭自我保护模式
  • 负载均衡Ribbon
    • 什么是Ribbon
    • 开启负载均衡
    • 修改负载均衡算法
  • Hystrix
    • 雪崩问题
    • 线程隔离&服务降级
    • 编写 Hystrix服务降级
    • 服务熔断
      • 熔断原理
      • 熔断原理图
      • 改熔断策略
  • Feign
    • 负载均衡
    • Hystrix支持
    • 请求压缩
    • 日志级别
  • Spring Cloud Gateway网关
    • Gateway加入后的架构
  • spring cloud config
  • spring cloud bus

Spring Cloud是Spring旗下的项目之一,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:

  • Eureka:注册中心
  • Zuul:服务网关(现在常用gateway)
  • Ribbon:负载均衡
  • Feign:服务调用
  • Hystrix:熔断器

1· Eureka注册中心

在介绍Eureka注册中心前,我们先建立两个模型:服务提供者(user-service),服务消费者(consumer)。用户操作消费者模块来调用提供者模块访问数据。

什么是Eureka

Eureka是基于REST(Representational State Transfer)服务,主要以AWS云服务为支撑,提供服务发现并实现负载均衡和故障转移。我们称此服务Eureka服务。Eureka提供了Java客户端组件,Eureka Client,方便与服务端的交互。客户端内置了基于round-robin实现的简单负载均衡。在Netflix,为Eureka提供更为复杂的负载均衡方案进行封装,以实现高可用,它包括基于流量、资源利用率以及请求返回状态的加权负载均衡。

基础架构

Eureka架构中的三个核心角色:

  • 服务注册中心:Eureka的服务端应用,提供服务注册和发现功能。
  • 服务提供者:提供服务的应用,可以是Spring Boot应用,也可以是其它任意技术实现,只要对外提供的是REST风格服务即可。
  • 服务消费者:消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。

Eureka原理图

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus
  • Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
  • 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
  • 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
  • 心跳(续约):提供者定期通过HTTP方式向Eureka刷新自己的状态

编写EurekaServer

添加eureka依赖

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
           

编写启动类

//声明当前应用时Eureka服务
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
           

编写配置yml配置文件

server:
  #如果jvm参数存在则使用参数port下面同理 ${port:10086}
  port: 10086
  ng:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      #eureka服务的地址,如果做集群,需要指定其他的eureka地址 ${defaultZone:http://127.0.0.1:10086/eureka}
      defaultZone: http://127.0.0.1:10086/eureka
      #不注册自己
    register-with-eureka: false
    #不拉取服务
    fetch-registry: false
           

服务注册

服务提供端添加依赖

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

在启动类上开启Eureka客户端功能

通过添加 @EnableDiscoveryClient 来开启Eureka客户端功能

@SpringBootApplication
@MapperScan("com.lxs.user.mapper")
@EnableDiscoveryClient //开启Eureka客户端发现功能
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
           

编写配置

server:
  port: ${port:9999}
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/springcloud?useSSL=false
    username: root
    password: 123456
  application:
    name: user-service
mybatis:
  type-aliases-package: com.yh.user.pojo
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
        #http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
           

不用指定register-with-eureka和fetch-registry,因为默认是true

服务发现

在客户端添加依赖

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

在启动类添加开启Eureka客户端发现的注解

@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端
public class UserConsumerDemoApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHTTP3ClientHTTPRequestFactory());
}
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
}
           

编写配置

spring:
  application:
    name: consumer-demo
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
           

修改代码,用DiscoveryClient类的方法,根据服务名称,获取服务实例

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
public String queryById(@PathVariable Long id) {
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}

}
           

失效剔除和自我保护

服务下线

当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线

了”。服务中心接受到请求之后,将该服务置为下线状态

失效剔除

有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间

(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。 可以通过eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。

自我保护

当关停一个服务,就会在Eureka面板看到一条警告:

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%,当EurekaServer节点在短时间内丢失过多客户端(可能发生了网络分区故障)。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多

数服务依然可用。

关闭自我保护模式

eureka:
  server:
    enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
    eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)
           

负载均衡Ribbon

什么是Ribbon

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

根据服务器名从eureka-server中获得地址列表,Ribbon会根据负载均衡算法从地址列表中得到一个地址进行访问。算法分为轮询和随机。默认为轮询

开启负载均衡

因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。直接修改代码:

在RestTemplate的配置方法上添加 @LoadBalanced 注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
           

修改负载均衡算法

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
           

Hystrix

Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务,防止出现级联失败。

雪崩问题

微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路。如果此时某个微服务发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞。服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。

这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这

个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。

Hystrix解决雪崩问题的手段,主要包括:

线程隔离

服务降级

线程隔离&服务降级

线程隔离:

Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。

用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理。

服务降级:

服务降级可以优先保证核心服务。用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息)。服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

触发Hystrix服务降级的情况:

  • 线程池已满
  • 请求超时

编写 Hystrix服务降级

  1. 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
           

2.开启熔断

在启动类消费者 ConsumerApplication 上添加注解:@EnableCircuitBreaker

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ConsumerApplication {
// ...
}
           

注意:@SpringCloudApplication=

@SpringBootApplication

@EnableDiscoveryClient

@EnableCircuitBreaker

@SpringCloudApplication
public class ConsumerApplication {
// ...
}
           

3.编写降级逻辑

当目标服务的调用出现故障,使用HystrixCommand来完成一个快速失败,并给出用户一个友好的提示。需要提前编写好失败时的降级处理逻辑。

@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallback")
public String queryById(@PathVariable Long id) {
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
public String queryByIdFallback(Long id) {
log.error("查询用户信息失败。id:{}", id);
return "对不起,网络太拥挤了!";
}
}
           

注:因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。

@HystrixCommand(fallbackMethod = “queryByIdFallBack”):用来声明一个降级逻辑的方法

4.超时设置

Hystrix的默认超时时长为1s

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds=2000:
           

服务熔断

熔断原理

  • 在服务熔断中,使用的熔断器,也叫断路器,其英文单词为:Circuit Breaker 熔断机制与家里使用的电路熔断原理类似;当如果电路发生短路的时候能立刻熔断电路,避免发生灾难。在分布式系统中应用服务熔断后;服务调用方可以自己进行判断哪些服务反应慢或存在大量超时,可以针对这些服务进行主动熔断,防止整个系统被拖垮。
  • Hystrix的服务熔断机制,可以实现弹性容错;当服务请求情况好转之后,可以自动重连。通过断路的方式,将后续请求直接拒绝,一段时间(默认5秒)之后允许部分请求通过,如果调用成功则回到断路器关闭状态,否则继续打开,拒绝请求的服务。

熔断原理图

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

状态机有3个状态:

  • Closed:关闭状态(断路器关闭),所有请求都正常访问。
  • Open:打开状态(断路器打开),所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
  • HalfOpen:半开状态,不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断路器,否则继续保持打开,再次进行休眠计时

改熔断策略

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds= 5000:
      circuitBreaker:
        errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
        sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
        requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20

           

Feign

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

1 .Feign依赖

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

2.Feign的客户端

@FeignClient("user-service")
public interface UserClient {
//http://user-service/user/123
@GetMapping("/user/{id}")
User queryById(@PathVariable("id") Long id);
}
           
  • 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟Mybatis的mapper很像
  • @FeignClient ,声明这是一个Feign客户端,同时通过 value 属性指定服务名称
  • 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果
  • @GetMapping中的/user,请不要忘记;因为Feign需要拼接可访问的地址

编写新的控制器类 ConsumerFeignController ,使用UserClient访问:

@RestController
@RequestMapping("/cf")
public class ConsumerFeignController {
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
return userClient.queryById(id);
}
}
           

3.开启Feign功能

在 ConsumerApplication 启动类上,添加注解,开启Feign功能

@SpringCloudApplication
@EnableFeignClients //开启feign功能
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
           

负载均衡

Feign中本身已经集成了Ribbon依赖和自动配置,因此不需要额外引入依赖,也不需要再注册 RestTemplate 对象。Fegin内置的ribbon默认设置了请求超时时长,默认是1000ms,可以通过手动配置来修改这个超时时长:

ribbon:
  ReadTimeout: 2000 # 读取超时时长
  ConnectTimeout: 1000 # 建立链接的超时时长
           

或者为某一个具体service指定

user-service
  ribbon:
    ReadTimeout: 2000 # 读取超时时长
    ConnectTimeout: 1000 # 建立链接的超时时长
           

因为ribbon内部有重试机制,一旦超时,会自动重新发起请求。可以通过配置来组织发起请求

ribbon:
  ConnectTimeout: 1000 # 连接超时时长
  ReadTimeout: 2000 # 数据通信超时时长
  MaxAutoRetries: 0 # 当前服务器的重试次数
  MaxAutoRetriesNextServer: 0 # 重试多少次服务
  OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
           

Hystrix支持

Feign默认有对Hystrix的集成,默认情况下是关闭的。需要通过下面的参数来开启:

feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能
           

首先,要定义一个类,实现刚才编写的UserFeignClient,作为fallback的处理类

@FeignClient("user-service")
public interface UserClient {
//http://user-service/user/123
@GetMapping("/user/{id}")
User queryById(@PathVariable("id") Long id);
}
           
@Component
public class UserClientFallback implements UserClient {
@Override
public User queryById(Long id) {
User user = new User();
user.setId(id);
user.setName("用户异常");
return user;
}
}
           

然后在UserClient中,指定刚才编写的实现类

@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
           

请求压缩

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能。也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
      min-request-size: 2048 # 设置触发压缩的大小下限
    response:
      enabled: true # 开启响应压缩
           

日志级别

可以通过 logging.level.lxs.xx=debug 来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为@FeignClient 注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。

1.在 consumer-demo 的配置文件中设置com.xxx包下的日志级别都为 debug添加如下配置:

logging:
  level:
    com.xxx: debug
           

2.编写配置类,定义日志级别

@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
//记录所有请求和响应的明细,包括头信息、请求体、元数据
return Logger.Level.FULL;
}
}
           

这里指定的Level级别是FULL,Feign支持4种级别:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据

3.在 UserClient 接口类上的@FeignClient注解中指定配置类

@FeignClient(value = "user-service", fallback = UserClientFallback.class, configuration =
FeignConfig.class)
           

Spring Cloud Gateway网关

  • Spring Cloud Gateway是Spring官网基于Spring 5.0、 Spring Boot 2.0、Project Reactor等技术开发的网关服务。
  • Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等。
  • Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式。
  • Spring Cloud Gateway是替代Netflix Zuul的一套解决方案。

Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。Spring Cloud Gateway本身也是一个微服务,需要注册到Eureka服务注册中心。

网关的核心功能是:过滤和路由

Gateway加入后的架构

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

不同于Feign,Feign是不同微服务之间的相互调用请求。Gateway不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都可经过网关,然后再由网关来实现 鉴权、动态路由等等操作。Gateway就是我们服务的统一入口。

spring cloud config

在码云中创建一个仓库,并添加yml配置文件

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus
Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

回到Idea,创建一个config模块

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

添加config依赖

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
           
@SpringBootApplication
@EnableConfigServer //开启配置服务
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
           

修改application.yml

server:
  port: 12000
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          #码云仓库地址
          uri: https://gitee.com/yin-hang1531/my-config

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

           

成功访问出配置信息

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

将服务者模块application.yml中的配置删除,创建bootstrap.yml将添加一下配置信息可从远程仓库中调用公共配置信息(以后配置信息交给nacos管理)

spring:
  cloud:
    config:
# 要与仓库中的配置文件的application保持一致
    name: user
# 要与仓库中的配置文件的profile保持一致
    profile: dev
# 要与仓库中的配置文件所属的版本(分支)一样
    label: master
    discovery:
# 使用配置中心
      enabled: true
# 配置中心服务名
      service-id: config-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
           

bootstrap.yml文件也是Spring Boot的默认配置文件,而且其加载的时间相比于application.yml更早。

application.yml和bootstrap.yml虽然都是Spring Boot的默认配置文件,但是定位却不相同。bootstrap.yml

可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。application.yml 可以用来定义应用级别

的参数,如果搭配 spring cloud config 使用,application.yml 里面定义的文件可以实现动态替换。

总结就是,bootstrap.yml文件相当于项目启动时的引导文件,内容相对固定。application.yml文件是微服务的一些常规配置参数,变化比较频繁。

spring cloud bus

****Spring Cloud Bus是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件的更改或者服务的监控管理。也就是消息总线可以为微服务做监控,也可以实现应用程序之间相互通信。 Spring Cloud Bus可选的消息代理有RabbitMQ和Kafka。(以后配置信息改变自动刷新交给nacos)

使用了Bus之后 :

Spring Cloud与微服务1· Eureka注册中心负载均衡RibbonHystrixFeignSpring Cloud Gateway网关spring cloud configspring cloud bus

继续阅读