Spring(六)Spring Cloud

文章目录

本系列文章:
  Spring(一)控制反转、两种IOC容器、自动装配、作用域
  Spring(二)延迟加载、生命周期、面向切面、事务
  Spring(三)父子容器、国际化、异步调用、定时器、缓存
  Spring(四)Spring MVC
  Spring(五)Spring Boot
  Spring(六)Spring Cloud

一、初识Spring Cloud

  微服务是一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTFUL API进行通信协作。被拆分成的每一个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。

  在微服务架构中,通常会使用以下两种服务调用方式:

  1. 使用HTTP的RESTFUL API或轻量级的消息发送协议,实现信息传递与服务调用的触发。
  2. 使用类似RabbitMQ等一些提供可靠异步交换的中间件。

  微服务优点:松耦合,聚焦单一业务功能,无关开发语言,团队规模降低。在开发中,不需要了解多有业务,只专注于当前功能,便利集中,功能小而精。微服务一个功能受损,对其他功能影响并不是太大,可以快速定位问题。微服务只专注于当前业务逻辑代码,不会和 html、css 或其他界面进行混合。可以灵活搭配技术,独立性比较舒服。
  微服务缺点:随着服务数量增加,管理复杂,部署复杂,服务器需要增多,服务通信和调用压力增大,运维工程师压力增大,人力资源增多,系统依赖增强,数据一致性,性能监控。

  Spring Cloud的设计目标:协调各个微服务,简化分布式系统开发。

1.1 Spring Cloud简介

  SpringCloud是一个基于SpringBoot实现的微服务架构开发工具。它为微服务架构中涉及的配置管理、服务治理、断路器、智能路由微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
  Spring Cloud完整技术:

  Spring Cloud组件架构:

  几个核心组件:

  Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里。
  Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡(客户端的负载均衡),从一个服务的多台机器中选择一台。
  Feign:基于Feign的动态代理理机制,根据注解和选择的机器器,拼接请求URL地址,发起请求。
  Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
  Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务。

  流程:

  1. 请求统一通过API网关(Zuul)来访问内部服务。
  2. 网关接收到请求后,从注册中心(Eureka)获取可用服务。
  3. 由Ribbon进行均衡负载后,分发到后端具体实例。
  4. 微服务之间通过Feign进行通信处理业务。
  5. Hystrix负责处理服务超时熔断。
  6. Turbine监控服务间的调用和熔断相关指标。

1.2 Spring Cloud的基础:Spring Boot

  Spring Boot是Spring Cloud的基础。
  Spring Boot的代码结构有三大块:

  • 1、src/main/java:Java代码目录。
  • 2、src/main/resources:配置目录,该目录用来存放应用的一些配置信息,比如应用名、服务端口、数据库连接信息等。
  • 3、src/test:单元测试目录。

  SpringBoot 默认的配置文件是src/main/resources/application.properties ,当然也可以使用yml来实现配置文件的功能。
  SpringBoot默认将Web应用打包成jar的方式,因为默认的Web模块依赖会包含嵌入式的Tomcat,这样使得应用jar自身就具备了提供Web服务的能力。
  SpringBoot的Starter Pom采用spring-boot-starter-*的命名方式,*代表一个特别的应用功能模块,如web、test。
  在定义属性时,常见的有properties和YAML两种方式。简单来说,YAML无法通过@PropertySource注解来加载配置。但是,YAML将属性加载到内存中保存的时候是有序的。

1.3 Spring Cloud的优缺点

1.3.1 优点
  • 产出于Spring大家族,Spring在企业级开发框架中无人能敌,来头很大,可以保证后续的更新、完善。
  • 组件丰富,功能齐全。Spring Cloud 为微服务架构提供了非常完整的支持。例如、配置管理、服务发现、断路器、微服务网关等。
  • Spring Cloud 社区活跃度很高,教程很丰富,遇到问题很容易找到解决方案。
  • 服务拆分粒度更细,耦合度比较低,有利于资源重复利用,有利于提高开发效率。
  • 可以更精准的制定优化服务方案,提高系统的可维护性。
  • 减轻团队的成本,可以并行开发,不用关注其他人怎么开发,先关注自己的开发。
  • 微服务可以是跨平台的,可以用任何一种语言开发。
  • 适于互联网时代,产品迭代周期更短。
1.3.2 缺点
  • 微服务过多,治理成本高,不利于维护系统。
  • 分布式系统开发的成本高(容错,分布式事务等)对团队挑战大。

  总的来说优点大过于缺点,目前看来Spring Cloud是一套非常完善的分布式框架,目前很多企业开始用微服务、Spring Cloud的优势是显而易见的。

1.4 SpringCloud和Dubbo的区别*

  首先,他们都是分布式管理框架。
  Dubbo是二进制传输,占用带宽会少一点。SpringCloud是HTTP传输,带宽会多一点,同时使用http协议一般会使用JSON报文,消耗会更大。
  Dubbo开发难度较大,所依赖的jar包有很多问题大型工程无法解决。SpringCloud对第三方的继承可以一键式生成,天然集成。
  最大的区别:Spring Cloud抛弃了Dubbo的RPC通信,采用的是基于HTTP的REST方式
  这两种方式各有优劣。虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适。

DubboSpring Cloud
服务注册中心ZookeeperSpring Cloud Netflix Eureka
服务调用方式RPCREST API
服务监控Dubbo-monitorSpring Boot Admin
断路器不完善Spring Cloud Netflix Hystrix
服务网关Spring Cloud Netflix Zuul
分布式配置Spring Cloud Config
服务跟踪Spring Cloud Sleuth
消息总线Spring Cloud Bus
数据流Spring Cloud Stream
批量任务Spring Cloud Task
  • 1、Dubbo的优点
      Dubbo 支持RPC调用,服务之间的调用性能会很好
      支持多种序列化协议,如 Hessian、HTTP、WebService。
      Dubbo Admin后台管理功能强大,提供了路由规则、动态配置、访问控制、权重调节、均衡负载等功能
      中文社区文档较为全面。
  • 2、Dubbo的一些问题
      Registry(注册中心) 严重依赖第三方组件(zookeeper或者redis),当这些组件出现问题时,服务调用很快就会中断。
      Dubbo只支持RPC调用。使得服务提供方(抽象接口)与调用方在代码上产生了强依赖,服务提供者需要不断将包含抽象接口的jar包打包出来供消费者使用。一旦打包出现问题,就会导致服务调用出错,并且以后发布部署会成很大问题(太强的依赖关系)。
      Dubbo RPC本身不支持跨语言(可以用跨语言RPC框架解决,或者自己再包一层REST服务,提供跨平台的服务调用实现,但相对麻烦很多)。
  • 3、为什么Dubbo比Spring Cloud性能要高一些?
      因为Dubbo采用单一长连接和NIO异步通讯(保持连接/轮询处理),使用自定义报文的TCP协议,并且序列化使用定制Hessian2框架,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况,但不适用于传输大数据的服务调用。而Spring Cloud直接使用HTTP协议(但也不是强绑定,也可以使用RPC库,或者采用HTTP2.0+长链接方式(Fegin可以灵活设置))。

  1.服务调用方式 dubbo是RPC springcloud Rest Api。
  2.注册中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper。
  3.服务网关,dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。

1.5 微服务之间如何独立通讯的*

  同步:Dobbo通过RPC;SpringCloud通过REST API。
  异步:消息队列,如RabbitMq、ActiveM、Kafka等。

  • REST HTTP 协议
      REST 请求在微服务中是最为常用的一种通讯方式, 它依赖于 HTTP\HTTPS 协议。RESTFUL 的特点是:
  1. 每一个 URI 代表 1 种资源。
  2. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作。GET 用来获取资源,POST 用来新建资源(也可以用于更新资源), PUT 用来更新资源,DELETE 用来删除资源。
  3. 通过操作资源的表现形式来操作资源。
  4. 资源的表现形式是 XML 或者 HTML。
  5. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
  • RPC TCP协议
      RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。它的工作流程是这样的:
  1. 执行客户端调用语句,传送参数。
  2. 调用本地系统发送网络消息。
  3. 消息传送到远程主机。
  4. 服务器得到消息并取得参数。
  5. 根据调用请求以及参数执行远程过程(服务)
  6. 执行过程完毕,将结果返回服务器句柄。
  7. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果。
  8. 消息传回本地主机。
  9. 客户端句柄由本地主机的网络服务接收消息。
  10. 客户端接收到调用语句返回的结果数据。

1.6 Spring Cloud的子项目

  • Spring Cloud Config
      集中配置管理工具,分布式系统中统一的外部配置管理,默认使用Git来存储配置,可以支持客户端配置的刷新及加密、解密操作。
  • Spring Cloud Netflix
      Netflix OSS 开源组件集成,包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。

Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略;
Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;
Feign:基于Ribbon和Hystrix的声明式服务调用组件;
Zuul:API网关组件,对请求提供路由及过滤功能。

  • Spring Cloud Bus
      用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状态更改很有用(例如配置更改事件)。
      可以简单理解为Spring Cloud Bus的作用就是管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式。当然作为消息总线的Spring Cloud Bus可以做很多事,而不仅仅是客户端的配置刷新功能。
      而拥有了Spring Cloud Bus之后,我们只需要创建一个简单的请求,并且加上@ResfreshScope注解就能进行配置的动态修改了。

  • Spring Cloud Consul
      基于Hashicorp Consul的服务治理组件。

  • Spring Cloud Security
      安全工具包,对Zuul代理中的负载均衡OAuth2客户端及登录认证进行支持。

  • Spring Cloud Sleuth
      Spring Cloud应用程序的分布式请求链路跟踪,支持使用Zipkin、HTrace和基于日志(例如ELK)的跟踪。

  • Spring Cloud Stream
      轻量级事件驱动微服务框架,可以使用简单的声明式模型来发送及接收消息,主要实现为Apache Kafka及RabbitMQ。

  • Spring Cloud Task
      用于快速构建短暂、有限数据处理任务的微服务框架,用于向应用中添加功能性和非功能性的特性。

  • Spring Cloud Zookeeper
      基于Apache Zookeeper的服务治理组件。

  • Spring Cloud Gateway
      API网关组件,对请求提供路由及过滤功能。

  • Spring Cloud OpenFeign
      基于Ribbon和Hystrix的声明式服务调用组件,可以动态创建基于Spring MVC注解的接口实现用于服务调用,在Spring Cloud 2.0中已经取代Feign成为了一等公民。

  • 【Spring Cloud比较核心的组件】
      Eureka:服务注册于发现。
      Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
      Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
      Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
      Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。

二、服务治理Eureka*

  Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中, 以实现Spring Cloud的服务发现功能。Eureka现在已经从1.0升级到2.0,可惜的是Eureka不在开源,但也不影响我们的使用。 由于基于REST服务,自然而然的就能想到,这个服务一定会有心跳检测、健康检查和客户端缓存等机制。
【主要功能:服务注册、服务发现】
  服务治理可以说是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册和发现。

  • 1、服务注册
      在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
      同时,服务注册中心还需要以心跳的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,达到排除故障服务的效果。
  • 2、服务发现
      由于在服务治理框架下运作,服务间的调用不再通过指定具体的实例地址来实现,而是通过向服务名发起请求调用实现。所以,服务调用方在调用服务提供方接口的时候,并不知道具体的服务实例位置。因此,调用方需要向服务注册中心咨询服务,并获取所有服务的实例清单,以实现对具体服务实例的访问。

【服务端、客户端】
  Spring Cloud Eureka,既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用Java编写,所以Eureka主要适用于通过Java实现的分布式系统,或是与JVM兼容语言构建的系统。同时,由于Eureka服务端的服务治理机制提供了完备的RESTful API,所以它也支持将非Java语言构建的微服务应用纳入Eureka的服务治理体系中来。
  Eureka包括两个端:

  1. Eureka Server:注册中心服务端,用于维护和管理注册服务列表。
  2. Eureka Client:注册中心客户端,向注册中心注册服务的应用都可以叫做Eureka Client(包括Eureka Server本身)。
  • Eureka服务端
      也服务注册中心。它同其他服务注册中心一样,支持高可用配置。如果Eureka以集群模式部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其他分片会把它们的状态再次同步回来。
      注册中心里有一个注册表,保存了各个服务所在的机器和端口号。
      服务端需要的依赖:spring-cloud-starter-netflix-eureka-server。eureka服务端的标识,标志着此服务是做为注册中心。
      服务端配置示例:
spring.application.name=eureka-server //服务名称
server.port=8000      //服务端口
eureka.client.register-with-eureka=false //自身不做为服务注册到注册中心
eureka.client.fetch-registry=false   //从注册表拉取信息
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/  服务注册地址

  运行服务spring-cloud-eureka。
  运行成功后访问localhost:8000,会显示Eureka提供的服务页面。

  • Eureka客户端
      主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。
      Eureka客户端负责将这个服务的信息注册到Eureka服务端中。
      客户端所需的依赖:
spring-cloud-starter-netflix-eureka-client     eureka客户端所需依赖。
spring-boot-starter-web       web服务所需,内置tomcat服务器。

  客户端application.properties配置示例:

spring.application.name=eureka-client-a
server.port=8001
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/

  运行服务spring-cloud-clientA。
  运行成功后访问localhost:8000,会显示eureka提供的服务页面。

2.1 Eureka理论

  构建Eureka服务治理有三个核心角色:

  1. 服务注册中心:Eureka提供的服务端,提供服务注册和发现的功能;
  2. 服务提供者:提供服务的应用,遵循Eureka通信机制的应用。它将自己注册到Eureka Server中,以供其他应用发现;
  3. 服务消费者:消费者应用从服务注册中心获取服务列表,从而让消费者知道可以从哪个服务提供者调用其所需的服务。
2.1.1 服务提供者

  “服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。
  在注册完服务之后,服务提供者会维护一个心跳,用来通知注册中心“我还活着”,以防被注册中心的“剔除任务”将服务实例从服务列表中排除出去,这个行为称之为服务续约
  关于续约的两个属性(以下为默认值):

	# 定义服务续约任务的调用间隔时间,单位:秒
	eureka.instance.lease-renewal-interval-in-seconds=30
	# 定义服务失效的时间,单位:秒
	eureka.instance.lease-expiration-duration-in-seconds=90
2.1.2 服务消费者

  当启动服务消费者的时候,它会发送一个rest请求给服务注册中心,来获取 上面注册的服务清单。为了性能考虑,Eureka server会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次。若希望修改缓存单的更新时间,可以通过eureka.client.register.fetch-interval-seconds=30参数进行修改,该参数默认值为30,单位为秒。
  服务消费者在拿到服务清单以后,通过服务名可以获得提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详情信息,所以客户端可以根据自己的需要决定具体使用哪个实例,在ribbon中会采默认采用轮训的方式进行调用,从而实现客户端的负载均衡。
  在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用到被关闭的实例。所以在客户端程序中,当服务实例进行正常的关闭操作时,它会触发一个服务下线的rest请求给eureka server注册中心,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(down),并把该下线事件传播出去。然后注册中心之间服务同步就会都知道该服务客户端已下线。
  有些时候服务实例并不一定是正常下线,有可能是内存溢出、网络故障等问题使得服务不能正常工作,而服务注册中心没有收到“服务下线”的请求。为了从服务列表中将这些无法提供服务的实例剔除掉,eureka server注册中心在启动的时候会创建一个定时任务,默认每隔一段时间(60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

2.1.3 服务续约/获取注册列表信息/服务下线/服务剔除*
  • 服务续约Renew(30秒)
      Eureka客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知 Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。
  • 获取注册列表信息Fetch Registries(30秒)
      Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不同,Eureka 客户端自动处理。
      如果由于某种原因导致注册列表信息不能及时匹配, Eureka客户端则会重新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。 Eureka客户端和Eureka服务器可以使用JSON / XML格式进行通讯。在默认的情况下Eureka客户端使用压缩 JSON格式来获取注册列表的信息。
  • 服务下线Cancel
      Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();。
  • 服务剔除Eviction(90秒)
      在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。

2.2 Eureka使用

  Eureka服务端所用的注解是@EnableEurekaServer,该注解的作用是启动一个服务注册中心提供给其他应用进行对话。该注解一般加在启动类上,示例:

@EnableEurekaServer
@SpringBootApplication
public class Application {
	public static void main(String[] args){
		//...
	}	
}

  在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己(在Eureka的服务治理设计中,所有的节点即使服务提供方,也是服务消费方)。如果要禁用它的客户端注册行为,需要设置属性eureka.client.register-with-eureka值为false。配置实例的主机名,示例:

	eureka.instance.hostname = localhost

  Eureka客户端所用的注解是@EnableDiscoveryClient,该注解让应用获得服务发现的能力,也一般加在启动类上,示例:

@EnableDiscoveryClient
@SpringBootApplication
public class Application {
	public static void main(String[] args){
		//...
	}	
}

  Eureka客户端需要配置服务名和服务注册中心的地址,示例:

	spring.application.name=hello-service
	eureka.client.serviceUrl.defaultZone=http:localhost:1111/eureka

2.3 Eureka配置

  Eureka客户端的配置主要分为两个方面:

  1. 服务注册相关的配置信息,包括服务注册中心的地址、服务获取的间隔时间可用区域等。
  2. 服务实例相关的配置信息,包括服务实例的名称、IP地址、端口号等。

  Eureka客户端的配置大多数时候不需要修改。

参数名说明默认值
eureka.client.enabled启用Eureka客户端true
eureka.client.registry-fetch-interval-seconds从Eureka服务端获取注册信息的时间间隔,单位为秒30
eureka.client.instance-info-replication-interval-seconds更新实例信息的变化到Eureka服务端的间隔时间,单位为秒30
initialInstanceInfoReplicationIntervalSeconds初始化实例信息到Eureka服务端的间隔时间,单位为秒30
eureka.client.eureka-service-url-poll-interval-seconds轮询Eureka服务端地址更改的时间间隔,单位为秒300
eureka.client.eureka-server-read-timeout-seconds读取Eureka Server信息的超时时间,单位为秒8
eureka.client.eureka-server-connect-timeout-seconds连接Eureka Server超时时间,单位为秒5
eureka.client.eureka-server-total-connections从Eureka客户端到所有Eureka服务端的连接总数200
eureka.client.eureka-server-total-connections-per-host从Eureka客户端到所有Eureka服务端主机的连接总数50
eureka.client.eureka-connection-idle-timeout-secondsEureka服务端连接的关闭空闲时间,单位为秒30
eureka.client. heartbeat-executor-thread-pool-size心跳连接池的初始化线程数2
eureka.client.heartbeat-executor-exponential-back-off-bound心跳超时重试延迟时间的最大乘数值10
eureka.client.cache-refresh-executor-thread-pool-size缓存刷新线程池的初始化线程数2
eureka.client.cache-refresh-executor-exponential-back-off-bound缓存刷新重试延迟时间的最大乘数值10
eureka.client.use-dns-for-fetching-service-urls使用DNS来获取Eureka服务端的serviceUrlfalse
eureka:client:register-with-eureka是否要将自身的实例信息注册到Eureka服务端true
eureka.client.prefer-same-zone-eureka是否偏好使用处于相同Zone的Eureka服务端true
eureka.client.filter-only-up-instances获取实例时是否过滤,仅保留UP状态的实例true
eureka.client.fetch-registry是否从Eureka服务端获取注册信息true

  健康检测:默认情况下,Eureka中各个服务实例的健康检测并不是通过spring-boot-actuator模块的/health端点来实现,而是依靠客户端心跳的方式来保持服务实例的存活。

2.4 Eureka相关问题

2.4.1 Eureka和Zookeeper的区别*

  A:高可用;C:一致性;P:分区容错性。
  Zookeeper保证了CP(强一致性和分区容错性),Eureka保证了AP( 可用性和分区容错性)

  zookeeper当主节点故障时,zk会在剩余节点重新选择主节点,耗时较长,虽然最终能够恢复,但是选取主节点期间会导致服务不可用,这是不能容忍的。
  eureka各个节点是平等的,一个节点挂掉,其他节点仍会正常保证服务

  1. 当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的信息,但不能容忍直接down掉不可用。也就是说,服务注册功能对高可用性要求比较高,但zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新选leader。问题在于,选取leader时间过长,30 ~ 120s,且选取期间zk集群都不可用,这样就会导致选取期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够恢复,但是漫长的选取时间导致的注册长期不可用是不能容忍的。
  2. Eureka保证了可用性,Eureka各个节点是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点仍然可以提供注册和查询服务。而Eureka的客户端向某个Eureka注册或发现时发生连接失败,则会自动切换到其他节点,只要有一台Eureka还在,就能保证注册服务可用,只是查到的信息可能不是最新的。除此之外,Eureka还有自我保护机制,如果在15分钟内超过85%的节点没有正常的心跳,那么Eureka就认为客户端与注册中心发生了网络故障,此时会出现以下几种情况:

  ①、Eureka不再从注册列表中移除因为长时间没有收到心跳而应该过期的服务。
  ②、Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点仍然可用)。
  ③、当网络稳定时,当前实例新的注册信息会被同步到其他节点。

  因此,Eureka可以很好地应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样使整个微服务瘫痪。

  1. Eureka取CAP的AP,注重可用性,Zookeeper取CAP的CP注重一致性。
  2. Zookeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但选举期间不可用。
  3. Eureka的自我保护机制,会导致一个结果就是不会再从注册列表移除因长时间没收到心跳而过期的服务。依然能接受新服务的注册和查询请求,但不会被同步到其他节点。不会服务瘫痪。
  4. Zookeeper有Leader和Follower角色,Eureka各个节点平等。
  5. Zookeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题。
  6. eureka本质是一个工程,Zookeeper只是一个进程。
2.4.2 什么是失效剔除*

  当服务非正常下线时,可能服务注册中心没有收到下线请求,注册中心会创建一个定时任务(默认60s),将没有在固定时间(默认90s)内续约的服务剔除掉。

2.4.3 什么是自我保护机制*

  在运行期间,注册中心会统计心跳失败比例在15分钟之内是否低于85%。如果低于,注册中心会将当前注册实例信息保护起来,不再剔除这些实例信息,当网络恢复后,退出自我保护机制。
  自我保护机制让服务集群更稳定、健壮。
  我们如果在Eureka Server的管理界面发现如下的内容,就说明已经触发了自我保护机制。

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.

  当触发自我保护机制后,Eureka Server就会锁定服务列列表,不让服务列表内的服务过期,不过这样我们在访问服务时,得到的服务很有可能是已经失效的实例。如果是这样,我们就会无法访问到期望的资源,会导致服务调用失败,所以这时我们就需要有对应的容错机制、熔断机制。
  关闭自我保护机制只需要将eureka.server.enable-self-preservation设置为false即可。

2.4.4 springcloud如何实现服务的注册

  1、服务发布时,指定对应的服务名,将服务注册到 注册中心(eureka zookeeper)
  2、注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用ribbon或feign进行服务直接的调用发现。

2.4.5 服务注册和发现是什么意思?Spring Cloud如何实现?

  当我们开始一个项目时,我们通常在属性文件中进行所有的配置。随着越来越多的服务开发和部署,添加和修改这些属性变得更加复杂。有些服务可能会下降,而某些位置可能会发生变化。手动更改属性可能会产生问题。 Eureka服务注册和发现可以在这种情况下提供帮助。由于所有服务都在Eureka服务器上注册并通过调用Eureka服务器完成查找,因此无需处理服务地点的任何更改和处理。

2.4.6 作为服务注册中心,Eureka比Zookeeper好在哪里*
  1. Eureka保证的是可用性和分区容错性,Zookeeper 保证的是一致性和分区容错性 。
  2. Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障。而不会像zookeeper那样使整个注册服务瘫痪。
2.4.7 Eureka是如何进行服务注册的*

  1、每30s发送心跳检测重新进行租约,如果客户端不能多次更新租约,它将在90s内从服务器注册中心移除。
  2、注册信息和更新会被复制到其他Eureka节点,来自任何区域的客户端可以查找到注册中心信息,每30s发生一次复制来定位他们的服务,并进行远程调用。
  3、客户端还可以缓存一些服务实例信息,所以即使Eureka全挂掉,客户端也是可以定位到服务地址的。

  • 消费者是如何发现服务提供者的
      1、当一个服务实例启动,会将它的ip地址等信息注册到eureka;
      2、当a服务调用b服务,a服务会通过Ribbon检查本地是否有b服务实例例信息的缓存;
      3、Ribbon会定期从eureka刷新本地缓存。
2.4.8 Eureka的缺点*

  某个服务不可用时,各个Eureka Client不能及时的知道,需要1~3个心跳周期才能感知。但是,由于基于Netflix的服务调用端都会使用Hystrix来容错和降级,当服务调用不可用时Hystrix也能及时感知到,通过熔断机制来降级服务调用,因此弥补了基于客户端服务发现的时效性的缺点。

三、负载均衡Ribbon

  RestTemplate是Spring提供的一个访问Http服务的客户端类,就是微服务之间的调用是使用的RestTemplate 。
  Ribbon是Netflix公司的一个开源的负载均衡项目,是一个客户端/进程内负载均衡器,运行在消费者端。消费者端获取到了所有的服务列表之后,在其内部使用负载均衡算法,进行对多个系统的调用。

  Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,并且Feign也是通过Ribbon来实现的。
  Feign默认集成了Ribbon。

  Ribbon客户端组件提供一系列完善的配置项如连接超时、重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

  通常说的负载均衡指的是服务端负载均衡,分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如F5等。软件负载均衡则是通过在服务器上安装一些具有负载均衡功能或模块的软件来完成请求分发工作,比如Nginx等。
  服务端的负载均衡:当客户端发送请求到负载均衡设备的时候,该设备按照某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务器的地址,然后进行转发。
  客户端负载均衡:所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心,在客户端负载均衡中心也要心跳去维护服务端清单的健康性,只是这个步骤需要和服务注册中心配合完成。
  通过Ribbon的封装,在微服务架构中使用客户端负载均衡的步骤:

  1. 服务提供者只需要启动多个服务实例并注册到一个注册中心或者多个相关联的服务注册中心;
  2. 服务消费者直接调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。

  当所调用的服务存在于多个服务器上时,就需要用到Ribbon了。Ribbon的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上。
  Ribbon的负载均衡默认使用的最经典的轮询算法。简单举例,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推。
  Ribbon是和Feign以及Eureka紧密协作的过程:

  • 1、首先Ribbon会从EurekaClient里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号;

  • 2、然后Ribbon就可以使用默认的RoundRobin算法,从中选择一台机器;

  • 3、Feign就会针对这台机器,构造并发起请求。

  • Nginx和Ribbon的区别
      Nignx是一种集中式的负载均衡器。

      Nginx是接收了所有的请求进行负载均衡的;而对于Ribbon来说它是在消费者端进行的负载均衡。

      在Nginx中请求是先进入负载均衡器,而在Ribbon中是先在客户端进行负载均衡才进行请求的。

3.1 RestTemplate*

  • 1、GET请求
      在RestTemplate中,对GET请求可以通过以下两个方法调用。
      第一种是getForEntity函数。该方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装。
      第二种是getForObject函数。该方法可以理解为对getForEntity的进一步封装,实现请求直接返回包装好的对象内容,示例:
	RestTemplate restTemplate = new RestTemplate();
	User result = restTemplate.getForObject(uri,User.class);

  当不需要关注请求响应除body外的其他内容时,使用该函数比较方便,可以少一个从Response中获取body的步骤。

  • 2、POST请求
      在RestTemplate中,对POST请求可以通过以下三个方法调用。
      第一种:postForEntity函数。该方法同GET请求中的getForEntity类似, 会在调用后返回ResponseEntity<T>对象,T为请求响应的body类型。
      第二种:postForObject函数。该方法也跟getForObject类似,用来简化postForEntity的处理,通过直接将请求响应中的body内容包装成对象来使用。
      第三种:postForLocation函数。该方法实现了以POST请求提交资源,并返回新资源的URI。
  • 3、PUT请求
      在RestTemplate中,对PUT请求可以通过put方法调用。put函数为void类型,所以没有返回内容。
  • 4、DELETE请求
      在RestTemplate中,对DELETE请求可以通过delete方法调用。由于在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息。

3.2 Ribbon的配置与使用

  对Ribbon参数配置通常有两种方式:全局配置以及指定客户端配置。

  • 1、全局配置
      使用ribbon.<key>=<value>格式进行配置即可。比如可以如下全局配置Ribbon创建连接的超时时间:
ribbon.ConnectTimeout=250
  • 2、指定客户端配置
      使用<client>.ribbon.<key>=<value>的格式进行配置。

  当在Spring Cloud的应用中同时引入Spring Cloud Ribbon和Spring Cloud Eureka依赖时,会触发Eureka中实现的对Ribbon的自动化配置。

3.3 重试机制

  由于Spring Cloud Eureka实现的服务治理机制强调了CAP原理中的AP,即可用性与可靠性。Eureka为了实现更高的服务可用性,牺牲了一定的一致性,在极端情况下它宁愿接受故障实例也不要丢弃“健康”实例。

3.4 Ribbon负载均衡策略*

  RandomRule:随机选择。也就是说Ribbon会随机从服务器列表中选择一个进行访问。
  RoundRobinRule:轮询。 【Ribbon默认采用的策略】。若经过一轮轮询没有找到可用的Provider,其最多轮询10轮。若最终还没有找到,则返回null 。
  RetryRule:重试。先按照RoundRobinRule策略获取Provider。若获取失败,则在指定的时限内重试。默认的时限为500毫秒。
  WeightedResponseTimeRule:带有加权的轮询策略略。对各个服务器响应时间进行加权处理,然后在采用轮询的方式来获取相应的服务器。
  ClientConfigEnabledRoundRobinRule:一般不用,通过继承该策略,默认的choose就实现了线性轮询机制,可以基于它来做扩展。
  BestAvailableRule:最大可用策略。先过滤出故障服务器器后,选择一个当前并发请求数最小的。
  PredicateBasedRule:先过滤清单,再轮询。
  AvailabilityFilteringRule:可用过滤策略。先过滤出故障的或并发请求大于阈值一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个。
  ZoneAvoidanceRule:区域感知策略。先使用主过滤条件(区域负载器,选择最优区域)对所有实例过滤并返回过滤后的实例清单,依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤,判断最小过滤数(默认1)和最小过滤百分比(默
认0),最后对满足条件的服务器则使用RoundRobinRule(轮询方式)选择一个服务器实例例。

  • 指定负载均衡算法
      1、建1个负载均衡配置类:
@Configuration
public class MyRibbonRuleConfig {
    @Bean
    public IRule MyRibbonRuleConfig (){
        //定义随机负载均衡算法
        return new RandomRule();
    }
}

  2、在启动类加上@RibbonClient注解:

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyRibbonRuleConfig.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class, args);
    }
}

3.5 Ribbon负载均衡能干什么

  1. 将用户的请求平摊的分配到多个服务上。
  2. 集中式LB即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方。
  3. 进程内LB将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

  Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

四、容错保护Hystrix

  Hystrix可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。
  Hystrix通过将依赖服务进行资源隔离,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix还提供故障时的fallback降级机制。总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。

  Hystrix 的设计原则:

  阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。
  避免请求排队和积压,采用限流和 fail fast 来控制故障。
  提供 fallback 降级机制来应对故障。
  使用资源隔离技术,比如 bulkhead (舱壁隔离技术)、 swimlane (泳道技术)、circuit breaker (断路技术)来限制任何一个依赖服务的故障的影响。
  通过近实时的统计/监控/报警功能,来提高故障发现的速度。
  通过近实时的属性和配置热修改功能,来提高故障处理和恢复的速度。
  保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。

  Hystrix是一个能进行熔断和降级的库,通过使用它能提高整个系统的弹性。

  • 服务雪崩
      此时我们整个微服务系统是这样的。服务A调用了服务B,服务B再调用了服务C,但是因为某些原因,服务C顶不住了,这个时候大量请求会在服务C阻塞。

      服务C阻塞了还好,毕竟只是一个系统崩溃了。但是请注意这个时候因为服务C不能返回响应,那么服务B调用服务C的的请求就会阻塞,同理服务B阻塞了,那么服务A也会阻塞崩溃。为什么阻塞会崩溃?因为这些请求会消耗占用系统的线程、IO等资源。

      这就叫服务雪崩
  • 服务雪崩的解决办法
    【1、降级】
      超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值。保证:服务出现问题整个项目还可以继续运行。
    【2、熔断】
      当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。通俗理解:熔断就是具有特定条件的降级。在代码上熔断和降级都是一个注解。保证:服务出现问题整个项目还可以继续运行。
    【3、缓存】
      提供了请求缓存。服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存了,就不在访问服务B了,即使出现大量请求时,也不会对B产生高负载。保证:减少对应用服务的调用。

4.1 降级和熔断*

  • 降级
      降级是指,当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应。

  • 熔断
      一般是某个服务故障或者是异常引起的,类似现实世界中的‘保险丝’,当某个异常条件被触发,直接熔断整个服务,而不是一直等到此服务超时,为了防止防止整个系统的故障,而采用了一些保护措施。比如A服务的X功能依赖B服务的某个接口,当B服务接口响应很慢时,A服务X功能的响应也会被拖慢,进一步导致了A服务的线程都卡在了X功能上,A服务的其它功能也会卡主或拖慢。此时就需要熔断机制,即A服务不在请求B这个接口,而可以直接进行降级处理。

  • 两者的区别
      相似处:

  1. 目的一致。都是为了系统的稳定性,防止因为个别微服务的不可用而拖死整个系统服务;
  2. 表现类似。在表现上都是让用户感知,该服务暂时不可用请稍后再试;
  3. 粒度一致。粒度上,都是服务级别的粒度,某些情况下,也有更细的粒度,如数据的持久层,只允许查询,不允许增删改。

  主要区别:

  1. 触发条件不同。服务熔断一般是某个服务挂掉了引起的,一般是下游服务,而服务降级一般是从整体的负荷考虑,主动降级;
  2. 管理目标的层次不同。熔断其实是一个框架级的处理,每个微服务都需要,没有层次之分,而降级一般需要对业务有层级之分,一般是从最外围服务开始。

  简单来说:

  服务降级:当服务调用出现响应时间过长或者运行出错或者宕机,就会调用服务降级方法快速响应
  服务熔断,只有在一定时间内服务调用失败(报错、超时、宕机)达到一定次数,才会启动服务熔断,进而调用服务降级方法快速响应
  服务降级每次都会先调用原服务方法,调用失败才会执行服务降级方法;服务熔断状态会直接调用服务降级方法

4.2 依赖隔离*

  资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了1000,但是分配给商品服务线程池内就10个线程,最多就只会用这10个线程去执行。不会因为对商品服务调用的延迟,将 Tomcat 内部所有的线程资源全部耗尽。
  Hystrix 实现资源隔离,主要有两种技术:线程池、信号量。默认情况下,Hystrix 使用线程池模式

  • 线程池
      Hystrix通过“舱壁模式”实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。
      通过实现对依赖服务的线程池的隔离,可以带来如下优势:
  1. 应用自身得到保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的其余部分。
  2. 可以有效降低接入新服务的风险。如果新服务介入后运行不稳定或存在问题,完全不会影响应用响应其他的请求。
  3. 当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下,容器级别的清理恢复速度要慢得多。
  4. 当依赖的服务出现配置错误时,线程池会快速反映此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。
  5. 当依赖的服务因实现机制调整等原因造成其性能出现很大变化时,线程池的监控指标会反映出这样的变化。

  总之,通过对依赖服务实现线程池隔离,可让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起非相关服务的异常。同时,也使得应用变得更加灵活,可以在不停止服务的情况,配合动态配置刷新实现性能配置上的调整。

  • 信号量
      信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。

      在Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销远比线程池的开销小,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下,才使用信号量。
      使用信号量的场景:
  1. 命令执行。如果将隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发。
  2. 降级逻辑。 当Hystrix尝试降级逻辑时,它会在调用线程池中使用信号量。

  一般来说,缓存服务,可能会将一些量特别少、访问又特别频繁的数据,放在自己的纯内存中。一般我们在获取到商品数据之后,都要去获取商品是属于哪个地理位置、省、市、卖家等,可能在自己的纯内存中,比如就一个 Map 去获取。对于这种直接访问本地内存的逻辑,比较适合用信号量做一下简单的隔离。

  • 线程池与信号量区别
      线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。
      线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。

      线程池其实最大的好处就是对于网络访问请求,如果有超时的话,可以避免调用线程阻塞住。而使用信号量的场景,通常是针对超大并发量的场景下,每个服务实例每秒都几百的 QPS ,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护。一般用信号量常见于那种基于纯内存的一些业务逻辑服务,而不涉及到任何网络访问请求。

  适用场景:

  线程池技术:适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。
  信号量技术:适合访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。

4.3 Hystrix相关问题

4.3.1 当服务无法访问时,是直接熔断还是降级*

  先降级,后熔断。

4.3.2 Hystrix如何实现熔断*

  Hystrix在运行过程中会向每个commandKey对应的熔断器器报告成功、失败、超时和拒绝的状态,熔断器器维护计算统计的数据,根据这些统计的信息来确定熔断器器是否打开。如果打开,后续的请求都会被截断。然后会隔一段时间默认是5s,尝试半开,放入一部分流量请求进来,相当于对依赖服务进行一次健康检查,如果恢复,熔断器器关闭,随后完全恢复调用。如下图:

4.3.3 Hystrix执行时内部原理

  Hystrix 最基本的支持高可用的技术:资源隔离 + 限流。

创建 command;
执行这个 command;
配置这个 command 对应的 group 和线程池。

  执行这个 command,调用了这个 command 的 execute() 方法之后,Hystrix 底层的执行流程和步骤。

  • 1、创建 command
      一个 HystrixCommand 或 HystrixObservableCommand 对象,代表了对某个依赖服务发起的一次请求或者调用。创建的时候,可以在构造函数中传入任何需要的参数。
      HystrixCommand 主要用于仅仅会返回一个结果的调用。
      HystrixObservableCommand 主要用于可能会返回多条结果的调用。
HystrixCommand = new HystrixCommand( arg1, arg2);
HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
  • 2、调用 command 执行方法
      执行 command,就可以发起一次对依赖服务的调用。要执行 command,可以在 4 个方法中选择其中的一个:execute()、queue()、observe()、toObservable()。
      execute() 和 queue() 方法仅仅对 HystrixCommand 适用。

execute():调用后直接 block 住,属于同步调用,直到依赖服务返回单条结果,或者抛出异常。
queue():返回一个 Future,属于异步调用,后面可以通过 Future 获取单条结果。
observe():订阅一个 Observable 对象,Observable 代表的是依赖服务返回的结果,获取到一个那个代表结果的 Observable 对象的拷贝对象。
toObservable():返回一个 Observable 对象,如果我们订阅这个对象,就会执行 command并且获取返回结果。

K value= hystrixCommand.execute();
Future<K> fValue= hystrixCommand.queue();
Observable<K> oValue= hystrixObservableCommand.observe();
Observable<K> toOValue= hystrixObservableCommand.toObservable();
  • 3、检查是否开启缓存(不太常用)
      如果这个 command 开启了请求缓存 Request Cache,而且这个调用的结果在缓存中存在,那么直接从缓存中返回结果。否则,继续往后的步骤。
  • 4、检查是否开启了断路器
      检查这个 command 对应的依赖服务是否开启了断路器。如果断路器被打开了,那么 Hystrix 就不会执行这个 command,而是直接去执行 fallback 降级机制,返回降级结果。
  • 5、检查线程池/队列/信号量是否已满
      如果这个 command 线程池和队列已满,或者 semaphore 信号量已满,那么也不会执行command,而是直接去调用 fallback 降级机制,同时发送 reject 信息给断路器统计。
  • 6、执行 command
      调用 HystrixObservableCommand 对象的 construct() 方法,或者 HystrixCommand 的 run() 方法来实际执行这个 command。
      HystrixCommand.run() 返回单条结果,或者抛出异常。
// 通过command执行,获取最新一条商品数据
ProductInfo productInfo = getProductInfoCommand.execute();

  HystrixObservableCommand.construct() 返回一个 Observable 对象,可以获取多条结果。

Observable<ProductInfo> = getProductInfosCommand.observe();
// 订阅获取多条结果
observable.subscribe(new Observer<ProductInfo>() {
    @Override
    public void onCompleted() {
        System.out.println("获取完了所有的商品数据");
    }
    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
    }
    /**
    * 获取完一条数据,就回调一次这个方法
    * @param productInfo 商品信息
    */
    @Override
    public void onNext(ProductInfo ) {
       System.out.println( );
    }
});

  如果是采用线程池方式,并且 HystrixCommand.run() 或者HystrixObservableCommand.construct() 的执行时间超过了 timeout 时长的话,那么 command所在的线程会抛出一个 TimeoutException,这时会执行 fallback 降级机制,不会去管 run() 或construct() 返回的值了。另一种情况,如果 command 执行出错抛出了其它异常,那么也会走fallback 降级。这两种情况下,Hystrix 都会发送异常事件给断路器统计。
  我们是不可能终止掉一个调用严重延迟的依赖服务的线程的,只能说给你抛出来一个TimeoutException。
  如果没有 timeout,也正常执行的话,那么调用线程就会拿到一些调用依赖服务获取到的结果,然后 Hystrix 也会做一些 logging 记录和 metric 度量统计。

  • 7、断路健康检查
      Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout 等事件发送给 circuit breaker断路器。断路器就会对这些事件的次数进行统计,根据异常事件发生的比例来决定是否要进行断路(熔断)。如果打开了断路器,那么在接下来一段时间内,会直接断路,返回降级结果。
      如果在之后,断路器尝试执行 command,调用没有出错,返回了正常结果,那么 Hystrix 就会把断路器关闭。
  • 8、调用 fallback 降级机制
      在以下几种情况中,Hystrix 会调用 fallback 降级机制:

断路器处于打开状态;
线程池/队列/semaphore满了;
command 执行超时;
run() 或者 construct() 抛出异常。

  一般在降级机制中,都建议给出一些默认的返回值,比如静态的一些代码逻辑,或者从内存中的缓存中提取一些数据,在这里尽量不要再进行网络请求了。在降级中,如果一定要进行网络调用的话,也应该将那个调用放在一个 HystrixCommand 中进行隔离。

HystrixCommand 中,实现 getFallback() 方法,可以提供降级机制。
HystrixObservableCommand 中,实现 resumeWithFallback() 方法,返回一个 Observable 对象,可以提供降级结果。

  如果没有实现 fallback,或者 fallback 抛出了异常,Hystrix 会返回一个 Observable,但是不会返回任何数据。
  不同的 command 执行方式,其 fallback 为空或者异常时的返回结果不同。

  对于 execute(),直接抛出异常。
  对于 queue(),返回一个 Future,调用 get() 时抛出异常。
  对于 observe(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
  对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。

  不同的执行方式:

  execute(),获取一个 Future.get(),然后拿到单个结果。
  queue(),返回一个 Future。
  observe(),立即订阅 Observable,然后启动 8 大执行步骤,返回一个拷贝的 Observable,订阅时立即回调给你结果。
  toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。

4.3.5 熔断的原理,以及如何恢复*

  熔断器模式定义了熔断器开关相互转换的逻辑:

  服务的健康状况 = 请求失败数 / 请求总数。熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的。
  当熔断器开关关闭时,请求被允许通过熔断器。如果当前健康状况高于设定阈值,开关继续保持关闭。如果当前健康状况低于设定阈值,开关则切换为打开状态。
  当熔断器开关打开时,请求被禁止通过。
  当熔断器开关处于打开状态,经过一段时间后,熔断器会自动进入半开状态,这时熔断器只允许一个请求通过。当该请求调用成功时,熔断器恢复到关闭状态。若该请求失败,熔断器继续保持打开状态,接下来的请求被禁止通过。
  熔断器的开关能保证服务调用者在调用异常服务时,快速返回结果,避免大量的同步等待。并且熔断器能在一段时间后继续侦测请求执行结果,提供恢复服务调用的可能。

4.3.6 Hystrix断路器

  Hystrix断路器有三种状态,分别是关闭(Closed)、打开(Open)与半开(Half-Open),三种状态转化关系如下:

  Closed 断路器关闭:调用下游的请求正常通过。
  Open 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑。
  Half-Ope:断路器处于半开状态。
  Hhystrix有自我恢复机制,意思是当服务提供方的熔断机制处于打开状态时,会在开启一个时间窗口,就是一定时间后或者是下一次请求的时间大于时间窗口的时间,hystrix就会重新将这次请求再次发送到服务提供方,如果成功就将状态改为half-open状态,如果失败就重新刷新时间窗口的时间。

4.3.7 Hystrix和Sentinel

  Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。
  Sentinel 的侧重点在于:

多样化的流量控制
熔断降级
系统负载保护
实时监控和控制台

  • 1、资源模型和执行模型上的对比
      Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象 HystrixCommand 或 HystrixObservableCommand ,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 commandKey 和 groupKey (用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。
      Sentinel 的设计则更为简单。相比 Hystrix Command 强依赖隔离规则,Sentinel 的资源定义与规则配置的耦合度更低。Hystrix 的 Command 强依赖于隔离规则配置的原因是隔离规则会直接影响 Command 的执行。在执行的时候 Hystrix 会解析 Command 的隔离规则来创建 RxJava Scheduler 并在其上调度执行,若是线程池模式则 Scheduler 底层的线程池为配置的线程池,若是信号量模式则简单包装成当前线程执行的 Scheduler。
  • 2、隔离设计上的对比
      隔离是 Hystrix 的核心功能之一。Hystrix 提供两种隔离策略:线程池隔离和信号量隔离,其中最推荐也是最常用的是线程池隔离。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead比较大,特别是对低延时的调用有比较大的影响。
      Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。
  • 3、熔断降级的对比
      Sentinel 与 Hystrix 都支持基于失败比率(异常比率)的熔断降级,在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断,此时所有对该资源的调用都会被 block,直到过了指定的时间窗口后才启发性地恢复。上面提到过,Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。

  除了上面的两者共同特性之外,Sentinel 还提供以下的特色功能:

  • 1.轻量级、高性能
      Sentinel 作为一个功能完备的高可用流量管控组件,其核心 sentinel-core 没有任何多余依赖,打包后只有不到 200KB,非常轻量级。
      引入 Sentinel 带来的性能损耗非常小。只有在业务单机量级超过 25W QPS 的时候才会有一些显著的影响(5% - 10% 左右),单机 QPS 不太大的时候损耗几乎可以忽略不计。
  • 2.流量控制
      Sentinel 可以针对不同的调用关系,以不同的运行指标(如 QPS、并发调用数、系统负载等)为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状。常用的有:
      直接拒绝模式:即超出的请求直接拒绝。
      慢启动预热模式:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

      匀速器模式:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel强大的调用链路统计信息,可以提供精准的不同维度的限流。
  • 3.实时监控和控制面板
      Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。
      Sentinel 控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。

    【总结】
SentinelHystrix
隔离策略信号量隔离线程池隔离/信号量隔离
熔断降级策略基于响应时间或失败比率基于失败比率
实时指标实现滑动窗口滑动窗口(基于RxJava)
规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
基于注解的支持支持支持
限流基于 QPS,支持基于调用关系的限流不支持
流量整形支持慢启动、匀速器模式不支持
系统负载保护支持不支持
控制台开箱即用,可配置规则、查看秒级监控、机器发现等不完善
常见框架的适配Servlet、Spring Cloud、Dubbo、gRPCServlet、Spring Cloud Netflix

五、服务调用Feign

  1、feign采用的是基于接口的注解。
  2、feign整合了ribbon,具有负载均衡的能力。
  3、整合了Hystrix,具有熔断的能力。
  使用:

1、添加pom依赖。
2、启动类添加@EnableFeignClients
3、定义一个接口@FeignClient(name=“xxx”)指定调用哪个服务

  在使用Ribbon时,通常都会利用它对RestTemplate的请求拦截来实现对依赖服务的接口调用,而RestTemplate已经实现了对HTTP请求的封装处理。Feign在此基础上做了进一步封装,在Feign的实现下,我们只需要创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用Ribbon时自行封装服务调用客户端的开发量。
  简而言之,Feign是一种声明式、模板化的HTTP客户端。
  @EnableFeignClients注解用来开启对Feign的支持功能。
  @FeignClient注解用来指定服务名来绑定服务,示例:

	@FeignClient("hello-service")
	public interface HelloService{
		@RequestMapping("/hello")
		String hello();
	}

  Feign使用示例:

  Feign的一个关键机制就是使用了动态代理。其具体调用过程:

  1. 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理;
  2. 接着你要是调用哪个接口,本质就是会调用Feign创建的动态代理;
  3. Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址;
  4. 最后针对这个地址,发起请求、解析响应。

  Feign的优点:

  1. Feign采用的是基于接口的注解;
  2. Feign整合了Ribbon,具有负载均衡的能力;
  3. 整合了Hystrix,具有熔断的能力。

   Ribbon和Feign的区别:

1、都是调用其他服务的,但方式不同。
2、启动类注解不同,Ribbon是@RibbonClient,feign的是@EnableFeignClients。
3、服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
4、调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。

六、网关服务Zuul

  Zuul包含了对请求的路由和过滤两个最主要的功能:其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
  如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务。
  Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。

  网关有的功能, Zuul基本都有。而Zuul中最关键的就是路由和过滤器。

  Zuul的主要作用可以分为两类:首先,对于路由规则与服务实例的维护问题。其次,对于类似签名校验、登录校验等问题。
  @EnableZuulProxy用于开启API网关服务功能。

  Zuul的优点:

方便监控。可以在微服务网管手机监控数据并将其推送到外部系统进行分析。
方便认证。可以在网关进行统一认证,然后再将请求转发到后端服务。
隐藏架构实现细节,提供统一的入口给客户端请求,减少了客户端和每个微服务的交互次数。
可以统一处理切面任务,避免每个微服务自己开发,提升效率。
高可用高伸缩性的服务,避免单点失效。

6.1 路由*

  使用Zuul实现路由功能十分简单,示例:

	zuul.routes.api-a-url.path=/api-a-url/**
	zuul.routes.api-a-url.url=http://localhost:8080/

  在该配置中,所有符合/api-a-url/**规则的访问都将被路由转发到http://localhost:8080地址上。
  当然,也可以让路由的path不是映射到具体的url,而是让它映射到某个具体的服务,具体的url规则交给Eureka的服务发现机制去自动维护,这种称为面向服务的路由,示例:

	zuul.routes.api-a-url.path=/api-a-url/**
	zuul.routes.api-a-url.serviceId=hello-service
  • 路径匹配规则
通配符说明
匹配任意单个字符
*匹配任意数量的字符
**匹配任意数量的字符,支持多级目录

  在使用路由规则匹配请求路径时,是通过线性遍历的方式,在请求路径获取到第一个匹配规则时就返回并结束匹配过程。所以放存在多个匹配的路由规则时,匹配结果完全取决于路由规则的保存顺序。
  由于properties的配置内容无法保证有序,所以当出现这种情况的时候,为了保证路由的优先顺序,需要使用YAML文件来配置,以实现有序的路由规则。

  • 路由前缀
      如果相位网关上的路由规则都增加/api前缀,就可以在配置文件中增加配置:zuul.prefix=/api,并且代理前缀会默认从路径中移除。此时,可以通过设置zuul.stripPrefix=false来关闭该移除代理前缀的动作,也可以通过zuul.routes.<route>.strip-prefix=true来对指定路由关闭移除代理前缀的动作。
  • 本地跳转
      在Zuul实现的API网关路由功能中,还可以支持forward形式的服务端跳转配置,示例:
	zuul.routes.api-a-url.path=/api-a-url/**
	zuul.routes.api-a-url.url=forward:/local
  • Cookie与头信息
      默认情况下,Zuul在请求路由时,会过滤掉HTTP请求头中的一些敏感信息,防止它们被传递到下有的外部服务器。默认的敏感头信息通过zuul.sensitiveHeaders参数定义,包括Cookie、Set-Cookie、Authorization三个属性。因此,平时开发的Web项目中常用的Cookie在Zuul网关中默认是不会传递的。为了解决这个问题,常常有两种做法:
  1. 通过设置全局参数为空来覆盖默认值,示例:
	zuul.sensitiveHeaders=

  这种方法并不推荐,虽然实现了Cookie的传递,但破坏了默认设置的用意。

  1. 通过指定路由的参数来配置,示例:
# 方法一:对指定路由开启自定义敏感头
zuul.routes.<router>.customSensitiveHeaders=true
# 方法二:将指定路由的敏感头设置为空
zuul.routes.<router>.sensitiveHeaders=

6.2 过滤*

  Zuul的路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
  过滤器比较常见的做法是继承ZuulFilter,实现四个方法即可:

	//返回值为过滤器的类型,它决定过滤器在请求的哪个生命周期中执行
	public String filterType();
	//过滤器的执行顺序,当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值依次执行
	public int filterOrder();
	//判断该过滤器是否需要被执行
	public boolean shouldFilter();
	//过滤器的具体逻辑
	public Object run();

  自定义了过滤器后,不会直接生效,还需要为其创建Bean,一般在启动类中创建,示例:

	@Bean
	public AccessFilter accessFilter(){
		return AccessFilter();
	}

  Zuul的具体作用:

  1. 作为系统的统一入口,屏蔽了系统内部各个微服务的细节。
  2. 与服务治理框架相结合,实现自动化的服务示例维护以及负载均衡的路由转发。
  3. 实现接口权限检验与微服务业务逻辑的解耦。
  4. 通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的检验前移,保证了微服务的无状态行,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。

  在上面要继承四个方法,对应了过滤器的4个基本特征:过滤类型、执行顺序、执行条件、具体惭怍。

  • filterType
      该函数需要返回一个字符串来代表过滤器的类型,而这个类型就是在HTTP请求过程中定义的各个阶段。在Zuul中默认定义了4种不同生命周期的过滤器类型,具体为:

pre:可以在请求被路由之前调用;
routing:在路由请求时被调用;
post:在routing和error过滤器之后被调用;
error:处理请求,发生错误时被调用。

  • filterOrder
      通过int值来定义过滤器的执行顺序,数值越小优先级越高。
  • shouldFilter
      返回一个boolean值来判断该过滤器是否要执行,可以通过此方法来指定过滤器的有效范围。
  • run
      过滤器的具体逻辑。

  过滤器示例:

@Component
public class PreRequestFilter extends ZuulFilter {
    // 返回过滤器类型 这里是前置过滤器
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
  
    // 指定过滤顺序 越小越先执行,这里第一个执行
    // 当然不是只真正第一个 在Zuul内置中有其他过滤器会先执行
    // 那是写死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
    @Override
    public int filterOrder() {
        return 0;
    }
  
    // 什么时候该进行过滤
    // 这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等
    @Override
    public boolean shouldFilter() {
        return true;
    }
  
    // 如果过滤器允许通过则怎么进行处理
    @Override
    public Object run() throws ZuulException {
        // 这里我设置了全局的RequestContext并记录了请求开始时间
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.set("startTime", System.currentTimeMillis());
        return null;
    }
}

@Slf4j
@Component
public class AccessLogFilter extends ZuulFilter {
    // 指定该过滤器的过滤类型
    // 此时是后置过滤器
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }
  
    // SEND_RESPONSE_FILTER_ORDER 是最后一个过滤器
    // 我们此过滤器在它之前执行
    @Override
    public int filterOrder() {
        return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
    }
  
    @Override
    public boolean shouldFilter() {
        return true;
    }
  
    // 过滤时执行的策略
    @Override
    public Object run() throws ZuulException {
         RequestContext context = RequestContext.getCurrentContext();
         HttpServletRequest request = context.getRequest();
         // 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔
         Long startTime = (Long) context.get("startTime");
         // 这里我可以获取HttpServletRequest来获取URI并且打印出来
        String uri = request.getRequestURI();
        long duration = System.currentTimeMillis() - startTime;
        log.info("uri: " + uri + ", duration: " + duration / 100 + "ms");
        return null;
    }
}
6.2.1 请求的生命周期*

  Zuul中4种不同类型的过滤器,他们覆盖了一个外部HTTP请求到达API网关,直到返回请求结果的全部生命周期。

  当外部HTTP请求到达API网关服务的时候,首先他会进入第一个阶段pre,在这里它会被pre类型的过滤器进行处理,该类型过滤器的主要目的是在进行请求路由之前做一些前置贾工,比如请求的校验等。在完成pre类型的过滤器处理之后,请求进入第二个阶段routing,也就是之前说的路由请求转发阶段,请求将会被routing类型过滤器处理。这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,请求进入第三个阶段post。此时请求将会被post类型的过滤器处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些贾工或转换等内容。另外,还有一个特殊的阶段error,该阶段只有上述三个阶段发生异常的时候才会触发,但是它最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端。

  • 默认启用的pre过滤器
      ServletDetectionFilter:执行顺序为-3,是最先被执行的过滤器。
      Servlet30WrapperFilter:执行顺序为-2,是第二个执行的过滤器。
      FromBodyWrapperFilter:执行顺序为-1,是第三个执行的过滤器。
      DebugFilter:执行顺序为1,是第四个执行的过滤器。
      PreDecorationFilter:执行顺序为5,是pre阶段最后被执行的过滤器。
  • 默认启用的route过滤器
      RibbonRoutingFilter:执行顺序为10,是route阶段第一个执行的过滤器。该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。
      SimpleHostRoutingFilter:执行顺序为100,是route阶段第二个执行的过滤器。该过滤器只对请求上下文中存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。
      SendForwardFilter:执行顺序为500,是route阶段第三个执行的过滤器。该过滤器只对请求上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中forward本地跳转配置。
  • 默认启用的post过滤器
      SendErrorFilter:执行顺序为0,是post阶段第一个执行的过滤器。给过滤器仅在请求上下文中包含error.status_code参数(由之前执行的过滤器设置的错误编码)并且还没有被该过滤器处理过的时候执行。
      SendResponseFilter:执行吮吸是1000,是post阶段最后执行的过滤器。该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只有在包含他们其中一个时执行处理逻辑。
6.2.2 异常处理

  对自定义过滤器中处理异常的两种基本方法:一种是通过在各个阶段的过滤器中增加try-catch块,实现过滤器内部的异常处理;另一种是利用error类型过滤器的生命周期特性,集中处理pre、route、post阶段抛出的异常信息。
  com.netflix.zuul.http.ZuulServlet的service中的各种过滤器的执行顺序:

	try{
		preRoute();
	}catch(ZuulException e){
		error(e);
		postRoute();
		return;
	}
	try{
		route();
	}catch(ZuulException e){
		error(e);
		postRoute();
		return;		
	}
	try{
		postRoute();
	}catch(ZuulException e){
		error(e);
		return;		
	}	
6.2.3 禁用过滤器

  不过是核心过滤器还是自定义过滤器,只要在API网关应用中为它们创建了实例,默认情况下,它们都是默认启用状态的。
  要禁用指定过滤器的话,示例:

	zuul.<SimpleClassName>.<filterType>.disable=true

  <SimpleClassName>代表过滤器的类名。filterType:即pre/routing/post/error。

6.2.4 令牌桶限流

  不仅仅是令牌桶限流方式, Zuul 只要是限流的活它都能干。

  令牌桶限流:有个桶,如果里面没有满那么就会以一定 固定的速率 会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。
  用Zuul的前置过滤器来实现一下令牌桶限流:

@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
    // 定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
    @Override
    public String filterType() {
        return FilterConstants.PRE_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;
   }
}

七、Spring Cloud相关问题

7.1 SpringBoot和SpringCloud的区别

  SpringBoot专注于快速方便的开发单个个体微服务。
  SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。
  SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系。
  SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。

7.2 Spring Cloud断路器的作用

  当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)。
  断路器有不同的状态:
  完全打开状态:一段时间内,达到一定的次数无法调用,并且多次监测没有恢复的迹象,断路器完全打开,那么下次请求就不会请求到该服务。
  半开:短时间内有恢复迹象,断路器会将部分请求发给该服务,正常调用时断路器关闭。
  关闭:当服务一直处于正常状态,能正常调用。

7.3 什么是Spring Cloud Config

  在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
  使用:

1、添加pom依赖。
2、配置文件添加相关配置。
3、启动类添加注解@EnableConfigServer。

7.4 什么是Spring Cloud Gateway

  Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。
  使用了一个RouteLocatorBuilder的bean去创建路由,除了创建路由RouteLocatorBuilder可以让你添加各种predicates和filters,predicates断言的意思,顾名思义就是根据具体的请求的规则,由具体的route去处理,filters是各种过滤器,用来对请求做各种判断和修改。

7.5 REST和RPC对比*

  1、RPC主要的缺陷是服务提供方和调用方式之间的依赖太强,需要对每一个微服务进行接口的定义,并通过持续继承发布,严格版本控制才不会出现冲突。
  2、REST是轻量级的接口,服务的提供和调用不存在代码之间的耦合,只需要一个约定进行规范。

7.6 说说RPC的实现原理

  首先需要有处理网络连接通讯的模块,负责连接建立、管理和消息的传输。其次需要有编解码的模块,因为网络通讯都是传输的字节码,需要将我们使用的对象序列化和反序列化。剩下的就是客户端和服务器端的部分,服务器端暴露要开放的服务接口,客户调用服务接口的一个代理实现,这个代理实现负责收集数据、编码并传输给服务器然后等待结果返回。

7.7 Spring Cloud如何实现服务的注册

  1. 服务发布时,指定对应的服务名,将服务注册到注册中心(eureka zookeeper)。
  2. 注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用Ribbon或Feign进行服务直接的调用发现。

7.8 接口限流方案

  1. 限制总并发数(比如数据库连接池、线程池)。
  2. 限制瞬时并发数(如 nginx 的 limit_conn 模块,用来限制 瞬时并发连接数)。
  3. 限制时间窗口内的平均速率(如 Guava 的 RateLimiter 、 nginx 的 limit_req 模块,限制每秒的平均速率)。
  4. 限制远程接口调用速率。
  5. 限制MQ的消费速率。
  6. 可以根据网络连接数、网络流量、 CPU 或 内存负载 等来限流。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值