当前位置:   article > 正文

【檀越剑指大厂—SpringCloudNetflix】SpringCloudNetflix高阶篇_proxy stream

proxy stream

一.基础概念

1.架构演进

在系统架构与设计的实践中,从宏观上可以总结为三个阶段;

单体架构 :就是把所有的功能、模块都集中到一个项目中,部署在一台服务器上,从而对外提供服务(单体架构、单体服务、单体应用)。直白一点:就是只有一个项目,只有一个 war;

分布式架构 :就是把所有的功能、模块拆分成不同的子项目,部署在多台不同的服务器上,这些子项目相互协作共同对外提供服务。直白一点:就是有很多项目,有很多 wr 包,这些项目相互协作完成需要的功能,不是一个 war 能完成的,一个 war 包完成不了;

微服务架构 :分布式强调系统的拆分,微服务也是强调系统的拆分,微服务架构属于分布式架构的范畴;

并且到目前为止,微服务并没有一个统一的标准的定义

2.分布式和微服务区别?

分布式,就是将巨大的一个系统划分为多个模块,这一点和微服务是一样的,都是要把系统进行拆分,部署到不同机器上,因为一台机器可能承受不了这么大的访问压力,或者说要支撑这么大的访问压力需要采购一台性能超级好的服务器其财务成本非常高,有这些预算完全可以采购很多台普通的服务器了,分布式系统各个模块通过接口进行数据交互,其实分布式也是一种微服务,因为都是把模块拆分变为独立的单元,提供接口来调用

它们的本质的区别体现在“目标”上,何为目标,就是你采用分布式架构或者采用微服务架构,你最终是为了什么,要达到什么目的?分布式架构的目标是什么?就是访问量很大一台机器承受不了,或者是成本问题,不得不使用多台机器来完成服务的部署;而微服务的目标是什么?只是让各个模块拆分开来,不会被互相影响,比如模块的升级或者出现 BUG 或者是重构等等都不要影响到其他模块,微服务它是可以在一台机器上部署;

3.什么是微服务?

简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如 RESTful API 的方式互相调用。

4.服务治理

服务治理可以说是微服务架构中最为核心和基础的模块, 它主要用来实现各个微服务实例的自动化注册与发现。 为什么我们在微服务架构中那么需要服务治理模块呢?微服务系统没有它会有什么不好的地方吗?

在最初开始构建微服务系统的时候可能服务并不多, 我们可以通过做一些静态配置来 完成服务的调用。 比如,有两个服务 A 和 B, 其中服务 A 需要调用服务 B 来完成一个业务 操作时,为了实现服务 B 的高可用, 不论采用服务端负载均衡还是客户端负载均衡, 都需 要手工维护服务 B 的具体实例清单。 但是随着业务的发展, 系统功能越来越复杂, 相应的 微服务应用也不断增加, 我们的静态配置就会变得越来越难以维护。 并且面对不断发展的业务, 我们的集群规模、 服务的位置 、 服务的命名等都有可能发生变化, 如果还是通过手 工维护的方式, 那么极易发生错误或是命名冲突等问题。 同时, 对于这类静态内容的维护 也必将消耗大量的人力。

为了解决微服务架构中的服务实例维护问题, 产生了大量的服务治理框架和产品。 这 些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化 管理。

5.服务注册

在服务治理框架中, 通常都会构建一个注册中心, 每个服务单元向注册 中心登记自己提供的服务, 将主机与端口号、 版本号、 通信协议等一些附加信息告 知注册中心, 注册中心按服务名分类组织服务清单。 比如, 我们有两个提供服务 A 的进程分别运行于 192.168.0.100:8000 和 192.168.0.101:8000 位置上, 另外还有三个提供服务 B 的进程分别运行千 192.168.0.100:9000、 192.168.0.101:9000、 192.168.0.102:9000 位置上。 当这些进程均启动, 并向注册中心注册自己的服务之后, 注册中心就会维护类似下面的一个服务清单。 另外, 服务注册中心还需要以心跳的方式去监测清单中的服务是否可用, 若不可用 需要从服务清单中剔除, 达到排除故障服务的效果。

Spring Cloud 支持得最好的是 Nacos,其次是 Eureka,其次是 Consul,再次是 Zookeeper。

6.服务发现

服务消费者向注册中心请求已经登记的服务列表,然后得到某个服务

由于在服务治理框架下运作, 服务间的调用不再通过指定具体的实例地 址来实现, 而是通过向服务名发起请求调用实现。 所以, 服务调用方在调用服务提 供方接口的时候, 并不知道具体的服务实例位置。 因此, 调用方需要向服务注册中 心咨询服务, 并获取所有服务的实例清单, 以实现对具体服务实例的访问。 比如, 现有服务 C 希望调用服务 A, 服务 C 就需要向注册中心发起咨询服务请求,服务注 册中心就会将服务 A 的位置清单返回给服务 C, 如按上例服务 A 的情况,C 便获得 了服务 A 的两个可用位置 192.168.0.100:8000 和 192.168.0.101:8000。 当服务 C 要发起调用的时候,便从该清单中以某种轮询策略取出一个位置来进行服 务调用,这就是后续我们将会介绍的客户端负载均衡。 这里我们只是列举了一种简 单的服务治理逻辑, 以方便理解服务治理框架的基本运行思路。 实际的框架为了性 能等因素, 不会采用每次都向服务注册中心获取服务的方式, 并且不同的应用场景 在缓存和服务剔除等机制上也会有一些不同的实现策略。

7.什么是 Spring Cloud?

Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为基于 JVM 的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI 等项目。

Spring Cloud 为开发人员提供了一些工具用来快速构建分布式系统中的一些常见模式和解决一些常见问题(例如配置管理、服务发现、断路器智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、群集状态)。

8.微服务与 Spring-Cloud?

微服务只是一种项目的架构方式、架构理念,或者说是一种概念,就如同我们的 MVC 架构一样

Spring Cloud 便是对这种架构方式的技术落地实现;

9.Spring Cloud 版本

为了避免大家对版本号的误解,避免与子项目版本号混淆,所以 Spring Cloud 发布的版本是一个按照字母顺序的伦敦地铁站的名字(“天使”是第一个版本,"布里克斯顿”是第二个),字母顺序是从 A-Z,目前最新稳定版本 Greenwich

SR3,当 Spring Cloud 里面的某些子项目出现关键性 bug 或重大更新,则发布序列将推出名称以“.SRX”结尾的版本,其中“X”是一个数字,比如:Greenwich SRl、Greenwich SR2、Greenwich SR3;

Spring Cloud 是微服务开发的一整套解决方案,采用 Spring Cloud 开发,每个项目依然是使用 Spring Boot

版本

  • Hoxton
  • Greenwich
  • Finchley
  • Edgware
  • Dalston
  • Camden
  • Brixton
  • Angel

img

10.Spring Cloud 整体架构?

在这里插入图片描述

11.Spring Cloud 组件

它主要提供的模块包括:

  • 服务发现(Eureka)
  • 断路器(Hystrix)
  • 智能路有(Zuul)
  • 客户端负载均衡(Ribbon)

12.Spring Cloud注解

  • @EnableEurekaServer: 用在springboot启动类上,表示这是一个eureka服务注册中心;
  • @EnableDiscoveryClient:用在springboot启动类上,表示这是一个服务,可以被注册中心找到;
  • @LoadBalanced:开启负载均衡能力;
  • @EnableCircuitBreaker:用在启动类上,开启断路器功能;
  • @HystrixCommand(fallbackMethod=”backMethod”):用在方法上,fallbackMethod指定断路回调方法;
  • @EnableConfigServer:用在启动类上,表示这是一个配置中心,开启Config Server;
  • @EnableZuulProxy:开启zuul路由,用在启动类上;
  • @SpringCloudApplication:
    @SpringBootApplication
    @EnableDiscovertyClient
    @EnableCircuitBreaker
    分别是SpringBoot注解、注册服务中心Eureka注解、断路器注解。对于SpringCloud来说,这是每一微服务必须应有的三个注解,所以才推出了@SpringCloudApplication这一注解集合。
  • @ConfigurationProperties:Spring源码中大量使用了ConfigurationProperties注解,比如server.port就是由该注解获取到的,通过与其他注解配合使用,能够实现Bean的按需配置。

该注解有一个prefix属性,通过指定的前缀,绑定配置文件中的配置,该注解可以放在类上,也可以放在方法上。

二.eureka

1.什么是 eureka?

Eureka 服务治理体系中的三个核心角色: 服务注册中心、 服务提供者以及服务消费者。

Spring Cloud Eureka, 使用 Netflix Eureka 来实现服务注册与发现, 它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用 Java 编写,所以 Eureka 主要适用 于通过 Java 实现的分布式系统,或是与 NM 兼容语言构建的系统。但是,由于 Eureka 服 务端的服务治理机制提供了完备的 RESTfulAPL 所以它也支持将非 Java 语言构建的微服 务应用纳入 Eureka 的服务治理体系中来。只是在使用其他语言平台的时候,需要自己来实 现 Euerka 的客户端程序。不过庆幸的是,在目前几个较为流行的开发平台上,都已经有了 一些针对 Eureka 注册中心的客户端实现框架,比如.NET 平台的 Steeltoe、 Node.js 的 euerkaj-sc-lient 等。

Eureka 服务端,我们也称为服务注册中心。它同其他服务注册中心一样,支持高可用 配置。它依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。如 果 Eureka 以集群模式部署,当集群中有分片出现故障时,那么 Eureka 就转入自我保护模 式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中 的其他分片会把它们的状态再次同步回来。以在 AWS 上的实践为例,Netflix 推荐每个可 用的区域运行一个 Eureka 服务端,通过它来形成集群。不同可用区域的服务注册中心通过 异步模式互相复制各自的状态,这意味着在任意给定的时间点每个实例关于所有服务的状 态是有细微差别的。

Eureka 客户端,主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式, 嵌入在客户端应用程序的代码中,在应用程序运行时,Euerka 客户端向注册中心注册自身 提供的服务并周期性地发送心跳来更新它的服务租约。同时,它也能从服务端查询当前注 册的服务信息并把它们缓存到本地并周期性地刷新服务状态。

2.eureka 架构

微信图片_20220414203157.png

  • Service Provider:暴露服务的服务提供方。
  • Service Consumer:调用远程服务的服务消费方。
  • EureKa Server:服务注册中心和服务发现中心。

3.eureka 保护机制

在没有 Eureka 自我保护的情况下,如果 Eureka Server 在一定时间内没有接收到某个微服务实例的心跳 I Eureka Server 将会注销该实例,但是当发生网络分区故障时,那么微服务与 Eureka Server 之间将无法正常通信,以上行为可能变得非常危险了因为微服务本身其实是正常的此时不应该注销这个微服务,如果没有自我保护机制,那么 Eureka Server 就会将此服务注销掉。

当 Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么就会把这个微服务节点进行保护。一旦进入自我保护模式,Eureka Server 就会保护服务注册表中的信息,不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该 Eureka Server 节点会再自动退出自我保护模式。

所以,自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务,使用自我保护模式,可以让 Eureka 集群更加的健壮.稳定

#禁用eurekd目我保护
eureka.server.enable-self-preservation=false
  • 1
  • 2

4.Eureka 与 Zookeeper?

著名的 CAP 理论指出,一个分布式系统不可能同时满足 C(一致性街)、A(可用性)和 P(分区容错性)。

由于分区容错性在是分布式系统中必须要保证的,因此我们只能在 A 和 C 之间进行权衡,在此 Zookeeper 保证的是 CP,而 Eureka 则是 AP。

Eureka保证AP

Eureka 优先保证可用性,Eureka 各个节点是平等的,某节点异常,剩余的节点依然可以提供注册和服务查询功能.

在向某个 Eureka 注册时,如果发现连接失败,则只要有一台 Eureka 还在,就能保证注册服务可用,但是数据可能不是最新的(不保证强一致性)。

Zookeeper保证CP

在 ZooKeeper 中,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举,但是问题在于,选举 leader 需要一定时间,且选举期间整个 ZooKeeper 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 ZooKeeper 集群失去 master 节点是大概率事件,虽然服务最终能够恢复,但是在选举时间内导致服务注册长期不可用是难以容忍的。

5.Eureka 高可用集群?

相互注册,去中心化

#端口号
server:
  port: 8769

spring:
  application:
    name: eurka-server

eureka:
  instance:
    hostname: eureka8769
  client:
    register-with-eureka: false #由于该应用为注册中心,所以设置 为 false, 代表不向注册中心注册自己。
    fetch-registry: false #由于注册中心的职责就是维护服务实例, 它并不需要去检索服务, 所以也设置为 false。
    serviceUrl:
      defaultZone: http://eureka8767:8767/eureka/,http://eureka8768:8768/eureka/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

6.eureka 三要素?

在 “服务治理”示例中, 我们的示例虽然简单, 但是麻雀虽小、 五脏俱全。 它已经包含了整个 Eureka 服务治理基础架构的三个核心要素。

  • 服务注册中心: Eureka 提供的服务端, 提供服务注册与发现的功能, 也就是我们实现的 eureka-server。
  • 服务提供者:提供服务的应用, 可以是 SpringBoot 应用, 也可以是其他技术平台且遵循 Eureka 通信机制的应用。它将自己提供的服务注册到 Eureka, 以供其他应用发现, 也就是我们实现的 spring-boot-service 应用。
  • 服务消费者:消费者应用从服务注册中心获取服务列表, 从而使消费者可以知道去何处调用其所需要的服务,在面我们使用了 Ribbon 来实现服务消费,另外后续还会介绍使用 Feign 的消费方式。 很多时候, 客户端既是服务提供者也是服务消费者。

7.Eureka 本地缓存

8.服务提供者

1.服务注册

将服务所在主机、端口、版本号、通信协议等信息登记到注册中心上;

“服务提供者” 在启动的时候会通过发送 REST 请求的方式将自己注册到 EurekaServer 上, 同时带上了自身服务的一些元数据信息。Eureka Server 接收到这个 REST 请求之后, 将元数据信息存储在一个双层结构 Map 中, 其中第一层的 key 是服务名, 第二层的 key 是 具体服务的实例名。(我们可以回想一下之前在实现 Ribbon 负载均衡的例子中, Eureka 信 息面板中一个服务有多个实例的清况, 这些内容就是以这样的双层 Map 形式存储的)

在服务注册时, 需要确认一下 eureka.client.register-with-eureka=true 参数是否正确, 该值默认为 true。若设置为 false 将不会启动注册操作。

boolean register() throws Throwable {
      logger.info("DiscoveryClient_{}: registering service...", this.appPathIdentifier);
      EurekaHttpResponse httpResponse;
      try {
          httpResponse = 			  this.eurekaTransport.registrationClient.register(this.instanceInfo);
      } catch (Exception var3) {
          logger.warn("DiscoveryClient_{} - registration failed {}", new Object[]{this.appPathIdentifier, var3.getMessage(), var3});
          throw var3;
      }
      if (logger.isInfoEnabled()) {
          logger.info("DiscoveryClient_{} - registration status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
      }
      return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
2.服务同步

如架构图中所示, 这里的两个服务提供者分别注册到了两个不同的服务注册中心上, 也就是说, 它们的信息分别被两个服务注册中心所维护。 此时, 由于服务注册中心之间因互相注册为服务, 当服务提供者发送注册请求到一个服务注册中心时, 它会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。通过服务同步,两个服务提供者的服务信息就可以通过这两台服务注册中心中的任意一台获取到。

3.服务续约

在注册完服务之后,服务提供者会维护一个心跳用来持续告诉 Eureka Server: "我还活着”,以防止 Eureka Server 的 “剔除任务 ” 将该服务实例从服务列表中排除出去,我们称该操作为服务续约(Renew)。

关千服务续约有两个重要属性,我们可以关注并根据需要来进行调整:

而 “服务获取” 的逻辑在独立的一个 W 判断中, 其判断依据就是我们之前所提到的 eureka.clien.fetch-registry=true 参数, 它默认为 true, 大部分情况下我们 不需要关心。 为了定期更新客户端的服务清单, 以保证客户端能够访问确实健康的服务实 例, “服务获取” 的请求不会只限于服务启动, 而是一个定时执行的任务, 从源码中我们可 以看到任务运行中的 registryFetchintervalSeconds 参数对应的就是之前所提到的 eureka.client.registry-fetch-interval-seconds=30 配置参数, 它默认为 30 秒。

eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90
#参数用于定义服务续约任务的调用间隔时间,默认为30秒。
eureka.instance.lease-renewal-interval-in-seconds
#参数用于定义服务失效的时间,默认为90秒。
eureka.instance.lease-expiration-duration-in-seconds
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
private void initScheduledTasks() {
      int renewalIntervalInSecs;
      int expBackOffBound;
      if (this.clientConfig.shouldFetchRegistry()) {
          renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
          expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
          this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
      }

      if (this.clientConfig.shouldRegisterWithEureka()) {
          renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
          expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();
          logger.info("Starting heartbeat executor: renew interval is: {}", renewalIntervalInSecs);
          this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
          this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2);
          this.statusChangeListener = new StatusChangeListener() {
              public String getId() {
                  return "statusChangeListener";
              }

              public void notify(StatusChangeEvent statusChangeEvent) {
                  if (InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {
                      DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
                  } else {
                      DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);
                  }

                  DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
              }
          };
          if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {
              this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);
          }

          this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
      } else {
          logger.info("Not registering with Eureka server per configuration");
      }

  }
  • 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

9.服务消费者

1.获取服务

到这里,在服务注册中心已经注册了一个服务,并且该服务有两个实例。 当我们启动 服务消费者的时候,它会发送一个 REST 请求给服务注册中心,来获取上面注册的服务清 单。为了性能考虑,EurekaServer 会维护一份只读的服务清单来返回给客户端,同时该缓 存清单会每隔 30 秒更新一次。

获取服务是服务消费者的基础,所以必须确保 eureka.client.fetch-registry= true 参数没有被修改成 false, 该值默认为 true。若希望修改缓存清单的更新时间,可 以通过 eureka.client.registry-fetch-interval-seconds=30 参数进行修改, 该参数默认值为 30, 单位为秒。

2.服务调用

服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。 因为有这些服务实例的详细信息, 所以客户端可以根据自己的需要决定具 体调用哪个实例,在 ribbon 中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。

对于访问实例的选择,Eureka 中有 Region 和 Zone 的概念, 一个 Region 中可以包含多个 Zone, 每个服务客户端需要被注册到一个 Zone 中, 所以每个客户端对应一个 Region 和一个 Zone。 在进行服务调用的时候,优先访问同处一个 Zone 中的服务提供方, 若访问不到,就 访问其他的 Zone, 更多关于 Region 和 Zone 的知识,我们会在后续的源码解读中介绍。

3.服务下线

在系统运行过程中必然会面临关闭或重启服务的某个实例的情况, 在服务关闭期间, 我们自然不希望客户端会继续调用关闭了的实例。 所以在客户端程序中, 当服务实例进行正常的关闭操作时, 它会触发一个服务下线的 REST 请求给 Eureka Server, 告诉服务注册中心:“我要下线了”。服务端在接收到请求之后, 将该服务状态置为下线(DOWN), 并把该下线事件传播出去。

10.服务注册中心

1.失效剔除

有些时候, 我们的服务实例并不一定会正常下线, 可能由于内存溢出、 网络故障等原因使得服务不能正常工作, 而服务注册中心并未收到 “服务下线” 的请求。 为了从服务列表中将这些无法提供服务的实例剔除,EurekaSrevre 在启动的时候会创建一个定时任务, 默认每隔一段时间(默认为 60 秒) 将当前清单中超时(默认为 90 秒)没有续约的服务剔除出去。

2.自我保护

当我们在本地调试基于 Eurkea 的程序时, 基本上都会碰到这样一个问题, 在服务注册中心的信息面板中出现类似下面的红色警告信息:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
  • 1

实际上, 该警告就是触发了 EurekaServer 的自我保护机制。 之前我们介绍过, 服务注 册到 EurekaSrevre 之后,会维护一个心跳连接,告诉 EurekaServer 自己还活着。EurkeaServer 在运行期间,会统计心跳失败的比例在 15 分钟之内是否低于 85%,如果出现低于的情况(在 单机调试的时候很容易满足, 实际在生产环境上通常是由于网络不稳定导致), Eureka Server 会将当前的实例注册信息保护起来, 让这些实例不会过期, 尽可能保护这些注册信 息。 但是, 在这段保护期间内实例若出现问题, 那么客户端很容易拿到实际已经不存在的 服务实例, 会出现调用失败的清况, 所以客户端必须要有容错机制, 比如可以使用请求重 试、 断路器等机制。

由于本地调试很容易触发注册中心的保护机制, 这会使得注册中心维护的服务实例不那么准确。 所以, 我们在本地进行开发的时候, 可以使用参数来关闭保护机制, 以确保注册中心可以将不可用的实例正确剔除。

eureka.server.enableself-preservation =false
  • 1

11.eureka 源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
    boolean autoRegister() default true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
//服务发现的核心类
package com.netflix.discovery;
  • 1
  • 2

image-20230109113222201

image-20230109113524705

package org.springframework.cloud.netflix.eureka;
  • 1

image-20230109113345110

org.springframework.cloud.client.discovery.DiscoveryClient 是 SpringCloud 的接口, 它定义了用来发现服务的常用抽象方法, 通过该接口可以有效地 屏蔽服务治理的实现细节, 所以使用 SpringCloud 构建的微服务应用可以方便地切换不同服务治理框架, 而不改动程序代码, 只需要另外添加一些针对服务治理框架的配置即可。 org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient 是对该接口的实现, 从命名来判断, 它实现的是对 Eureka 发现服务的封装。 所以 EurekaDiscoveryClient 依赖了 Netflix Eureka 的 com.netflix.discovery. EurekaClient 接口, EurekaClient 继承了 LookupService 接口, 它们都是 Netflix 开源包中的内容, 主要定义了针对 Eureka 的发现服务的抽象方法, 而真正实现发现服务的 则是 Netflix 包中的 com.netftx.discovery.DiscoveryClient 类。

12.DiscoveryClient

DiscoveryClient 这个类用于帮助与 Eureka Server 互相协作。
EurekaClient 负责下面的任务:

  • 向 Eureka Server 注册服务实例

  • 向 Eureka Server 服务租约

  • 当服务关闭期间, 向 Eureka Server 取消租约

  • 查询 Eureka Server 中的服务实例列表

13.什么是 Region?

客户端依次加载了两个内容, 第一个是 Region, 第二个 是 Zone, 从其加载逻辑上我们可以判断它们之间的关系:

通过 getRegion 函数, 我们可以看到它从配置中读取了一个 Region 返回, 所以 一 个微服务应用只可以属于一个 Region, 如果不特别配置, 默认为 defaul。若我们要 自己设置, 可以通过 eureka.client.region 属性来定义。

public static String getRegion(EurekaClientConfig clientConfig) {
      String region = clientConfig.getRegion();
      if (region == null) {
          region = "default";
      }
      region = region.trim().toLowerCase();
      return region;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

14.什么是 Zone?

通过 getAvailabilityZones 函数, 可以知道当我们没有特别为 Region 配置 Zone 的时候, 将默认采用 defaultZone, 这也是我们之前配置参数 eureka.client.serviceUrl.defaultZone 的由来。 若要为应用指定 Zone, 可以通过 eureka.client.availability-zones 属性来进行设置。从该函数的 return 内容, 我们可以知道 Zone 能够设置多个, 并且通过逗号分隔来配置。 由此, 我们可以判断 Region 与 Zone 是一对多的关系

public String[] getAvailabilityZones(String region) {
      String value = (String)this.availabilityZones.get(region);
      if (value == null) {
          value = "defaultZone";
      }

      return value.split(",");
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当我们在微服务应用中使用 Ribbon 来实现服务调用时,对于 Zone 的设置可以在负载 均衡时实现区域亲和特性: Ribbon 的默认策略会优先访问同客户端处于一个 Zone 中的服 务端实例,只有当同一个 Zone 中没有可用服务端实例的时候才会访问其他 Zone 中的实例。 所以通过 Zone 属性的定义,配合实际部署的物理结构,我们就可以有效地设计出对区域性 故障的容错集群。

15.服务消费基础

服务消费基础实现方式,不是传统实现方式

@RestController
public class DcController {

    @Autowired
    LoadBalancerClient loadBalancerClient;
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/consumer")
    public String dc() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-server");
        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/dc";
        System.out.println(url);
        return restTemplate.getForObject(url, String.class);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以看到这里,我们注入了LoadBalancerClientRestTemplate,并在/consumer接口的实现中,先通过loadBalancerClientchoose函数来负载均衡的选出一个eureka-client的服务实例,这个服务实例的基本信息存储在ServiceInstance中,然后通过这些对象中的信息拼接出访问/dc接口的详细地址,最后再利用RestTemplate对象实现对服务提供者接口的调用。

16.什么是 consul?

consul 特性:

  • 服务发现
  • 健康检查
  • Key/Value 存储
  • 多数据中心

服务发现的接口DiscoveryClient是 Spring Cloud 对服务治理做的一层抽象,所以可以屏蔽 Eureka 和 Consul 服务治理的实现细节,我们的程序不需要做任何改变,只需要引入不同的服务治理依赖,并配置相关的配置属性就能轻松的将微服务纳入 Spring Cloud 的各个服务治理框架中。

由于 Consul 自身提供了服务端,所以我们不需要像之前实现 Eureka 的时候创建服务注册中心,直接通过下载 consul 的服务端程序就可以使用。

三.Ribbon 和 feign

1.什么是负载均衡?

载均和分为硬件负载均衡和软件负载均衡:

  • 硬件负载均衡:比如 F5、深信服、Array 等;
  • 软件负载均衡:比如 Nginx、LVS、HAProxy 等;

2.服务端负载均衡

image-20230113110509771

3.什么是 Ribbon?

Ribbon 是 Netfliⅸ 公司发布的开源项目(组件、框架、jar 包),主要功能是提供客户端的软件负载均衡算法,它会从 eureka 中获取一个可用的服务端清单通过心跳检测来易剔除故的服务端节点以保证清单中都是可以正常访问的服务端节点。

当客户端发送请求,则 ribbon 负载均衡器按某种算法(比如轮询、权重、最小连接数等从维护的可用服务端清单中取出一台服务端的地址然后进行请求

Ribbon 是一个基于 HTTP 和 TCP 客户端的负载均衡器。Ribbon 非常简单,可以说就是一个 jar 包,这个 jar 包实现了负载均衡算法,Spring Cloud 对 Ribbon 做了二次封装,可以让我们使用 RestTemplate 的服务请求,自动转换成客户端负载均衡的服务调用。

Ribbon 可以在通过客户端中配置的 ribbonServerList 服务端列表去轮询访问以达到均衡负载的作用。当 Ribbon 与 Eureka 联合使用时,ribbonServerList 会被 DiscoveryEnabledNIWSServerList 重写,扩展成从 Eureka 注册中心中获取服务端列表。同时它也会用 NIWSDiscoveryPing 来取代 IPing,它将职责委托给 Eureka 来确定服务端是否已经启动。

Ribbon 支持多种负载均衡算法,还支持自定义的负载均衡算法。

使用 Ribbon 实现负载均衡的调用(spring cloud -> 封装 ribbon + eureka + restTemplate)

Ribbon 默认是采用懒加载,即第一次访问时才会去创建 LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true
    clients: user
  • 1
  • 2
  • 3
  • 4

4.IRule

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

image-20230111091941107

5.ILoadBalancer

一个服务对应一个 LoadBalancer,一个 LoadBalancer 只有一个 Rule,LoadBalancer 记录服务的注册地址,Rule 提供从服务的注册地址中找出一个地址的规则。

通过 LoadBalancerIntercepor 拦截器,可以拦截请求 url 和服务 id,然后结合 IRule 进行路由处理

public interface ILoadBalancer {
    void addServers(List<Server> var1);

    Server chooseServer(Object var1);

    void markServerDown(Server var1);

    /** @deprecated */
    @Deprecated
    List<Server> getServerList(boolean var1);

    List<Server> getReachableServers();

    List<Server> getAllServers();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

6.负载均衡策略

Ribbon 提供七种负载均衡策略,默认的负载均衡策略是轮训策略。RoundRobinRule

public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnectionListener, IClientConfigAware {
    private static final IRule DEFAULT_RULE = new RoundRobinRule();
}
  • 1
  • 2
  • 3

通过 choose 方法选择指定的算法。完整的算法包含如下:

策略名称解释描述
RandomRule随机算法实现
RoundRobinRule轮询负载均衡策略,依次轮询所有可用服务器列表
RetryRule先按照 RoundRobinRule 策略获取服务,如果获取服务失败会在指定时间内重试
AvaliabilityFilteringRule过滤掉那些因为一直连接失败的被标记为 circuit tripped 的后端 server,并过滤掉那些高并发的的后端 server(active connections 超过配置的阈值)
BestAvailableRule会先过滤掉由于多次访问故障二处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
WeightedResponseTimeRule根据响应时间分配一个 weight,响应时间越长,weight 越小,被选中的可能性越低
ZoneAvoidanceRule复合判断 server 所在区域的性能和 server 的可用性选择 server,新版本默认的负载均衡策略
  • RoundRobinRule: 轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮(代码中写死的,不能修改)。若还未找到,则返回 null。

  • RandomRule: 随机策略,从所有可用的 provider 中随机选择一个。。

  • RetryRule: 重试策略。先按照 RoundRobinRule 策略获取 server,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。

  • BestAvailableRule: 最可用策略。选择并发量最小的 provider,即连接的消费者数量最少的 provider。其会遍历服务列表中的每一个 server,选择当前连接数量 minimalConcurrentConnections 最小的 server。

  • AvailabilityFilteringRule: 可用过滤算法。该算法规则是:过滤掉处于熔断状态的 server 与已经超过连接极限的 server,对剩余 server 采用轮询策略。

7.负载均衡原理

Ribbon 负载均衡基本原理:
1、Ribbon 会拦截 Eureka Client 客户端发出的 http 请求,获得服务名(userservice)
2、从 Eureka 中拉取动态服务列表(8081、8082。。。。。)
3、从里面选一个服务(如 8081)出来(轮询、随机等算法)
4、去调用这个服务

在这里插入图片描述

8.ribbon组件

主要有以下组件:

  • IRule,定义如何选择规则和策略
  • IPing,接口定义了我们如何“ping”服务器检查是否活着
  • ServerList,定义了获取服务器的列表接口,存储服务列表
  • ServerListFilter,接口允许过滤配置或动态获得的候选列表服务器
  • ServerListUpdater,接口使用不同的方法来做动态更新服务器列表
  • IClientConfig,定义了各种api所使用的客户端配置,用来初始化ribbon客户端和负载均衡器,默认实现是DefaultClientConfigImpl ILoadBalancer, 接口定义了各种软负载,动态更新一组服务列表及根据指定算法从现有服务器列表中选择一个服务

@LoadBalanced注解,开启客户端的负载均衡:

  • RestTemplate
  • LoadBalancerAutoConfiguration
    • @ConditionalOnClass(RestTemplate.class)
    • @ConditionalOnBean(LoadBalancerClient.class)
  • SmartInitializingSingleton 加载@LoadBalanced注解的RestTemplate到RestTemplateCustomizer
  • LoadBalancerInterceptor拦截器拦截RestTemplate请求

9.远程调用有哪些?

  • Httpclient(apache)
  • Httpurlconnection (jdk)
  • restTemplate (spring)
  • OkHttp(android)
  • feign(Spring cloud)

10.什么是 Feign?

Feign 是一个声明式的 Web Service 客户端,它使得编写 Web Serivce 客户端变得更加简单。我们只需要使用 Feign 来创建一个接口并用注解来配置它既可完成。它具备可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解。Feign 也支持可插拔的编码器和解码器。Spring Cloud 为 Feign 增加了对 Spring MVC 注解的支持,还整合了 Ribbon 和 Eureka 来提供均衡负载的 HTTP 客户端实现。

Feign 在Ribbon+RestTemplate 的基础上做了进一步封装,在Feign 封装之后,我们只需创建一个接口并使用注解的方式来配置,即可完成对服务提供方的接口绑定简化了使用 Ribbon + RestTemplate 的调用,自动封装服务调用客户端.

Feign 中也使用 Ribbon,自动集成了 ribbon

11.feignclient 幂等性

Retryer是重试器,其实现方法有两种,第一种是系统默认实现方式,第二种是可以自定义重试器,一般少用,通过默认实现重试类Default可以看到其构造函数中的重试次数为5。

public Default() {
  this(100, SECONDS.toMillis(1), 5);
}

public Default(long period, long maxPeriod, int maxAttempts) {
  this.period = period;
  this.maxPeriod = maxPeriod;
  this.maxAttempts = maxAttempts;
  this.attempt = 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

因此解决Feign调用的幂等性问题最简单也就最常用的就是让Feign不重试。

12.ip 不能访问

使用了@LoadBalanced 注解后不能再使用 192.168.28.1(ip)调用,必须要使用注册中心中注册的服务名称

所以在使用 RestTemplate 的时候,有些场景下我们只是单纯的用 ip 进行调用(如调用第三方接口服务),此时的 RestTemplate 就不能够加@LoadBanlanced 注解。还有一种办法是直接在需要使用 RestTemplate 的地方,直接 new 一个新的 RestTemplate 对象。如下

public void test(){
	RestTemplate restTemplate = new RestTemplate();
	//这里的接口地址就可以直接放协议+ip+端口,原理与上面一样
	restTemplate.exchange("接口地址",xxx,xxx,xxx);
}
  • 1
  • 2
  • 3
  • 4
  • 5

13.feign 超时

Spring Cloud 中 Feign 客户端是默认开启支持 Ribbon 的,最重要的两个超时就是连接超时 ConnectTimeout 和读超时 ReadTimeout,在默认情况下,也就是没有任何配置下,Feign 的超时时间会被 Ribbon 覆盖,两个超时时间都是 1 秒。

#ribbon配置
ribbon:
  #建立连接超时时间
  ConnectTimeout: 5000
  #建立连接之后,读取响应资源超时时间
  ReadTimeout: 5000

#Feign配置
feign:
  client:
    config:
      #这里填具体的服务名称(也可以填default,表示对所有服务生效)
      app-order:
        #connectTimeout和readTimeout这两个得一起配置才会生效
        connectTimeout: 5000
        readTimeout: 5000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

四.hystrix

1.什么是 Hystrix?

在 Spring Cloud 中使用了 Hystrix 来实现断路器的功能。Hystrix 是 Netflix 开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

Hystrix 原理是命令模式,在遇到不同的情况下,请求 fallback,降级处理

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.Hystrix 有哪些功能?

核心功能:

  • 超时
  • 异常
  • 限流

断路器的作用

  1. 防止单个服务的故障导致器依赖和相关的服务容器的线程资源被耗尽:
  2. 减少负载,并负责通过请求的快速失败功能而不是让请求进行排队.
  3. 尽可能提供服务降级的功能,以免用户免受故障影响
  4. 使用隔离技术来限制某个服务出现问题所带来的影响
  5. 尽可能提供实时监控信息,提供监控和警报来优化发现故障的时间
  6. 允许使用配置修改相关的参数(如请求超时时可以触发熔断)
  7. 对服务调用消费者内部的故障进行保护,而不仅仅是在网络流量上进行保护降级,限流

一个简单的场景就是家里的保险丝,当电流过大时保险丝熔断,使得家里的其他电器不至于造成大规模的损坏:
在微服务中,当大量请求到达产品微服务,这会导致其服务响应变得缓慢,或者因为内存不足等原因使得服务器出现宕机或者数据库出现问题;

另一个原因,当一个微服务中有很多请求时,会导致他调用其他微服务的时候(正常的服务线程)造成积压,产生雪崩;

3.Hystrix 超时配置

#开启feign开始hystrix的支持
feign:
  hystrix:
    enabled: true

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000 #hystrix超时时间
        timeout:
          enabled: true #开启hystrix超时管理
ribbon:
  ReadTimeout: 2000 #请求超时时间
  http:
    client:
      enabled: true #开启ribbon超时管理
  ConnectTimeout: 2000 #连接超时时间
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • Dalston 版本之后默认关闭,注意开启 feign 的 hystrix 的功能
  • ribbon 和 hystrix 都要设置,谁小以谁为准

4.熔断降级

  • 可以监听你的请求有没有超时;(默认是 1 秒,时间可以改)
  • 异常或报错了可以快速让请求返回,不会一直等待;(避免线程累积)
  • 当的系统马上迎来大量的并发(双十一秒杀这种或者促销活动)

此时如果系统承载不了这么大的并发时,可以考虑先关闭一些不重要的微服务(在降级方法中返回一个比较友好的信息),把资源让给核心微服务,待高峰流量过去,再开启回来。

5.服务限流

通过 2 种方式进行限流

  • 线程数限制
  • 信号量限制

限流配置,通过限制线程数进行限流,限流就是限制你某个微服务的使用量(可用线程),

hystriⅸ 通过线程池的方式来管理微服务的调用,它默认是一个线程池(大小 10 个)管理你的所有微服务,你可以给某个微服务开辟新的线程池.

@HystrixCommand(
        fallbackMethod = "addServiceFallback",
        commandProperties = {
                @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
        },
        threadPoolProperties = {
                @HystrixProperty(name = "coreSize", value = "2"),
                @HystrixProperty(name = "maxQueueSize", value = "1"),
                @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
                @HystrixProperty(name = "queueSizeRejectionThreshold", value = "100")
        }
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • commandProperties 可以配置 hystrix 的各种配置
  • hreadPoolProperties 属性用于控制线程池的行为,要求为我们建立一个大小为 coreSize 的,且在之前创建一个队列的线程池,队列大小为 maxQueueSize 对应的 value 的值
  • 这个队列的作用是:控制在线程池中线程繁忙时允许堵塞的请求数(即 value 值),一旦请求数超过了队列大小,对线程池的任何请求都将失败,直到队列中有空间为止。此外,对于 maxQueueSize 的值也有说明,当 value=-1 时,则将使用 Java SynchronousQueue 来保存所有传入的请求,同步队列本质上会强制要求正在处理中的请求数量永远不要超过线程池中的可用数量;将 maxQueueSize 设置为大于 1 的值将导致 Hystrix 使用 Java LinkedBlockingQueue。这使得即使所有的线程都忙于处理请求,也能对请求进行排队。

6.commandProperties

Command 属性主要用来控制 HystrixCommand 命令的行为,它主要分下面的类别:

  • Execution:用来控制 HystrixCommand.run()的执行
    • execution.isolation.strategy:该属性用来设置 HystrixCommand.run()执行的隔离策略。默认为 THREAD
    • execution.isolation.thread.timeoutInMilliseconds:该属性用来配置 HystrixCommand 执行的超时时间,单位为毫秒。
    • execution.timeout.enabled:该属性用来配置 HystrixCommand.run()的执行是否启用超时时间。默认为 true。
    • execution.isolation.thread.interruptOnTimeout:该属性用来配置当 HystrixCommand.run()执行超时的时候是否要它中断。
    • execution.isolation.thread.interruptOnCancel:该属性用来配置当 HystrixCommand.run()执行取消时是否要它中断。
    • execution.isolation.semaphore.maxConcurrentRequests:当 HystrixCommand 命令的隔离策略使用信号量时,该属性用来配置信号量的大小。当最大并发请求达到该设置值时,后续的请求将被拒绝。
  • Fallback:用来控制 HystrixCommand.getFallback()的执行
    • fallback.isolation.semaphore.maxConcurrentRequests:该属性用来设置从调用线程中允许 HystrixCommand.getFallback()方法执行的最大并发请求数。当达到最大并发请求时,后续的请求将会被拒绝并抛出异常。
    • fallback.enabled:该属性用来设置服务降级策略是否启用,默认是 true。如果设置为 false,当请求失败或者拒绝发生时,将不会调用 HystrixCommand.getFallback()来执行服务降级逻辑。
  • Circuit Breaker:用来控制 HystrixCircuitBreaker 的行为。
    • circuitBreaker.enabled:确定当服务请求命令失败时,是否使用断路器来跟踪其健康指标和熔断请求。默认为 true。
    • circuitBreaker.requestVolumeThreshold:用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认 10 秒)内仅收到 19 个请求,即使这 19 个请求都失败了,断路器也不会打开。
    • circuitBreaker.sleepWindowInMilliseconds:用来设置当断路器打开之后的休眠时间窗。休眠时间窗结束之后,会将断路器设置为“半开”状态,尝试熔断的请求命令,如果依然时候就将断路器继续设置为“打开”状态,如果成功,就设置为“关闭”状态。
    • circuitBreaker.errorThresholdPercentage:该属性用来设置断路器打开的错误百分比条件。默认值为 50,表示在滚动时间窗中,在请求值超过 requestVolumeThreshold 阈值的前提下,如果错误请求数百分比超过 50,就把断路器设置为“打开”状态,否则就设置为“关闭”状态。
    • circuitBreaker.forceOpen:该属性默认为 false。如果该属性设置为 true,断路器将强制进入“打开”状态,它会拒绝所有请求。该属性优于 forceClosed 属性。
    • circuitBreaker.forceClosed:该属性默认为 false。如果该属性设置为 true,断路器强制进入“关闭”状态,它会接收所有请求。如果 forceOpen 属性为 true,该属性不生效。
  • Metrics:该属性与 HystrixCommand 和 HystrixObservableCommand 执行中捕获的指标相关。
    • metrics.rollingStats.timeInMilliseconds:该属性用来设置滚动时间窗的长度,单位为毫秒。该时间用于断路器判断健康度时需要收集信息的持续时间。断路器在收集指标信息时会根据设置的时间窗长度拆分成多个桶来累计各度量值,每个桶记录了一段时间的采集指标。例如,当为默认值 10000 毫秒时,断路器默认将其分成 10 个桶,每个桶记录 1000 毫秒内的指标信息。
    • metrics.rollingStats.numBuckets:用来设置滚动时间窗统计指标信息时划分“桶”的数量。默认值为 10。
    • metrics.rollingPercentile.enabled:用来设置对命令执行延迟是否使用百分位数来跟踪和计算。默认为 true,如果设置为 false,那么所有的概要统计都将返回-1。
    • metrics.rollingPercentile.timeInMilliseconds:用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
    • metrics.rollingPercentile.numBuckets:用来设置百分位统计滚动窗口中使用桶的数量。
    • metrics.rollingPercentile.bucketSize:用来设置每个“桶”中保留的最大执行数。
    • metrics.healthSnapshot.intervalInMilliseconds:用来设置采集影响断路器状态的健康快照的间隔等待时间。
  • Request Context:涉及 HystrixCommand 使用 HystrixRequestContext 的设置。
    • requestCache.enabled:用来配置是否开启请求缓存。
    • requestLog.enabled:用来设置 HystrixCommand 的执行和事件是否打印到日志的 HystrixRequestLog 中。

7.threadPoolProperties

 threadPoolProperties = {
            @HystrixProperty(name = "coreSize",value = "2"),
            @HystrixProperty(name = "allowMaximumSizeToDivergeFromCoreSize",value="true"),
            @HystrixProperty(name = "maximumSize",value="2"),
            @HystrixProperty(name = "maxQueueSize",value="2")
    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 如果请求量少,达不到 coreSize,通常会使用核心线程来执行任务。

  • 如果设置了 maxQueueSize,当请求数超过了 coreSize, 通常会把请求放到 queue 里,待核心线程有空闲时消费。

  • 如果 queue 长度无法满足存储请求,则会创建新线程执行直到达到 maximumSize 最大线程数,多出核心线程数的线程会在空闲时回收。

8.常用限流方式?

  • Hystriⅸ 限流
  • Nginx
  • Redis+Lua
  • Sentinel
  • 基于限流算法自己实现(令牌桶、漏桶慎法)

9.熔断器什么场景下会失效

  1. feign 未开启 hystrix 功能
  2. 超时时间配置不正确
  3. 未开启熔断器的功能,false
  4. 超时时间没有考虑 ribbon 的情况

10.什么是 Dashboard?

Hystrix Dashboard 是一个通过收集 actuator 端点提供的 Hystrix 流数据,因此在这些被断路器保护的应用中需要开启 Hystrix 流的 Actuator 端点,并将其图表化的客户端。如果需要通过图表化的界面查看被断路器保护的方法相关调用信息、或者实时监控这些被断路器保护的应用的健康情况,就可以使用 Hystrix Dashboard。

11.Dashboard 使用

#监控链接
http://localhost:8080/hystrix.stream
http://localhost:8080/actuator/hystrix.stream

#请求下hystrix的接口
http://localhost:8080/portal/hystrix/1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

image-20230114174232728

#访问链接
http://localhost:9909/hystrix
  • 1
  • 2

image-20230114175027076

portal中需要加入

@Bean
public ServletRegistrationBean getServlet() {
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean registrationBean = new 			ServletRegistrationBean(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/hystrix.stream");
    registrationBean.setName("HystrixMetricsStreamServlet");
    return registrationBean;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

dashboard中需要加入

hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"
  • 1
  • 2
  • 3

12.仪表盘解读

  • 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度

  • 该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。

  • 曲线:用来记录 2 分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。

img

这里写图片描述

在图表中,左上角的圆圈代表了该方法的流量和状态:

  • 圆圈越大代表方法流量越大
  • 圆圈为绿色代表断路器健康、黄色代表断路器偶发故障、红色代表断路器故障

右上角的计数器(三列数字):

第一列从上到下

  • 绿色代表当前成功调用的数量

  • 蓝色代表短路请求的数量

  • 蓝绿色代表错误请求的数量

第二列从上到下

  • 黄色代表超时请求的数量

  • 紫色代表线程池拒绝的数量

  • 红色代表失败请求的数量

第三列

  • 过去 10s 的错误请求百分比

Thread Pools:

Hystrix 会针对一个受保护的类创建一个对应的线程池,这样做的目的是 Hystrix 的命令被调用的时候,不会受方法请求线程的影响(或者说 Hystrix 的工作线程和调用者线程相互之间不影响)

左下角从上至下:

  • Active 代表线程池中活跃线程的数量

  • Queued 代表排队的线程数量,该功能默认禁止,因此默认情况下始终为 0

  • Pool Size 代表线程池中线程的数量(上面图我搞错了,困得死 MMP

右下角从上至下:

  • Max Active 代表最大活跃线程,这里展示的数据是当前采用周期中,活跃线程的最大值

  • Execcutions 代表线程池中线程被调用执行 Hystrix 命令的次数

  • Queue Size 代表线程池队列的大小,默认禁用,无意义

13.turbine 的作用

监控数据聚合

Turbine 是聚合服务器发送事件流数据的一个工具,Hystrix 的监控中,只能监控单个节点,实际生产中都为集群,因此可以通过 Turbine 来监控集群下 Hystrix 的 metrics 情况,同一个服务负载均衡情况下,可以监控多个实例.hosts>1

14.turbine 使用

http://localhost:8081/portal/hystrix/1
  • 1
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
@EnableTurbine
@EnableEurekaClient
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixTurbineApplication {

    public static void main(String[] args) {

        SpringApplication.run(HystrixTurbineApplication.class, args);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

image-20230114182124693

#端口号
server:
  port: 9999

spring:
  application:
    name: hystrix-turbine-service #服务名称

#服务提供者
eureka:
  client:
    service-url:
      defaultZone: http://eureka8767:8767/eureka/,http://eureka8768:8768/eureka/,http://eureka8769:8769/eureka/
  instance:
    lease-renewal-interval-in-seconds: 2 #每间隔2s,向服务端发送一次心跳,证明自己依然"存活”
    lease-expiration-duration-in-seconds: 10 #告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我踢出掉
    prefer-ip-address: true #告诉服务端,服务实例以IP作为链接,而不是取机器名
    instance-id: springcloud-service-turbine-9999 #告诉服务端,服务实例的id,id要是唯一的

# 开启Feign对Hystrix的支持
feign:
  hystrix:
    enabled: true
  client:
    config:
      default:
        connectTimeout: 5000 # 指定Feign连接提供者的超时时限
        readTimeout: 5000 # 指定Feign从请求到获取提供者响应的超时时限

# 开启actuator的所有web终端
management:
  endpoints:
    web:
      exposure:
        include: "*"

# 设置服务熔断时限
hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

turbine:
  app-config: portal-service
  cluster-name-expression: new String("default")
  combine-host-port: true
  instanceUrlSuffix: /actuator/hystrix.stream #turbine默认监控actuator/路径下的端点,修改直接监控hystrix.stream
  #cluster-name-expression: metadata['cluster']
  #aggregator:
  # cluster-config: ribbon
  #instanceUrlSuffix:  /hystrix.stream #turbine默认监控actuator/路径下的端点,修改直接监控hystrix.stream
  • 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

image-20230114182023905

15.@SpringCloudApplication

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • @SpringBootApplication
  • @EnableDiscoveryClient
  • @EnableCircuitBreaker

组合注解,SpringBootApplication,注册中心客户端 EnableDiscoveryClient,支持 hystrix 的 EnableCircuitBreaker

16.超时时间

Ribbon 和 Hystrix和Feign 的超时时间配置的关系

  • 在 Spring Cloud 中使用 Feign 进行微服务调用分为两层:Hystrix 的调用和 Ribbon 的调用,Feign 自身的配置会被覆盖。
  • Hystrix 的超时时间和 ribbon 的超时时间存在一定的关系
  • 说明下,如果不启用 Hystrix,Feign 的超时时间则是 Ribbon 的超时时间,Feign 自身的配置也会被覆盖
Hystrix的超时时间=Ribbon的重试次数(包含首次) * (ribbon.ReadTimeout + ribbon.ConnectTimeout)

Ribbon重试次数(包含首次)= 1 + ribbon.MaxAutoRetries  +  ribbon.MaxAutoRetriesNextServer  +  (ribbon.MaxAutoRetries * ribbon.MaxAutoRetriesNextServer)
  • 1
  • 2
  • 3

五.zuul

1.微服务网关?

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供 REST API 的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix 中的 Zuul 就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

Zuu 包含了对请求的路由和过滤 两个最主要的功能:

其中路由功能负责将外部请求转发到具体的微服务实例上是实现外部访问统一入口的基础,过滤功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础;

Zuul 和 Eureka 进行整合,将 Zuul 自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获得其他微服务的信息,也即以后的访问微服务都是通过 Zuul.

2.Zuul 网关的作用

网关有以下几个作用:

  • 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
  • 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
  • 动态路由:动态的将请求路由到不同的后端集群中。
  • 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。

3.zuul 的功能?

使用 Zuul 的过滤器, 可以对请求的一些列信息进行安全认证/权限认证/身份识别/限流/记录日志等功能。只需要自定义 Filter 后继承 ZuulFilter 类即可。重写其 filterType、filterOrder、shouldFilter 以及 run 方法。 filterType 表示 filter 执行的时机:

其 value 值含义如下所示:

  • pre:在请求被路由之前调用 比如:登录验证
  • routing: 在请求被路由之中调用
  • post: 在请求被路由之后调用
  • error: 处理请求发生错误时调用
  • filterOrder 表示执行的优先级,值越小表示优先级越高
  • shouldFilter 则表示该 filter 是否需要执行

4.路由配置

zuul:
  sensitiveHeaders: Cookie,Set-Cookie,Authorization
  routes:
    portal:
      path: /portal-service/** #访问路径:http:/localhost:8888/portal-service/portal/1
      service-id: portal-service
    goods:
      path: /goods-service/** #http:/localhost:8888/goods-service/kwanGoodsInfo/1
      service-id: goods-service
  host:
    connect-timeout-millis: 5000 #超时时间
  prefix: /api #访问路径:http:/localhost:8888/api/portal-service/portal/1    http:/localhost:8888/api/goods-service/kwanGoodsInfo/1
  retryable: true
  ignored-services: portal-service #感略某个服务名,禁止通过该服务名访可
  #  ignored-services: *  #禁止通过所有的服务名访间
  ignored-patterns: /**/feign/** #不给匹配此棋式的路径进行路由·那么你到时候访间不到
  LogFilter:
    route:
      disable: true #用LogFilter过滤器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4.过滤器

filter 是 zuul 的核心组件,zuul 大部分功能都是通过过滤器来实现的。zuul 中定义了 4 种标准过滤器类型,这些过滤器类型对应于清求的典型生命周期。

  • PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient:或 Netfilx Ribbon 请求微服务
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header 收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR:在其他阶段发生错误时执行该过滤器。

5.过滤器禁用

zuul 过滤器的禁用,Spring Cloud 默认为 Zuul 编写并启用了一些过滤器,例妆如 DebugFilter,

FormBodyWrapperFilter 等,这些过滤器都存放在 spring-cloud-netflix-zuul 这个 jr 包里,一些场景下,想要禁用掉部分过滤器,该怎么办呢?只需在 application.properties 里设置 zuul…disable=true 例如,要禁用上面我们写的过滤器,这样配置就行了:

zuul.LogFilter.route.disable=true
  • 1

6.Zuul 的熔断降级

zuul 是一个代理服务,但如果被代理的服务突然断了,这个时候 zuul 上面会有出错信息,例如,停止了被调用的微服务;一般服务方自己会进行服务的熔断降级,但对于 zuul 本身,也应该进行 zuul 的降级处理:

Zuul 的 fallback 容错处理逻辑,只针对 timeout 异常处理,当请求被 Zuul 路由后,只要服务有返回(包括异常),都不会触发Zuul的fallback容错逻辑。

因为对于 Zuul 网关来说,做请求路由分发的时候,结果由远程服务运算的。那么远程服务反馈了异常信息,Zuul 网关不会处理异常,因为无法确定这个错误是否是应用真实想要反馈给客户端的。

六.config

1.分布式配置中心

Spring Cloud Config 为服务端和客户端提供了分布式系统的外部化配置支持。

配置服务器默认采用 git 来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过 git 客户端工具来方便的管理和访问配置内容。当然他也提供本地化文件系统的存储方式

配置中心用途 :

  • 变量获取
  • 加解密
  • 自动刷新
  • 高可用
  • 安全认证

2.为什么需要分布式配置中心

在分布式微服务体系中,服务的数量以及配置信息日益增多,比如各种服务器参数配置、各种数据库访问参数配置、各种环境下配置信息的不同、配置信息修改之后实时生效等等,传统的配置文件方式或者将配置信息存放于数据库中的方式已无法满足开发人员对配置管理的要求,如:

  • 安全性:配置跟随源代码保存在代码库中,容易造成配置泄漏
  • 时效性:修改配置,需要重启服务才能生效
  • 局限性:无法支持动态调整:例如日志开关、功能开关

3.常用分布式配置中心框架

  • Apollo(阿波罗):携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景
  • diamond:淘宝开源的持久配置中心,支持各种持久信息(比如各种规则,数据库配置等)的发布和订阅
  • XDiamond:全局配置中心,存储应用的配置项,解决配置混乱分散的问题,名字来源于淘宝的开源项目 diamond,前面加上一个字母 X 以示区别。
  • Qconf:奇虎 360 内部分布式配置管理工具,用来替代传统的配置文件,使得配置信息和程序代码分离,同时配置变化能够实时同步到客户端,而且保证用户高效读取配置,这使的工程师从琐碎的配置修改、代码提交、配置上线流程中解放出来,极大地简化了配置管理工作;
  • Disconf:百度的分布式配置管理平台,专注于各种分布式系统配置管理的通用组件和通用平台,提供统一的配置管理服务;
  • Spring Cloud Config:Spring Cloud 微服务开发的配置中心,提供服务端和客户端支持;

4.什么是 Spring Cloud Config?

Spring Cloud Config 是一个解决分布式系统的配置管理方案。它包含 Client 和 Server 两个部分,Server 提供配置文件的存储、以接口的形式将配置文件的内容提供出去,Client 通过接口获取数据、并依据此数据初始化自己的应用。

Spring cloud 使用 git 或 svn、也可以是本地存放配置文件,默认情况下使用 git。

5.配置路由规则

/{application)-{profile}.properties

http://localhost:8888/application-dev.properties

/{label}/{application}-{profile}.properties

http://localhost:8888/master/application-dev.properties

/[application}-{profile}.yml

http://localhost:8888/master/application-dev.yml

/{label}/{application}-{profile}.yml

http://localhost:8888/master/application-dev.properties

其中:

{application}表示配置文件的名字,对应的配置文件即 application,

{profile}表示环境,有dev、test、online 及默认,

{label}表示分支,默认我们放在 master 分支上,

通过浏览器上访问http:/localhost:8888/application,/dev/master

6.加解密

加解密是借助 JCE 实现的,默认情况下我们的 JRE 中自带了 JCE(Java Cryptography Extension),但是默认是一个有限长度的版本,我们这里需要一个不限长度的 JCE,JCE 是对称加密,安全性相对非对称加密低一点,但是使用更加方便一点

下载完成之后解压,把得到到两个 Jar 包复制到$JAVA_HOME\jre\lib\security 目录下。

在我们的 configserver 项目中的 bootstrap.yml 配置文件中加入如下配置项:

encrypt:
  key: Thisismysecretkey
  • 1
  • 2

访问下面连接进行单个数据加密,这里是 post 请求,不是 get

#加密
http://localhost:7001/encrypt

#解密
http:/192.168.6.817001/decrypt
  • 1
  • 2
  • 3
  • 4
  • 5

可能会报错,需要添加账号密码

在这里插入图片描述

username='{cipher)516269d5f6af298bf843b31c847ad705d8305e784c394c7395a55a0742d8b69d'
  • 1

需要使用{cipher}的密文形式,单引号不要忘记了。然后 push 到远程。

加解密在 config-server 服务中进行的,解密过程不需要在客户端进行配置

7.加密过程安全认证

1.添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
2.config 配置

在 config 的配置文件中配置

spring:
  security:
    user:
      password: user123 #配置登陆的密码是user123
      name: user #配置登陆的账户是user
  • 1
  • 2
  • 3
  • 4
  • 5

或者

显式指定账号密码,指定账户密码的优先级要高于 URI

spring:
  cloud:
    config:
      label: ${spring.profiles.active} #指定Git仓库的分支,对应config server 所获取的配置文件的{label}
      discovery:
        enabled: true #表示使用服务发现组件中的configserver ,而不是自己指定config server的uri,默认为false
        service-id: microservice-config-server #服务发现中configserver的serverId
      fail-fast: true #失败快速响应
      username: user
      password: user123
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
3.客户端配置
spring:
  cloud:
    config:
      uri: http://user:user123@localhost:7001/
  • 1
  • 2
  • 3
  • 4

7.局部刷新

Spring Boot 的 actuator 提供了一个刷新端点/refresh,添加依赖

spring-boot-starter-actuator,可用于配置的刷新;

<!--springboot的一个监控actuator-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

在 Controller 上添加注解@RefreshScope,添加这个注解的类会在配置更新时得到特殊的处理;

打开 web 访问端点

management,endpoints.web.exposure.include=“*”
  • 1

访问http:/localhost:8080/actuator/refresh 进行手动刷新配置;必须要 post 方式访问这个接口

8.详解@RefreshScope

8.全局刷新

全局刷新需要使用到消息总线

前面使用/actuator/refresh 端点手动刷新配置虽然可以实现刷新,但所有微服务节点的配置都需要手动去刷新,如果微服务非常多,其工作量非常庞大。因此实现配置的自动刷新是志在必行,Spring Cloud Bus 就可以用来实现配置的自动刷新;

Spring Cloud Bus 使用轻量级的消息代理(例如 RabbitMQ、Kafka 等)广播传播状态的更改(例如配置的更新)或者其他的管理指令,可以将 Spring Clou Bus 想象成一个分布式的 Sprina Boot Actuator;

#形置rabbitmq
spring.rabbitmq.host=192.168.10.128
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password-guest
#开启spring cloud bus默认是开启的,也可以省略速形置
spring.cloud.bus.enabled=true
打开所有的web访问端点
management.endpoints.web.exposure.include=*
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

各个微服务(客户端),其他各个微服务用于接收消息,那么也需要有 spring cloud bus 的依赖和 RabbitMQ 的连接信息;I

然后 post 方式请求地址,如果返回成功,则 RabbitMQ 将收到消息,然后微服务会消费消息,config 的所有客户端的微服务配置都会动态刷新;

http:/localhost::8888/actuator//bus-refresh
  • 1

2.什么是总线

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

3.消息总线原理

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

通过使用 Spring Cloud Bus 与 Spring Cloud Config 的整合,并以 RabbitMQ 作为消息代理,实现了应用配置的动态更新。

img

4.SpringCloudBus

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

Spring Cloud Bus 能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。

利用消息总线触发一个服务端 ConfigServer 的/bus/refresh 端点,而刷新所有客户端的配置

img

当我们将系统启动起来之后,“Service A”的三个实例会请求 Config Server 以获取配置信息,Config Server 根据应用配置的规则从 Git 仓库中获取配置信息并返回。

此时,若我们需要修改“Service A”的属性。首先,通过 Git 管理工具去仓库中修改对应的属性值,但是这个修改并不会触发“Service A”实例的属性更新。我们向“Service A”的实例 3 发送 POST 请求,访问/bus/refresh接口。此时,“Service A”的实例 3 就会将刷新请求发送到消息总线中,该消息事件会被“Service A”的实例 1 和实例 2 从总线中获取到,并重新从 Config Server 中获取他们的配置信息,从而实现配置信息的动态更新。

而从 Git 仓库中配置的修改到发起/bus/refresh的 POST 请求这一步可以通过 Git 仓库的 Web Hook 来自动触发。由于所有连接到消息总线上的应用都会接受到更新请求,所以在 Web Hook 中就不需要维护所有节点内容来进行更新,从而解决了通过 Web Hook 来逐个进行刷新的问题。

通过destination参数来指定需要更新配置的服务或实例。

七.Sleuth

1.什么是 Sleuth?

Spring Cloud Sleuth 是 Spring Cloud 提供的分布式系统服务链追踪组件,它大量借用了 Google 的 Dapper,Twitter 的 Zipkin。学习 Spring Cloud Sleuth,最好先对 Zipkin 有一些了解,对 span、trace 这些概念有相应的认识。

2.为什么需要 Sleuth?

链路追踪:通过 Sleuth 可以很清楚的看出一个请求都经过了那些服务,可以很方便的理清服务间的调用关系等。

性能分析:通过 Sleuth 可以很方便的看出每个采样请求的耗时,分析哪些服务调用耗时,当服务调用的耗时随着请求量的增大而增大时, 可以对服务的扩容提供一定的提醒。

数据分析,优化链路:对于频繁调用一个服务,或并行调用等,可以针对业务做一些优化措施。

可视化错误:对于程序未捕获的异常,可以配合 Zipkin 查看。

  • 快速发现问题
  • 判断故障影响范围
  • 梳理服务依赖以及依赖的合理性
  • 分析链路性能问题以及实时容量规划

单纯的理解链路追踪,就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。

3.链路追踪相关产品

常见的链路追踪技术有下面这些

cat:由大众点评开源,基于 Java 开发的实时应用监控平台,包括实时应用监控,业务监控 。 集成方案是通过代码埋点的方式来实现监控,比如: 拦截器,过滤器等。 对代码

的侵入性很大,集成成本较高。风险较大。

zipkin:由 Twitter 公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括:数据的收集、存储、查找和展现。该产品结合

spring-cloud-sleuth 使用较为简单, 集成很方便, 但是功能较简单。

pinpoint:Pinpoint 是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件, UI 功能强大,接入端无代码侵入。

skywalking:本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件, UI 功能较强,接入端无代码侵入。目前已加入 Apache 孵化器。

Sleuth:SpringCloud 提供的分布式系统中链路追踪解决方案。

注意: SpringCloud alibaba 技术栈中并没有提供自己的链路追踪技术的,我们可以采用Sleuth +Zinkin来做链路追踪解决方案

4.基本术语?

Sleuth 基本概念涉及到三个专业术语: spanTraceAnnotations

span :基本工作单位,每次发送一个远程调用服务就会产生一个 Span。Span 是一个 64 位的唯一 ID。通过计算 Span 的开始和结束时间,就可以统计每个服务调用所花费的时间。。

Trace :一系列 Span 组成的树状结构,一个 Trace 认为是一次完整的链路,内部包含 n 多个 Span。Trace 和 Span 存在一对多的关系,Span 与 Span 之间存在父子关系。

Annotations :用来及时记录一个事件的存在,一些核心 annotations 用来定义一个请求的开始和结束。

开始与结束:

  • cs(Client Sent) :客户端发起一个请求,这个 annotation 描述了这个 span 的开始;
  • sr(Server Received) :服务端获得请求并准备开始处理它,如果 sr 减去 cs 时间戳便可得到网络延迟;
  • ss(Server Sent) :请求处理完成(当请求返回客户端),如果 ss 减去 sr 时间戳便可得到服务端处理请求需要的时间;
  • cr(Client Received) :表示 span 结束,客户端成功接收到服务端的回复,如果 cr 减去 cs 时间戳便可得到客户端从服务端获取回复的所有所需时间。

核心: 为什么能够进行整条链路的追踪? 其实就是一个 Trace ID 将 一连串的 Span 信息连起来了。根据 Span 记录的信息再进行整合就可以获取整条链路的信息。

img

5.什么是 zipkin

Zipkin 是一种分布式跟踪系统。它有助于收集解决微服务架构中的延迟问题所需的时序数据。它管理这些数据的收集和查找。Zipkin 的设计基于 Google Dapper 论文。应用程序用于向 Zipkin 报告时序数据。Zipkin UI 还提供了一个依赖关系图,显示了每个应用程序通过的跟踪请求数。如果要解决延迟问题或错误,可以根据应用程序,跟踪长度,注释或时间戳对所有跟踪进行筛选或排序。选择跟踪后,您可以看到每个跨度所需的总跟踪时间百分比,从而可以识别有问题的应用程序。

6.zipkin 的坑

SpringBoot2.2.x 以后的版本 集成 zipkin 的方式改变了,原来是通过@EnablezipkinServer注解,现在这个注解不起作用了。

服务端应该通过下载 jar 包,然后 运行 jar 包来集成 。 可以选择版本下载,要选择后缀为 -exec.jar 的,之后切换到 jar 包在的路径,然后用 java -jar 的方式启动就可以了(默认端口号是 9411 ,所以如果提供了 zipkin client,需要把它的配置文件中的端口号改成 9411,对应的配置应该是 zipkin: base-url: http://localhost:9411)

spring:
  cloud:
    config:
      fail-fast: false #客户端连接失败时开启重试,需要结合spring-retry、spring-aop
      label: main #获取配置文件的分支名
      name: application-portal8081 #获取的文件名
      profile: portal8081 #文件后缀
      uri: http://localhost:7001 #配置中心服务端地址
  zipkin:
    base-url: http://localhost:9411
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

7.zipkin 下载安装

1.下载地址
https://repo1.maven.org/maven2/io/zipkin/zipkin-server/
  • 1
2.下载 jar 包

可以使用迅雷下载,或者科学上网

image-20230116191405230

3.启动服务端

启动报错,看看端口是否被占用

java -jar zipkin-server-2.23.9-exec.jar
  • 1

image-20230116215157698

4.访问控制台
http://127.0.0.1:9411/zipkin
  • 1

image-20230116191538530

8.客户端配置

1.pom 依赖
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
2.配置文件
#端口号,指定端口,不然默认是8080,会出现冲突
server:
  port: 8081

spring:
  cloud:
    config:
      fail-fast: false #客户端连接失败时开启重试,需要结合spring-retry、spring-aop
      label: main #获取配置文件的分支名
      name: application-portal8081 #获取的文件名
      profile: portal8081 #文件后缀
      uri: http://localhost:7001 #配置中心服务端地址
  zipkin:
    base-url: http://localhost:9411 #zipkin地址 默认值就是0.1,代表收集10%的请求追踪信息。
    discovery-client-enabled: false
    sender:
      type: web
  sleuth:
    sampler:
      percentage: 0.1 #收集百分比
eureka:
  client:
    service-url:
      defaultZone: http://eureka8767:8767/eureka/,http://eureka8768:8768/eureka/,http://eureka8769:8769/eureka/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

9.控制台

需要调用一下服务,才能在控制台查询到

  • 可以看到请求过的链路信息
  • 包含的微服务
  • 耗时信息
  • 还能通过筛选条件进行查询
  • 可以通过 trace id 进行查询
http://127.0.0.1:9411/zipkin
  • 1

image-20230116190428674

详细面板可以看到 :

  • 具体的统计信息
  • 统计耗时信息,开始时间以及结束时间,以及每一段的耗时信息
  • 标签信息,接口信息
  • 请求方式,请求方法
  • Span ID
  • Trace ID
  • 服务名
  • 服务数
  • 深度
  • 跨度总数

image-20230116190454228

10.依赖关系图

image-20230116191021420

11.采样数据

zikpin 日志采样率设置为 0.1,就是 10 次只能有一次被记录下来。当时看到这个问题很郁闷,那 9 次的请求咋办,日志不完整怎么排查错误呢?

日志其实是完整的,日志可以用 logback 收集并保存成文件。所有的请求信息 都会被这个文件所记录,只是 zikpin 侧重于链路追踪,并不是排查错误,更多的是我们知道服务直接调用耗时和服务直接依赖关系而已。所以不需要完整日志,只需要 0.1 比例就够了

sleuth:
  sampler:
    probability: 0.1
  • 1
  • 2
  • 3

而这个采样其实只是对 zipkin 有效,只是在 zipkin 界面显示 0.1 的日志而已,并不是后台日志也都是只收集 0.1。

而 logback 会把我们所有请求信息全部记录下来,不会遗漏,这样的话就可以顺利排错。

12.数据持久化

zipkin 中的数据默认是保存在内存中的,重启 sleuth 服务,数据会被清空,这显然是不合适的.

zipkin 可以集成 elasticsearch,或者 mysql 进行数据的持久化,这样服务重启,数据不丢失.

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号