整合--Spring Cloud Netflix

195 阅读15分钟

Spring Cloud是什么?

Spring Cloud 是微服务系统架构的一站式解决方案,使我们能在 Spring Boot 的基础上 服务发现注册 、配置中心 、消息总线 、负载均衡 、断路器 、数据监控 等操作,而 Spring Cloud 为我们提供了一套简易的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建。

Spring Cloud核心组件

  • 服务发现 Netflix Eureka
  • 客服端负载均衡 Netflix Ribbon
  • 服务调用 Open Feign
  • 断路器 Netflix Hystrix
  • 服务网关 Netflix Zuul
  • 分布式配置 Spring Cloud Config

注册中心、服务发现 Netflix Eureka

简介

Eureka是基于REST(代表性状态转移)的服务,主要在 AWS 云中用于定位服务,以实现负载均衡和中间层服务器的故障转移。我们称此服务为Eureka服务器。Eureka 还带有一个基于 Java 的客户端组件 Eureka Client,它使与服务的交互变得更加容易。客户端还具有一个内置的负载平衡器,可以执行基本的循环负载平衡。在 Netflix,更复杂的负载均衡器将 Eureka 包装起来,以基于流量,资源使用,错误条件等多种因素提供加权负载均衡,以提供出色的弹性。

服务发现与注册原理

总的来说,Eureka提供服务注册、服务续约、获取注册列表信息、服务下线、服务剔除功能。下面挨个解释

服务注册 Register

当 Eureka 客户端向 Eureka Server 注册时,它提供自身的元数据,比如 IP 地址、端口,运行状况指示符 URL,主页等。

服务续约 Renew

Eureka 客户端会每隔 30 秒(默认eureka.instance.lease-renewal-interval-in-seconds=30)发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka 客户端仍然存在,没有出现问题。 正常情况下,如果 Eureka Server 在 90 秒(默认eureka.instance.lease-expiration-duration-in-seconds=90)没有收到 Eureka 客户端的续约,它会将实例从其注册表中删除。

【提问】Eureka 的自我保护机制

在某种特定情况下 Eureka Server 不会剔除其注册列表中的实例,那就是 Eureka 的自我保护时期。

比如说:当一个 server 节点出现了网络分区等不可抗力原因,那么它会因此收不到 client 的续约心跳,如果网络波动比较大,也就可能导致 server 因为一次网络波动剔除了所有或者绝大部分 Client 。

所以 Eureka 会有一种自我保护机制,默认是15分钟内收到的续约低于原来的85%(配置项:eureka.server.renewalPercentThreshold: 0.85)那么就会开启 自我保护 。这阶段 Eureka Server 即使过了 90秒 也不会剔除其列表中的实例。

获取注册列表信息 Fetch Registries

Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每 30 秒钟)更新一次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。 Eureka 服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka 客户端和 Eureka 服务器可以使用 JSON / XML 格式进行通讯。在默认的情况下 Eureka 客户端使用压缩 JSON 格式来获取注册列表的信息。

服务下线 Cancel

Eureka 客户端在程序关闭时向 Eureka 服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();

服务剔除 Eviction

在默认的情况下,当 Eureka 客户端连续 90 秒(3 个续约周期)没有向 Eureka 服务器发送服务续约,即心跳,Eureka 服务器会将该服务实例从服务注册列表删除。

【提问】Eureka 与 Zookeeper 对比

  • Eureka: 符合AP原则 为了保证了可用性,Eureka 不会等待集群所有节点都已同步信息完成,它会无时无刻提供服务。
  • Zookeeper: 符合CP原则 为了保证一致性,在所有节点同步完成之前是阻塞状态的。

客服端负载均衡 Netflix Ribbon

简介

Ribbon是一个客户端负载均衡器。Ribbon 是先在客户端进行负载均衡再进行请求的。

Ribbon初始化原理

  1. 第一步:SpringBoot 加载 Ribbon 的自动配置类(LoadBalancerAutoConfiguration),去初始化 Ribbon。
  2. 第二步:Ribbon 初始化时会收集 @LoadBalanced 注解的 RestTemplate 和 AsyncRestTemplate ,把它们放到一个 List 里面。
  3. 第三步:然后 Ribbon 里面的 RestTemplateCustomizer 会给每个 RestTemplate 加上了拦截器(LoadBalancerInterceptor)。
  4. 第四步:从 Eureka 注册中心获取服务列表,然后存到 Ribbon 中。
  5. 第五步:加载 YMAL 配置文件,配置好负载均衡配置,创建一个 ILoadbalancer 实例。

核心原理(负载过程)

  1. 第一步:Ribbon 拦截所有标注 @loadBalance 注解的 RestTemplate。
  2. 第二步:将 Ribbon 默认的拦截器 LoadBalancerInterceptor 添加到 RestTemplate 的执行逻辑中,当 RestTemplate 每次发送 HTTP 请求时,都会被 Ribbon 拦截。
  3. 拦截后:Ribbon 会创建一个 ILoadBalancer 实例。
  4. ILoadBalancer 实例会使用 RibbonClientConfiguration 完成自动配置。就会配置好 IRule(负载均衡策略抽象),IPing(IPing用来检测Server是否可用),ServerList。
  5. Ribbon 会从服务列表中选择一个服务,将请求转发给这个服务。

Ribbon支持的负载均衡策略(算法)

Ribbon有其中负载策略,分别为:轮询策略、权重策略、随机策略、最小连接数策略、重试策略、可用性敏感策略、区域敏感策略。默认为轮询策略

可以为每一个服务配置策略:

providerName: #注册中心的服务名
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

还可以实现 IRule 接口,然后自定义负载策略

  • 轮询策略 RoundRobinRule:依次调用服务实例
  • 权重策略 WeightedResponseTimeRule:根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。原理:刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。
  • 随机策略 RandomRule:随机选择一个服务实例
  • 最小连接数策略 BestAvailableRule:也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。
  • 重试策略 RetryRule:RetryRule,按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。配置如下:
    ribbon:
      ConnectTimeout: 2000 # 请求连接的超时时间
      ReadTimeout: 5000 # 请求处理的超时时间
    providerName: #注册中心的服务名
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    
  • 可用敏感性策略 AvailabilityFilteringRule:先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。
  • 区域敏感策略 ZoneAvoidanceRule:根据服务所在区域(eureka.instance.metadata-map.zone=xxxxx)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。原理:使用 ZoneAvoidancePredicate判断判定一个 Zone 的运行性能是否可用,剔除不可用的 Zone(的所有 Server),AvailabilityPredicate 用于过滤掉连接数过多的 Server。
    个人理解Zone(区域):开发者主观给服务分类

服务调用 OpenFeign

Feign是什么?

Feign是spring cloud组件中的一个轻量级restful的http服务客户端。Feign集成并封装了Ribbon、RestTemplate实现了负载均衡的执行Http调用,开发者不必手动使用RestTemplate调服务,而是定义一个接口,在这个接口中标注一个注解即可完成服务调用。

OpenFeign是什么?

OpenFeign是Spring Cloud在feign的基础上支持了Spring Mvc的注解,如@RequesMapping@GetMapping@PostMapping等。OpenFeign还实现与Ribbon的整合。

断路器 Netflix Hystrix

核心概念

Hystrix的主要提供 服务降级、服务熔断、服务限流功能。

为什么阻塞会崩溃?(服务雪崩)

因为这些请求会消耗占用系统的线程、IO 等资源,消耗完这个系统服务器就容易崩。这就是服务雪崩

什么是 Hystrix 之熔断和降级(纯概念)?

  • 服务熔断:熔断是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 断路器 直接将此请求链路断开。
  • 服务降级:降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。比如某接口突如其来的大量访问,可能会导致系统崩溃。那么我们就进行 服务降级 ,一些请求会做一些降级处理比如当前人数太多请稍后查看等等。

【提问】Hystrix 中的 断路器模式

Hystrix遵循的设计原则是防止任何单独的依赖耗尽资源。
使用 @HystrixCommand 标注某个方法,这样 Hystrix 就会使用 断路器 来“包装”这个方法,每当调用时间超过指定时间时(默认为 1000ms),断路器将会中断对这个方法的调用。

舱壁模式:舱壁模式会将远程资源调用隔离在他们自己的线程池中,以便可以控制单个表现不佳的服务,而不会使该程序崩溃。

配置案例(每当10个请求中,有60%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到10s之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。),代码:

@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class FeignClientTestController {
  @HystrixCommand(commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")
  })
  public String serverMethod() {
    return null;
  }
  public String defaultFallback() {
      return "降级回复";
  }
}

@HystrixCommand属性:

  • groupKey:HystrixCommand 命令所属的组的名称:默认注解方法类的名称
  • commandKey:HystrixCommand 命令的key值,默认值为注解方法的名称
  • threadPoolKey: 线程池名称,默认定义为groupKey
  • fallbackMethod:定义回退方法的名称, 此方法必须和hystrix的执行方法在相同类中
  • commandProperties:配置hystrix命令的参数
  • threadPoolProperties:配置hystrix依赖的线程池的参数
  • ignoreExceptions:如果hystrix方法抛出的异常包括RUNTIME_EXCEPTION,则会被封装HystrixRuntimeException异常。我们也可以通过此方法定义哪些需要忽略的异常
  • observableExecutionMode:定义执行hystrix observable的命令的模式
  • raiseHystrixExceptions:如果hystrix方法抛出的异常包括RUNTIME_EXCEPTION,则会被封装HystrixRuntimeException异常。此方法定义需要抛出的异常
  • defaultFallback:定义回调方法:但是defaultFallback不能传入参数,返回参数和hystrix的命令兼容

服务网关 Netflix Zuul

什么是网关?

网关是系统唯一对外的入口,介于客户端与服务器端之间。用于对请求进行鉴权、限流、 路由、监控等功能。

为什么要使用网关?

微服务之间直接调用不便于访问与管理。 Zuul 为客户端提供统一入口。好处众多,比如:

  • 网关层对外部和内部进行了隔离,保障了后台服务的安全性。
  • 对外访问控制由网络层面转换成了运维层面,减少变更的流程和错误成本。
  • 减少客户端与服务的耦合,服务可以独立运行,并通过网关层来做映射。
  • 通过网关层聚合,减少外部访问的频次,提升访问效率。
  • 节约后端服务开发成本,减少上线风险。
  • 为服务熔断,灰度发布,线上测试提供简单方案。
  • 便于进行应用层面的扩展。

Zuul请求处理生命周期

  1. http发送请求到zuul网关
  2. zuul网关首先经过pre filter
  3. 验证通过后进入routing filter,接着将请求转发给远程服务,远程服务执行完返回结果,如果出错,则执行error filter
  4. 继续往下执行post filter
  5. 最后返回响应给http 客户端

Zuul的路由

路由可以实现 统一前缀、服务名屏蔽、路径屏蔽、敏感请求头屏蔽等功能。

ZUUL有两种路由,传统路由面向服务路由

  • 传统路由:不依赖于服务发现机制的情况下,通过在配置文件中具体指定每个路由表达式与服务实例的映射关系来实现 API 网关对外不请求的路由。
  • 面向服务路由:Zuul 注册到 Eureka,让路由的 path 不是映射具体的 url ,而是让它映射到某个具体的服务,而具体的 url 则交给 Eureka 的服务发现机制去自动维护。

Zuul 的过滤功能

自定义Filter只需要继承 ZuulFilter,然后交给Spring容器即可。

过滤器类型

有三种,按执行先后顺序分别为:PreRoutingPost。前置Pre就是在请求之前进行过滤,Routing路由过滤器就是路由策略,Post后置过滤器就是在 Response 之前进行过滤的过滤器。

限流案列--令牌桶限流

首先我们会有个固定容量的桶,如果里面没有满那么就会以 固定的速率 会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。

com.google.common.util.concurrent.RateLimiter实现令牌桶限流:

@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
    //定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
    //过滤器type
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
    //在同一个Type中,定义过滤器执行的顺序
    @Override
    public int filterOrder() {
        return -5;
    }
    @Override
    public Object run() throws ZuulException {
        log.info("放行");
        return null;
    }
    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        if(!RATE_LIMITER.tryAcquire()) {
            log.warn("访问量超载");
            // 指定当前请求未通过过滤
            context.setSendZuulResponse(false);
            // 向客户端返回响应码429,请求数量过多
            context.setResponseStatusCode(429);
            return false;
        }
        return true;
    }
}

ZUUL的核心组件

  • Zuul Servlet:zuul的servlet容器
  • Zuul Filter Runner:zuul执行filter的处理器
  • Pre routing Filter:zuul请求的前置过滤器
  • Routing Filter:zuul请求的路由放行过滤器
  • Post routing Filter:zuul请求的后置过滤器
  • Request Context:zuul servlet的上下文
  • Filter Loader:filter加载器
  • Filter File Manager:filter内容管理器
  • Filter Directory:filter过滤器存放路径
  • Filter Publisher:发布filter的处理类
  • Filter Persister:持久化filter的处理类
  • Filter Poller:轮询Persister中的filter并将新filter推送至Filter Directory

分布式配置 Spring Cloud Config

为什么要使用进行配置管理?

微服务系统那么多 服务(ConsumerProviderEureka ServerZuul) 都会持有自己的配置,这个时候我们在项目运行的时候可能需要更改某些应用的配置,如果我们不进行配置的统一管理,我们只能去每个应用下一个一个寻找配置文件然后修改配置文件再重启应用。

Spring Cloud Config是什么?

Config 为分布式系统中的外部化配置提供服务器和客户端支持。使用 Config 服务器,可以在中心位置管理所有环境中应用程序的外部属性。
Config 能将各个 应用/系统/模块 的配置文件存放到 统一的地方然后进行管理(Git 或者 SVN)。

修改配置文件,已启动的服务更新配置问题

动态修改配置文件,已启动的应用不会应用到新的配置

已启动的服务怎么应用动态修改的配置文件?

手动刷新,实现流程(原理)

  1. 引入actuator监控
  2. 暴露监控端口(配置文件配置)
  3. 添加@RefreshScope注解
  4. 请求某个微服务的 /actuator/busrefresh 时(Spring Boot 版本 2.4.0以下为: /actuator/bus-refresh),该微服务从配置服务器 Config Server 获取最新配置。

利用BUS自动刷新

使用Spring Cloud Bus(管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式)。只需要创建一个简单的请求,并且加上 @ResfreshScope 注解就能进行配置的动态修改了。

  1. 安装消息队列,假设是RabbitMQ。
  2. 配置中心服务端 引入spring-cloud-starter-bus-amqp,添加配置: RabbitMQ、git仓库、暴露bus刷新配置的端点
  3. 配置中心客户端 引入spring-cloud-starter-bus-amqp,添加配置: RabbitMQ、config、暴露bus刷新配置的端点
  4. 请求配置中心/actuator/busrefresh更新全部服务配置。