02SpringCloud

Eureka

  • 分为server和client
  • 对于服务发现而言,可用性比数据一致性更加重要——AP 胜过 CP。而 Spring Cloud Netflix 在设计 Eureka 时遵守的就是 AP 原则。
  • 注解@EnableEurekaServer

server

  • 服务的注册中心,负责维护注册的服务列表,同其他服务注册中心一样,支持高可用配置。
  • 配置
    • register-with-eureka: false #false表示不向注册中心注册自己。
      fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务

Eureka的高可用

  • Eureka Server 可以运行多个实例来构建集群,解决单点问题,但不同于 ZooKeeper 的选举 leader 的过程,Eureka Server 采用的是 Peer to Peer 对等通信。这是一种去中心化的架构,无 master/slave 区分,每一个 Peer 都是对等的。在这种架构中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点。每个节点都可被视为其他节点的副本。
  • 如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点,当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会进行replicateToPeer(节点间复制)操作,将请求复制到其他 Eureka Server 当前所知的所有节点中。
  • 一个新的 Eureka Server 节点启动后,会首先尝试从邻近节点获取所有实例注册表信息,完成初始化。Eureka Server 通过getEurekaServiceUrls()方法获取所有的节点,并且会通过心跳续约的方式定期更新。默认配置下,如果 Eureka Server 在一定时间内没有接收到某个服务实例的心跳,Eureka Server 将会注销该实例(默认为 90 秒,通过eureka.instance.lease-expiration-duration-in-seconds配置)。当 Eureka Server 节点在短时间内丢失过多的心跳时(比如发生了网络分区故障),那么这个节点就会进入自我保护模式。

自我保护模式

  • 默认配置下,如果 Eureka Server 每分钟收到心跳续约的数量低于一个阈值,并且持续 15 分钟,就会触发自我保护。
    • 阈值=instance的数量 × (60 / instance的心跳间隔秒数) × 自我保护系数
  • 在自我保护模式中,Eureka Server 会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该 Eureka Server 节点就会自动退出自我保护模式。它的设计哲学前面提到过,那就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。这样做会使客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况。因此客户端要有容错机制,比如请求重试、断路器。该模式可以通过eureka.server.enable-self-preservation = false来禁用,

client

服务提供者

  • 服务提供方,作为一个 Eureka Client,向 Eureka Server 做服务注册、续约和下线等操作,注册的主要数据包括服务名、机器 ip、端口号、域名等等。
  • Eureka Server 会维护一个已注册服务的列表,这个列表为一个嵌套的 HashMap:
    • private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
              = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
        + 第一层,application name 和对应的服务实例。
      + 第二层,服务实例及其对应的注册信息,包括 IP,端口号等。
      #### 续约与剔除
      + 服务实例启动后,会周期性地向 Eureka Server 发送心跳以续约自己的信息,避免自己的注册信息被剔除。续约的方式与服务注册基本一致:首先更新自身状态,再同步到其它 Peer。
      ### 服务消费者
      + 服务消费方,作为一个 Eureka Client,向 Eureka Server 获取 Service Provider 的注册信息,并通过远程调用与 Service Provider 进行通信。
      + 它启动后,会从 Eureka Server 上获取所有实例的注册信息,包括 IP 地址、端口等,并缓存到本地。这些信息默认每 30 秒更新一次。前文提到过,如果与 Eureka Server 通信中断,Service Consumer 仍然可以通过本地缓存与 Service Provider 通信。
      + ,服务端的更改可能需要 2 分钟才能传播到所有客户端。这是因为 Eureka 有三处缓存和一处延迟造成的。
      + Eureka Server 对注册列表进行缓存,默认时间为 30s。
      + Eureka Client 对获取到的注册信息进行缓存,默认时间为 30s。
      + Ribbon 会从上面提到的 Eureka Client 获取服务列表,将负载均衡后的结果缓存 30s
      + 如果不是在 Spring Cloud 环境下使用这些组件 (Eureka, Ribbon),服务启动后并不会马上向 Eureka 注册,而是需要等到第一次发送心跳请求时才会注册。心跳请求的发送间隔默认是 30s。Spring Cloud 对此做了修改,服务启动后会马上注册。
      ## Eureka配置
      + ```yaml
      spring:
      application:
      name: eureka-server
      server:
      port: 7000
      eureka:
      instance:
      hostname: localhost
      client:
      register-with-eureka: false
      fetch-registry: false
      service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • eureka.client.fetch-registry:表示是否从 Eureka Server 获取注册信息,默认为 true。
  • eureka.client.register-with-eureka:表示是否将自己注册到 Eureka Server,默认为 true。
  • eureka.client.service-url.defaultZone:设置与 Eureka Server 交互的地址,查询服务和注册服务都需要依赖这个地址。默认是 http://localhost:8761/eureka ;多个地址可使用英文逗号(,)分隔

Ribbon

  • 为 REST 客户端实现负载均衡。它主要包括六个组件
  • 使用@LoadBalanced注解RestTemplate@Bean方法,直接用RestTemplate调用服务

六大组件

ServerList

  • 负载均衡使用的服务器列表。这个列表会缓存在负载均衡器中,并定期更新。当 Ribbon 与 Eureka 结合使用时,ServerList 的实现类就是 DiscoveryEnabledNIWSServerList,它会保存 Eureka Server 中注册的服务实例表。

ServerListFilter

  • 服务器列表过滤器。这是一个接口,主要用于对 Service Consumer 获取到的服务器列表进行预过滤,过滤的结果也是 ServerList。Ribbon 提供了多种过滤器的实现。

IPing

  • 探测服务实例是否存活的策略

IRule

  • 负载均衡策略,其实现类表述的策略包括:轮询、随机、根据响应时间加权等

ILoadBalancer

  • 负载均衡器。这也是一个接口,Ribbon 为其提供了多个实现,比如 ZoneAwareLoadBalancer。而上层代码通过调用其 API 进行服务调用的负载均衡选择。一般 ILoadBalancer 的实现类中会引用一个 IRule
  • 需要注入RestTemplate和LoadBalancerClient
  •  ServiceInstance instance = client.choose("eureka-producer");
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    ### RestClient
    + 服务调用器。顾名思义,这就是负载均衡后,Ribbon 向 Service Provider 发起 REST 请求的工具。
    ## 工作流程
    + 优先选择在同一个 Zone 且负载较少的 Eureka Server;
    + 定期从 Eureka 更新并过滤服务实例列表;
    + 根据用户指定的策略,在从 Server 取到的服务注册列表中选择一个实例的地址;
    + 通过 RestClient 进行服务调用。
    # Hystrix
    ## 雪崩效应
    + 在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因 “服务提供者” 的不可用导致 “服务消费者” 的不可用, 并将不可用逐渐放大的过程。
    ### 雪崩的可能原因
    + 服务提供者不可用
    + 硬件故障
    + 程序bug
    + 缓存击穿
    + 用户大量请求
    + 重试夹大流量
    + 用户重试
    + 代码逻辑重试
    + 服务调用者不可用
    + 同时等待造成的资源耗尽
    ### 应对策略
    + 流量控制
    + 网关限流
    + 因为 Nginx 的高性能,目前一线互联网公司大量采用 Nginx+Lua 的网关进行流量控制,由此而来的 OpenResty 也越来越热门。
    + 用户交互限流
    + 用户交互限流的具体措施有: 1. 采用加载动画,提高用户的忍耐等待时间。2. 提交按钮添加强制等待时间机制。
    + 关闭重试
    + 改进缓存模式
    + 缓存预加载
    + 同步改为异步刷新
    + 服务自动扩容
    + 服务调用者降级服务
    + 资源隔离
    + 对调用服务的线程池进行隔离
    + 对依赖服务进行分类
    + 强依赖和若依赖。强依赖服务不可用会导致当前业务中止,而弱依赖服务的不可用不会导致当前业务的中止。
    + 不可用服务调用快速失败
    + 一般通过 **超时机制**, **熔断器** 和熔断后的 **降级方法** 来实现
    ## 使用Hystirx预防雪崩
    ### 服务降级(FallBack)
    + 对于查询操作,我们可以实现一个 fallback 方法,当请求后端服务出现异常的时候,可以使用 fallback 方法返回的值。fallback 方法的返回值一般是设置的默认值或者来自缓存。
    ### 资源隔离
    + 在 Hystrix 中,主要通过线程池来实现资源隔离。通常在使用的时候我们会根据调用的远程服务划分出多个线程池。例如调用产品服务的 Command 放入 A 线程池,调用账户服务的 Command 放入 B 线程池。这样做的主要优点是运行环境被隔离开了。这样就算调用服务的代码存在 bug 或者由于其他原因导致自己所在线程池被耗尽时,不会对系统的其他服务造成影响。
    + 优势
    + 应用自身得到完全的保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的额其余部分。
    + 可以有效的降低接入新服务的风险。如果新服务接入后运行不稳定或存在问题,完全不会影响到应用其他的请求。
    + 当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下容器级别的清理恢复速度要慢得多。
    + 当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。同时,我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过 Spring Cloud Config 与 Spring Cloud Bus 的联合使用来介绍)来处理它。
    + 当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,此时线程池的监控指标信息会反映出这样的变化。同时,我们也可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。
    + 除了上面通过线程池隔离服务发挥的优点之外,每个专有线程池都提供了内置的并发实现,可以利用它为同步的依赖服务构建异步的访问。
    #### 信号量
    + Hystrix 中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下才使用信号量。在 HystrixCommand 和 HystrixObservableCommand 中 2 处支持信号量的使用:
    + 命令执行:如果隔离策略参数 execution.isolation.strategy 设置为 SEMAPHORE,Hystrix 会使用信号量替代线程池来控制依赖服务的并发控制。
    + 降级逻辑:当 Hystrix 尝试降级逻辑时候,它会在调用线程中使用信号量。
    + 信号量的默认值为 10,我们也可以通过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算类似。仅访问内存数据的请求一般耗时在 1ms 以内,性能可以达到 5000rps,这样级别的请求我们可以将信号量设置为 1 或者 2,我们可以按此标准并根据实际请求耗时来设置信号量。
    ### 断路器模式
    + 当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),直接切断原来的主逻辑调用。但是,在 Hystrix 中的断路器除了切断主逻辑的功能之外,还有更复杂的逻辑,
    + 当 Hystrix Command 请求后端服务失败数量超过一定阈值,断路器会切换到开路状态 (Open)。这时所有请求会直接失败而不会发送到后端服务。
    + 阈值的参数
    + 快照时间窗
    + 断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒。
    + 请求总数下限
    + 在快照时间窗内,必须满足请求总数下限才有资格进行熔断。默认为 20,意味着在 10 秒内,如果该 Hystrix Command 的调用此时不足 20 次,即时所有的请求都超时或其他原因失败,断路器都不会打开
    + 错误百分比下限
    + 当请求总数在快照时间窗内超过了下限,比如发生了 30 次调用,如果在这 30 次调用中,有 16 次发生了超时异常,也就是超过 50% 的错误百分比,在默认设定 50% 下限情况下,这时候就会将断路器打开
    + 断路器保持在开路状态一段时间后 (默认 5 秒),自动切换到半开路状态 (HALF-OPEN)。这时会判断下一次请求的返回情况,如果请求成功,断路器切回闭路状态 (CLOSED),否则重新切换到开路状态 (OPEN)。
    ## 监控
    + 在 Spring Boot 的启动类上面引入注解`@EnableHystrixDashboard`,启用 Hystrix Dashboard 功能

    + <img src="https://xy-note-pic.oss-cn-beijing.aliyuncs.com/hystrix.jpg" alt="hystrix" style="zoom:80%;" />

    + Delay:控制服务器上轮询监控信息的延迟时间,默认为 2000 毫秒,可以通过配置该属性来降低客户端的网络和 CPU 消耗。
    + Title:该参数可以展示合适的标题。
    ### 三种监控方式
    + 默认的集群监控:通过 URL:`http://turbine-hostname:port/turbine.stream` 开启,实现对默认集群的监控
    + 指定的集群监控:通过 URL:`http://turbine-hostname:port/turbine.stream?cluster=[clusterName]` 开启,实现对 clusterName 集群的监控。
    + 单体应用的监控: ~~通过 URL:`http://hystrix-app:port/hystrix.stream` 开启~~ ,实现对具体某个服务实例的监控。**(现在这里的 URL 应该为 `http://hystrix-app:port/actuator/hystrix.stream`,Actuator 2.x 以后 endpoints 全部在`/actuator`下,可以通过`management.endpoints.web.base-path`修改)**
    + **注意:**前两者都对集群的监控,需要整合 Turbine 才能实现
    ## Hystrix使用
    + ```yaml
    //配置中加入此项,表示开始使用hystrix
    feign:
    hystrix:
    enabled: true
  • 被监控实例中为启动类添加@EnableCircuitBreaker@EnableHystrix注解,开启断路器功能
  • 被监控实例的配置文件
    • management:
        endpoints:
          web:
            exposure:
              include: hystrix.stream
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      ### 监控界面解读
      + https://www.haoyizebo.com/posts/7f8b5ef9/
      + https://www.haoyizebo.com/posts/56139c3d/
      ### 创建回调类
      + ```java
      @Component
      public class HelloRemoteHystrix implements HelloRemote {

      @Override
      public String hello(@RequestParam(value = "name") String name) {
      return "Hello World!";
      }

      }

添加fallback属性

  • HelloRemote类添加指定 fallback 类,在服务熔断的时候返回 fallback 类中的内容
  • @FeignClient(name = "eureka-producer", fallback = HelloRemoteHystrix.class)
    public interface HelloRemote {
    
        @GetMapping("/hello/")
        String hello(@RequestParam(value = "name") String name);
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Feign
    + Feign 是一个声明式的 Web Service 客户端,它的目的就是让 Web Service 调用更加简单。它整合了 RibbonHystrix,从而让我们不再需要显式地使用这两个组件。Feign 还提供了 HTTP 请求的模板,通过编写简单的接口和插入注解,我们就可以定义好 HTTP 请求的参数、格式、地址等信息。接下来,Feign 会完全代理 HTTP 的请求,我们只需要像调用方法一样调用它就可以完成服务请求。
    ## 应用
    + 启动类加上@EnableFeignClients
    + 创建接口类
    + ```java
    @FeignClient(name = "eureka-producer") //调用的服务名称
    public interface HelloRemote {

    @GetMapping("/hello/")
    String hello(@RequestParam(value = "name") String name);

    }
  • 在Controller注入HelloRrmote 接口直接调用方法

Gateway

  • 为什么需要网关
    • 简化客户端调用复杂度
      • 在微服务架构模式下后端服务的实例数一般是动态的,对于客户端而言很难发现动态改变的服务实例的访问地址信息。因此在基于微服务的项目中为了简化前端的调用逻辑,通常会引入 API Gateway 作为轻量级网关,同时 API Gateway 中也会实现相关的认证逻辑从而简化内部服务之间相互调用的复杂度。
    • 数据裁剪以及聚合
      • 通常而言不同的客户端对于显示时对于数据的需求是不一致的,比如手机端或者 Web 端又或者在低延迟的网络环境或者高延迟的网络环境。
        因此为了优化客户端的使用体验,API Gateway 可以对通用性的响应数据进行裁剪以适应不同客户端的使用需求。同时还可以将多个 API 调用逻辑进行聚合,从而减少客户端的请求数,优化客户端用户体验
    • 多渠道支持
      • 当然我们还可以针对不同的渠道和客户端提供不同的 API Gateway, 对于该模式的使用由另外一个大家熟知的方式叫 Backend for front-end, 在 Backend for front-end 模式当中,我们可以针对不同的客户端分别创建其 BFF
    • 遗留系统的微服务化改造
      • 对于系统而言进行微服务改造通常是由于原有的系统存在或多或少的问题,比如技术债务,代码质量,可维护性,可扩展性等等。API Gateway 的模式同样适用于这一类遗留系统的改造,通过微服务化的改造逐步实现对原有系统中的问题的修复,从而提升对于原有业务响应力的提升。通过引入抽象层,逐步使用新的实现替换旧
        的实现。

Spring Cloud Zuul

  • Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
  • 使用@EnableZuulProxy注解开启Zuul功能

路由

  • 当我们这里构建的api-gateway应用启动并注册到 Eureka 之后,服务网关会发现上面我们启动的两个服务producerconsumer,这时候 Zuul 就会创建两个路由规则。每个路由规则都包含两部分,一部分是外部请求的匹配规则,另一部分是路由的服务 ID。针对当前示例的情况,Zuul 会创建下面的两个路由规则:
    • 转发到producer服务的请求规则为:/producer/**
    • 转发到consumer服务的请求规则为:/consumer/**

过滤

Filter的生命周期

  • Filter 的生命周期有 4 个,分别是“PRE”、“ROUTING”、“POST”和“ERROR”,整个生命周期可以用下图来表示

    • ZuulFilter的生命周期
    • PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

    • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。

    • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

    • ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul 还允许我们创建自定义的过滤器类型。例如,我们可以定制一种 STATIC 类型的过滤器,直接在 Zuul 中生成响应,而不将请求转发到后端的微服务。

Zuul中默认实现的Filter

  • 类型 顺序 过滤器 功能
    pre -3 ServletDetectionFilter 标记处理 Servlet 的类型
    pre -2 Servlet30WrapperFilter 包装 HttpServletRequest 请求
    pre -1 FormBodyWrapperFilter 包装请求体
    route 1 DebugFilter 标记调试标志
    route 5 PreDecorationFilter 处理请求上下文供后续使用
    route 10 RibbonRoutingFilter serviceId 请求转发
    route 100 SimpleHostRoutingFilter url 请求转发
    route 500 SendForwardFilter forward 请求转发
    post 0 SendErrorFilter 处理有错误的请求响应
    post 1000 SendResponseFilter 处理正常的请求响应

禁用指定的Filter

  • 可以在 application.yml 中配置需要禁用的 filter,格式为zuul.<SimpleClassName>.<filterType>.disable=true
    比如要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter就设置
  • zuul:
      SendResponseFilter:
        post:
          disable: true
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    ### 自定义Filter
    + ```java
    public class TokenFilter extends ZuulFilter {

    /**
    * 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
    * 这里定义为pre,代表会在请求被路由之前执行。
    *
    * @return
    */
    @Override
    public String filterType() {
    return "pre";
    }

    /**
    * filter执行顺序,通过数字指定。
    * 数字越大,优先级越低。
    *
    * @return
    */
    @Override
    public int filterOrder() {
    return 0;
    }

    /**
    * 判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。
    * 实际运用中我们可以利用该函数来指定过滤器的有效范围。
    *
    * @return
    */
    @Override
    public boolean shouldFilter() {
    return true;
    }

    /**
    * 过滤器的具体逻辑
    *
    * @return
    */
    @Override
    public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();

    String token = request.getParameter("token");
    if (token == null || token.isEmpty()) {
    ctx.setSendZuulResponse(false);
    ctx.setResponseStatusCode(401);
    ctx.setResponseBody("token is empty");
    }
    return null;
    }
    }
  • 我们通过继承ZuulFilter抽象类并重写了下面的四个方法来实现自定义的过滤器,这四个方法分别定义了:
    • filterType():过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
    • filterOrder():过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。通过数字指定,数字越大,优先级越低。
    • shouldFilter():判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。
    • run():过滤器的具体逻辑。这里我们通过ctx.setSendZuulResponse(false)令 Zuul 过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回 body 内容进行编辑等。
  • 在实现了自定义过滤器之后,它并不会直接生效,我们还需要为其创建具体的 Bean 才能启动该过滤器。

Spring Cloud Gateway

  • 基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
  • 特征
    • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
    • 动态路由
    • Predicates 和 Filters 作用于特定路由
    • 集成 Hystrix 断路器
    • 集成 Spring Cloud DiscoveryClient
    • 易于编写的 Predicates 和 Filters
    • 限流
    • 路径重写

术语

  • **Route(路由)**:这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
  • Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个ServerWebExchangeTTP 请求的任何内容,例如 headers 或参数。
  • Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。

流程

  • SprngCloudGateWay流程
  • 配置

    • spring:
        application:
          name: cloud-gateway
        cloud:
          gateway:
            discovery:
              locator:
                enabled: true
      #      routes:
      #        - id: default_path_to_http
      #          uri: https://haoyizebo.com
      #          order: 10000
      #          predicates:
      #            - Path=/**
      #          filters:
      #            - SetPath=/
      #			 添加head头
      #            - AddResponseHeader=X-Response-Default-Foo, Default-Bar
      server:
        port: 10000
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:7000/eureka/
      logging:
        level:
          org.springframework.cloud.gateway: debug
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      + 配置说明
      + `spring.cloud.gateway.discovery.locator.enabled`:是否与服务注册于发现组件进行结合,通过 serviceId 转发到具体的服务实例。默认为`false`,设为`true`便开启通过服务中心的自动根据 serviceId 创建路由的功能。
      + `spring.cloud.gateway.routes`用于配合具体的路由规则,是一个数组。这里我创建了一个 id 为`default_path_to_http`的路由,其中的配置是将未匹配的请求转发到`https://haoyizebo.com`。实际上开启了服务发现后,如果只使用默认创建的路由规则,这个 routes 不配置也是可以的,所以我就先注释掉了不用它了。
      # Config
      + Spring Cloud Config用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为**服务端****客户端**两个部分。其中服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置仓库并为客户端提供获取配置信息、加密 / 解密信息等访问接口;而客户端则是微服务架构中的各个微服务应用或基础设施,它们通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。
      + Spring Cloud Config 实现了对服务端和客户端中环境变量和属性配置的抽象映射,所以它除了适用于 Spring 构建的应用程序之外,也可以在任何其他语言运行的应用程序中使用。由于 Spring Cloud Config 实现的配置中心默认采用 Git 来存储配置信息,所以使用 Spring Cloud Config 构建的配置服务器,天然就支持对微服务应用配置信息的版本管理,并且可以通过 Git 客户端工具来方便的管理和访问配置内容。当然它也提供了对其他存储方式的支持,比如:SVN 仓库、本地化文件系统
      ## 基于git
      ### 准备工作
      + 准备一个 Git 仓库,在 Github 上面创建了一个文件夹 config-repo 用来存放配置文件,为了模拟生产环境,我们创建以下三个配置文件:
      ```sh
      // 开发环境
      config-client-dev.yml
      // 测试环境
      config-client-test.yml
      // 生产环境
      config-client-prod.yml
      每个配置文件中都写一个属性 neo.hello, 属性值分别是 dev/test/prod。下面我们开始配置 Server 端。

Server端

  • 创建一个基础的 Spring Boot 工程,命名为:config-server-git

  • 添加依赖

    • <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-config-server</artifactId>
      </dependency>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      + 配置文件:在 application.yml 中添加配置服务的基本信息以及 Git 仓库的相关信息
      + ```yaml
      spring:
      application:
      name: config-server
      cloud:
      config:
      server:
      git:
      uri: https://github.com/X-coder/spring-cloud-study # 配置git仓库的地址
      search-paths: config-repo # git仓库地址下的相对地址,可以配置多个,用,分割。
      username:git帐号
      passowrd:git密码
      server:
      port: 12000
  • 启动类

    • 启动类添加@EnableConfigServer,激活对配置中心的支持

Client端

  • 与 Spring Cloud Config 相关的属性必须配置在 bootstrap.yml 中,config 部分内容才能被正确加载。因为 config 的相关配置会先于 application.yml,而 bootstrap.yml 的加载也是先于 application.yml。
  • bootstrap.yml
    • spring:
        cloud:
          config:
            uri: http://localhost:12000 # 配置中心的具体地址,即 config-server
            name: config-client # 对应 {application} 部分
            profile: dev # 对应 {profile} 部分
            label: master # 对应 {label} 部分,即 Git 的分支。如果配置中心使用的是本地存储,则该参数无用
      
      1
      2
      3
      4
      5
      6
      7
      8
      ### Refresh
      + 客户端可以从服务端 REST 接口获取配置。但客户端并不能主动感知到配置的变化,从而主动去获取新的配置。客户端如何去主动获取新的配置信息呢,Spring Cloud 已经给我们提供了解决方案,每个客户端通过 POST 方法触发各自的 `/actuator/refresh`
      + 添加依赖
      + ```xml
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
  • 需要给加载变量的类上面加载@RefreshScope,在客户端执行/actuator/refresh的时候就会更新此类下面的变量值
    • @RestController
      @RefreshScope
      public class HelloController {
      
          @Value("${info.profile:error}")
          private String profile;
      
          @GetMapping("/info")
          public Mono<String> hello() {
              return Mono.justOrEmpty(profile);
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      + 在 application.yml 中添加配置暴露Endpoint 
      + ```yaml
      management:
      endpoints:
      web:
      exposure:
      include: refresh

Webhook

  • 只要提交代码就自动调用客户端来更新,Webhook 是当某个事件发生时,通过发送 HTTP POST 请求的方式来通知信息接收方。Webhook 来监测你在 Github.com 上的各种事件,最常见的莫过于 push 事件。如果你设置了一个监测 push 事件的 Webhook,那么每当你的这个项目有了任何提交,这个 Webhook 都会被触发,这时 Github 就会发送一个 HTTP POST 请求到你配置好的地址

  • SpringCloudConfigWebhook

  • Payload URL :触发后回调的 URL

  • Content type :数据格式,两种一般使用 json

  • Secret :用作给 POST 的 body 加密的字符串。采用 HMAC 算法

  • events :触发的事件列表。

  • events 事件类型 描述
    push 仓库有 push 时触发。默认事件
    create 当有分支或标签被创建时触发
    delete 当有分支或标签被删除时触发
    这样我们就可以利用 hook 的机制去触发客户端的更新,但是当客户端越来越多的时候,hook 机制也不够优雅了,另外每次增加客户端都需要改动 hook 也是不现实的。其实,Spring Cloud 给了我们更好解决方案——Spring Cloud Bus。后续我们将继续学习如何通过 Spring Cloud Bus 来实现以消息总线的方式进行通知配置信息的变化,完成集群上的自动化更新

高可用

  • 将所有的 Config Server 都指向同一个 Git 仓库,这样所有的配置内容就通过统一的共享文件系统来维护,而客户端在指定 Config Server 位置时,只要配置 Config Server 外的均衡负载即可
  • 注册为服务
    • 把 config-server 也注册为服务,这样所有客户端就能以服务的方式进行访问。通过这种方法,只需要启动多个指向同一 Git 仓库位置的 config-server 就能实现高可用了。
  • https://www.haoyizebo.com/posts/6cea3e14/

Spring Cloud Bus


02SpringCloud
https://x-leonidas.github.io/2022/02/01/11技术栈/spring/02SpringCloud/
作者
听风
发布于
2022年2月1日
更新于
2025年6月26日
许可协议