Spring Cloud - 笔记

服务发现和注册

Eureka

Eureka的拓扑结构如下图:

Eureka拓扑

Eureka有以下几个特点:

  • 服务器的自我保护机制。客户端通过“心跳”向Eureka集群保证自己的可用性,这个“心跳”的 间隔默认为90s,但实际上即使超过90s没有心跳发送Eureka服务器也不会立刻将该客户端从服务 列表信息中删除。这样设计的出发点是出于对网络故障导致的心跳发送失败的考虑,也是Eureka 优先保证可用性的体现。
  • 服务器优先保证可用性。Eureka集群不存在类似Leader节点的概念,所有的节点都是平等的。 这使得Eureka集群不会由于Leader的选举导致整体不可用,并且当客户端与其中部分的节点之间 存在网络故障时,可以从其他的节点中获得服务信息。
  • 客户端会缓存节点信息。当客户端首次从Eureka服务器得到服务列表时,会将之缓存在本地内存 当中。因此,即使在最为极端的情况下客户端和集群中所有的Eureka服务器都发生网络故障,依然 存在不会导致服务调用的失败的可能。

Eureka的设计原则是优先保证可用性,平等的节点保证了客户端可以向任意的服务器进行注册和 心跳的发送,自我保护机制使得客户端可以最大程度得到“可能可用”的服务列表。

Zookeeper

Zookeeper的拓扑结构如下图:

Zookeeper服务注册中心拓扑

Zookeeper提供分布式协同服务,也能作为分布式的服务注册中心使用。而Zookeeper作为服务注册 中心则有一下特点:

  • Leader选举产生集群整体的不可用。当Zookeeper集群中Leader节点故障时,即使客户端与其他 节点能够正常通信也无法获得任何已被注册服务的信息。
  • 临时节点在客户端和服务器的会话结束时会立刻被删除。因此产生网络分区导致心跳无法发送时, 服务的信息会立刻从Zookeeper中被删除。

由于Zookeeper优先一致性的设计原则,在作为服务注册中心时容易产生由网络分区产生的服务 不可用的情况,因此,有观点提出Zookeeper或许并不适合作为服务注册中心使用。

服务调用

Ribbon

Ribbon能够将服务名通过服务发现转为实际的ip:port形式,并且会在转换的过程中使用特定 的负载均衡算法决定调用的服务器。

Ribbon的负载均衡算法有:

  1. 轮循策略
  2. 随机策略
  3. 权重策略。根据响应时间决定调用的服务器
  4. 最小连接数策略。
  5. 重试策略
  6. 可用性敏感策略
  7. 区域敏感策略

也可以通过实现IRule接口的方式自定义负载均衡算法。配置负载均衡算法的方式有2种:

  1. 全局配置。将IRule的Bean定义的配置类放置于主启动方法可以扫描的路径下
  2. 指定特定服务的负载均衡算法。将IRule的Bean定义的配置类放置在主启动方法无法扫描 的路径下,并通过@RibbonClient指定服务名以及IRule所在的配置类

Spring Cloud Ribbon集成方式:

  1. 引入依赖spring-cloud-starter-netflix-ribbon(实际Eureka本身就会引入Ribbon的依赖)
  2. 定义RestTemplate的Bean,并使用@LoadBalanced修饰定义方法
  3. 使用http://{service-name}的方式调用服务接口

OpenFeign

OpenFeign用于实现声明市的HTTP Client,相较于Ribbon中直接使用RestTemplate而言 更加方便。

Spring Cloud OpenFeign集成方式:

  1. 引入依赖spring-cloud-starter-openfeign
  2. 开启声明式HTTP客户端@EnableFeignClients
  3. 定义接口并使用@FeignClient修饰
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping("/payment/get/{id}")
    Result<Payment> getPaymentById(@PathVariable("id") Long id);

    @PostMapping("/payment/create")
    Result<?> create(@RequestBody Payment payment);
}

断路器

Hystrix

Hystrix的基本原理如下图所示:

Hystrix线程隔离

Hystrix的基本原理是通过将需要隔离的逻辑放入指定的线程池中运行,从而达到隔离的目的。 举个例子,假设a()方法被@HystrixCommand注解修饰,那么在b()方法中调用a()时, 就会通过特别为a()准备的线程池执行a()的逻辑,这样b()的线程就能够监控a()的 执行结果,无论是超时异常等情况都能够被检测并纳入统计。

Spring Cloud中Hystrix的集成方式:

  1. 引入依赖spring-cloud-starter-netflix-hystrix
  2. 开启断路器@EnableCircuitBreaker
  3. 使用@HystrixCommand修饰需要监控的方法

统一网关

Gateway

Gateway拓扑

统一网关位处于外部客户端以及内部系统之间,它的主要作用有:

  1. 统一鉴权。对客户端传入的请求的参数进行鉴权处理,如果不通过则直接返回失败
  2. 统一监控。对所有访问内网的请求进行监控和记录等操作
  3. 请求路由。将客户端提供的URL转为内部服务器调用的URL

Gateway工作原理

Gateway类似于Eureka Server是一个单独的服务程序,不需要写业务相关代码,但需要配置 相关的路由规则以及请求的过滤器。为此,Gateway提供了3个主要概念:

  1. 断言。判断客户端Request是否能被某个路由规则转发
  2. 路由。通过客户端提供的URL计算出内部可以调用的服务URL
  3. 过滤器。处理客户端提供的Reuqest以及内部服务返回的Response,可以用于日志记录以及 统一鉴权

Gateway的启用方式:

  1. 引入依赖spring-cloud-starter-gateway
  2. 按需求实现GlobalFilter接口引入自定义的过滤器
  3. 配置相应的路由规则,示例如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery: # 开启服务发现与注册
        locator:
          enabled: true
      routes:
        - id: payment_route
          uri: lb://cloud-payment-service # lb://{service-name} 表示通过服务发现与注册进行转发
          predicates:
            - Path=/payment/get/**

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

Gateway作为统一网关有以下特点:

  1. 使用Java语言编写,因此与Spring生态统一使用学习成本相对较低
  2. 提供的机制能够实现复杂的网关功能
  3. 性能而言,比Nginx低,但使用了高性能网络框架Netty,因此性能上要高于Zuul
  4. 默认的工作方式是代码/配置的硬编码的方式,而不是通过Web页面的配置方式,因此更新路由 规则需要重启服务

统一配置

Config + Bus

手动更新配置

Config的主要思想是通过git管理配置文件的版本,而Config服务器能够通过HTTP URL的方式 访问存在于git服务器的特定分支的配置文件。

  1. 引入依赖spring-cloud-config-server
  2. 使用@EnableConfigServer注解开启配置服务器
  3. 通过配置文件指定远程配置文件所在的git仓库地址
1
2
3
4
5
6
spring:
  cloud:
    config:
      server:
        git:
          uri: git@github.com.....

可通过REST风格的接口直接从Config服务器获取到git上的配置文件,获取的URL按照特定 的规则有3种:

  1. /{label}/{name}-{profile}.yml
  2. /{name}-{profile}.yml
  3. /{name}/{profile}/{label}

对于想要引入可刷新配置的服务提供者,需要执行以下步骤:

  1. 引入依赖spring-cloud-starter-config
  2. 在引入可刷新依赖的Bean的类上使用@RefreshScope修饰
  3. 服务的配置文件名称需要改为bootstrap.yml(可能是父子容器),并进行以下配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
spring:
  cloud:
    config:
      label: {branch}
      name: {config file name}
      profile: {profile}
      uri: {config server url}
management:
  endpoints:
    web:
      exposure:
        include: "*"

通过POST调用需要刷新配置的服务提供者的http://ip:port/actuator/refresh接口

自动刷新配置

  1. 引入依赖spring-cloud-starter-bus-amqp
  2. 在配置文件中新增RabbitMQ配置并暴露bus-refresh接口
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  
manangement:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'

客户端需要进行以下配置:

  1. 引入spring-cloud-starter-bus-amqp
  2. 配置文件bootstrap.yml中新增相应的RabbitMQ配置
1
2
3
4
5
6
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

刷新配置的方式均为调用Config服务器的特定接口:

  • 全局刷新POST http://host:port/actuator/bus-refresh
  • 定点刷新POST http://host:port/actuator/bus-refresh/{application}:{port}

消息驱动

Alibaba

Nacos

Nacos能够同时提供服务注册以及统一配置中心的功能,Nacos集群官方推荐的拓扑是由虚拟IP 以及负载均衡的服务器作为统一入口的,这么做的好处在于Nacos集群的拓展可以不更改客户端 的配置信息。其拓扑如下:

Nacos拓扑

Nacos由官方提供专门的服务器二进制包,运行之后可以通过页面进行所有功能的配置。

Nacos Discovery

  1. 引入依赖spring-cloud-starter-nacos-discovery
  2. 使用@EnableDiscoveryClient开启服务发现客户端
  3. 在配置文件中配置Nacos服务器的地址
1
2
3
4
5
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

Nacos作为服务注册中心有以下特点:

  1. 支持Ribbon
  2. AP和CP可切换

Nacos Config

  1. 引入依赖spring-cloud-starter-nacos-config
  2. 在配置文件中引入Nacos相关服务器的配置
1
2
3
4
5
6
7
spring:
  cloud:
    nacos:
      config:
        server-addr: localhost:1234 # Nacos服务器的访问地址
        prefix: config
        file-extension: yaml

Nacos通过使用(namespace, group, dataId)坐标唯一确定一个配置文件,在实际开发中 可以通过命名空间(namespace)以及分组(group)进行分割。其中, dataId是由多个部分拼合而成,规则为{prefix}-{spring-profiles-active}.{file-extension}, 规则中所有的字段都需要与配置文件一致。

Sentinel

Sentinel拓扑

同样是服务的熔断与降级,Sentinel采用的方案为动态配置,也就是通过页面对服务中 的各个资源进行降级/限流/熔断的规则配置,并通过其他服务(如Nacos)保存配置的规则。

Sentinel的集成方法:

  1. 引入依赖spring-cloud-starter-alibaba-sentinel
  2. 在配置文件中引入以下配置
1
2
3
4
5
6
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080 # Sentinel服务器的控制台URI
        port: 8719 # 客户端开启的监听端口,用于Sentinel服务器配置相应的规则

Sentinel的@SentinelResource提供了对规则异常的处理以及对普通运行时异常的处理。这里 有一个问题,即对普通运行时异常的处理和Spring本身的@RestControllerAdvisor之间的冲突。 实验结果为:

  1. 使用@RestControllerAdvisor能够触发异常的降级策略
  2. 同时使用@SentinelResourcefallback属性与@RestControllerAdvisor优先触发前者

由此,可以使用@RestControllerAdvisor对整个应用的异常进行统一处理,并结合fallback 对需要特殊处理方式的方法进行额外处理。

Sentinel也能够和Hystrix一样开启Feign的服务降级,开启方法也是在feign的配置中开启相应 的降级。

1
2
3
feign:
  sentinel:
    enabled: true

使用方式与Hystrix的降级一致,即通过实现@FeignClient修饰的接口。

Sentinel也提供了持久化方案,也就是能够将在Dashboard中进行的所有配置保存在其他的服务器 中。在使用Spring Cloud Alibaba时,最为推荐的是使用同为Alibaba套件的Nacos,开启步骤如下:

  1. 引入依赖sentinel-datasource-nacos
  2. spring.cloud.sentinel配置中新增以下内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  cloud:
    sentinel:
      ds1:
        nacos:
          server-addr: localhost:8848 # Nacos的服务器地址
          dataId: ${spring.application.name}
          groupId: DEFAULT_GROUP
          data-type: json
          rule-type: flow

Seata

Seata解决方案

在图中,存在以下几个概念:

  1. TC. 事务协调器
  2. TM. 事务管理器
  3. RM. 资源管理器

对于整个分布式事务而言,首先需要事务的开启者通过TMTC注册一个全局事务,并 从TC得到一个全局事务ID,这个ID将在整个调用链的上下文中传递。而涉及到几个 分支事务,则分别携带全局事务ID向TC注册自己,在完成自己相应的操作之后告知TC 可以提交。最终,由TC控制并决定提交/回滚事务。当然,这只是大致流程,实际上通过 TC控制全部事务本身就十分困难。

Seata的集成方法:

  1. 引入依赖seata-all
  2. 通过@GlobalTransactional的注解修饰需要开启分布式事务的方法

总结

本文是Spring Cloud的一些凌乱介绍,来自于我个人在学习网课时的一些记录,仅仅只涉及 了一部分粗浅的内容。

Spring Cloud的组件也代表了目前一般的微服务所需要的基本功能,包括了服务发现/注册、 服务调用、服务熔断、统一配置、统一网关和分布式事务。

服务发现/注册、统一配置和分布式事务需要中心服务器进行协调,而为了保证高可用性, 中心服务器往往是由集群所构成。因此,学习是需要考虑2点:

  1. 客户端和服务器之间的通信,这部分代表了组件功能的设计思路
  2. 集群内部各节点的构成方案,这部分关系到集群内部的请求处理逻辑,也关系到了集群 的可用性与一致性问题

服务调用与服务熔断则更加偏向编码,因此学习时更加偏向于如何使用以及如何拓展,例如 Ribbon中的IRule路由规则以及服务熔断的中的降级规则等。

Built with Hugo
主题 StackJimmy 设计