Spring(六)Spring Cloud----Eureka、Ribbon、Hystrix

文章目录

本系列文章:
  Spring(一)控制反转、两种IOC容器、自动装配、作用域
  Spring(二)延迟加载、生命周期、面向切面、事务
  Spring(三)异步调用、定时器、缓存
  Spring(四)Spring MVC
  Spring(五)Spring Boot
  Spring(六)Spring Cloud----Eureka、Ribbon、Hystrix
  Spring(七)Spring Cloud----Feign、Zuul和Apollo

一、初识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性能好;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路由网关。

1.5 微服务之间如何独立通讯的(同步:RPC/REST 异步:消息队列)

  同步: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的子项目(Eureka/Ribbon/Hystrix/Feign/Zuul)

  • 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 网关转发请求给对应的服务。

  • SpringCloud和SpringBoot的依赖
      在Spring Cloud项目里,使用spring-cloud-starter-parent替代spring-boot-starter-parent,其具备spring-bootstarter--parent的同样功能并附加了Spring Cloud的依赖,示例:

    <parent>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-parent</artifactId>
        <version>Angel.SR3</version>
        <relativePath/>
    </parent>

  在项目根目录下的pom.xml中的配置对所有的子模块都是生效的,即子模块中不需要再添加这些依赖,示例:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

二、服务治理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就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其他分片会把它们的状态再次同步回来。
      注册中心里有一个注册表,保存了各个服务所在的机器和端口号。
  • Eureka客户端
      主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。

2.1 Eureka理论(服务注册中心:Eureka服务端 服务提供者/服务消费者:Eureka客户端)

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

  1. 服务注册中心:Eureka提供的服务端,提供服务注册和发现的功能;
  2. 服务提供者:提供服务的应用,遵循Eureka通信机制的应用。它将自己注册到Eureka Server中,以供其他应用发现;
  3. 服务消费者:消费者应用从服务注册中心获取服务列表,从而让消费者知道可以从哪个服务提供者调用其所需的服务。
2.1.1 服务提供者(服务注册:注册到Eureka服务端 服务续约:和Eureka服务端保持通信)
  • 服务注册
      “服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。
  • 服务续约
      在注册完服务之后,服务提供者会维护一个心跳,用来通知注册中心“我还活着”,以防被注册中心的“剔除任务”将服务实例从服务列表中排除出去,这个行为称之为服务续约
      Eureka客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知 Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除
	# 定义服务续约任务的调用间隔时间,单位:秒
	eureka.instance.lease-renewal-interval-in-seconds=30
	# 定义服务失效的时间,单位:秒
	eureka.instance.lease-expiration-duration-in-seconds=90
2.1.2 服务消费者(获取服务:从Eureka服务端获取服务清单 服务调用:根据清单调用服务 服务下线:通知Eureka服务端自己不提供服务)
  • 获取服务
      当启动服务消费者的时候,它会发送一个rest请求给服务注册中心,来获取 上面注册的服务清单。为了性能考虑,Eureka server会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次。若希望修改缓存单的更新时间,可以通过eureka.client.register.fetch-interval-seconds=30参数进行修改,该参数默认值为30,单位为秒。
      每次返回注册列表信息可能与Eureka客户端的缓存信息不同,Eureka客户端自动处理。
      如果由于某种原因导致注册列表信息不能及时匹配, Eureka客户端则会重新获取整个注册表信息。
  • 服务调用
      服务消费者在拿到服务清单以后,通过服务名可以获得提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详情信息,所以客户端可以根据自己的需要决定具体使用哪个实例,在ribbon中会采默认采用轮训的方式进行调用,从而实现客户端的负载均衡。
  • 服务下线
      在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用到被关闭的实例。所以在客户端程序中,当服务实例进行正常的关闭操作时,它会触发一个服务下线的rest请求给eureka server注册中心,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(down),并把该下线事件传播出去。
2.1.3 服务注册中心(失效剔除:和Eureka客户端的通信断开)
  • 失效剔除
      有些时候服务实例并不一定是正常下线,有可能是内存溢出、网络故障等问题使得服务不能正常工作,而服务注册中心没有收到“服务下线”的请求。为了从服务列表中将这些无法提供服务的实例剔除掉,eureka server注册中心在启动的时候会创建一个定时任务,默认每隔一段时间(60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

2.2 Eureka使用(服务端@EnableEurekaServer 客户端@EnableDiscoveryClient)

  • 服务端
      Eureka服务端需要的依赖示例:
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>

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

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

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

server.port=1111
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false //由于该应用为注册中心,所以设置为 false, 代表不向注册中心注册自己
eureka.client.fetch-registry=false  //由于注册中心的职责就是维护服务实例,它并不需要去检索服务, 所以也设置为 false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

  至此,启动应用并访问 http://localhost: 1111/,就能看到Eureka信息面板。

  • 客户端
      客户端所需要的依赖:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

  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的区别(ZK保证强一致性,Eureka保证高可用性)

  A:高可用;C:一致性;P:分区容错性。
  Zookeeper和Eureka都可以集群部署,因此有分区容错性。此外,Zookeeper保证了强一致性,Eureka保证了高可用性

  zookeeper当主节点故障时,zk会在剩余节点重新选择主节点,耗时较长(30 ~ 120s),虽然最终能够恢复,但是选取主节点期间会导致服务不可用
  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 什么是失效剔除(服务非正常下线,Eureka服务端将服务踢掉)

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

2.4.3 什么是自我保护机制(一定时间内统计服务失效过多,则不再剔除失效服务)

  在运行期间,注册中心会统计心跳失败比例在15分钟之内是否低于85%。如果低于,注册中心会将当前注册实例信息保护起来,不再剔除这些实例信息,当网络恢复后,退出自我保护机制
  自我保护机制让服务集群更稳定、健壮。
  当触发自我保护机制后,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也能及时感知到,通过熔断机制来降级服务调用,因此弥补了基于客户端服务发现的时效性的缺点。

2.4.9 dubbo服务注册与发现原理

Provider:服务提供⽅
Consumer:调⽤远程服务的服务消费⽅
Registry:服务注册与发现的注册中⼼
Monitor:统计服务的调⽤次调和调⽤时间的监控中⼼
Container:服务运⾏容器

  1. 服务容器器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中⼼心返回服务提供者地址列表给消费者,如果有变更更,注册中心将基于长连接推送变更更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调⽤用次数和调⽤用时间,定时每分钟发送一次统计数据到监控中心。

三、负载均衡Ribbon

  Ribbon是Netflix公司的一个开源的负载均衡项目,是一个客户端/进程内负载均衡器,运行在消费者端。消费者端获取到了所有的服务列表之后,在其内部使用负载均衡算法,进行对多个系统的调用。示例:

  Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,微服务间的调用、API网关的请求转发等实际上都是通过Ribbon 来实现的,并且Feign也是通过Ribbon来实现的(Feign默认集成了Ribbon)。

  通常说的负载均衡指的是服务端负载均衡,分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如F5等。软件负载均衡则是通过在服务器上安装一些具有负载均衡功能或模块的软件来完成请求分发工作,比如Nginx等。

  客户端负载均衡:所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心,在客户端负载均衡中心也要心跳去维护服务端清单的健康性,只是这个步骤需要和服务注册中心配合完成。
  通过Ribbon的封装,在微服务架构中使用客户端负载均衡的步骤:

  1. 服务提供者只需要启动多个服务实例并注册到一个注册中心或者多个相关联的服务注册中心;
  2. 服务消费者直接调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
  • Nginx和Ribbon的区别
      Nignx是一种集中式的负载均衡器。

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

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

3.1 RestTemplate

  RestTemplate会使用 Ribbon 的自动化配置, 同时通过配置@LoadBalanced还能够开启客户端负载均衡。
  当使用RestTemplate时,可以添加如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

  @LoadBalanced的常见使用方式:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

  接下来,就可以在代码里使用RestTemplate对象了,示例:

    @Autowired
    private RestTemplate restTemplate;

  RestTemplate可以处理GET、PUT、POST、DELETE4种请求。

3.1.1 GET请求(getForEntity/getForObject)

  在RestTemplate中,对GET请求可以通过getForEntity和getForObject方法调用。
  getForEntity方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装。使用示例:

	// 示例1
	RestTemplate restTemplate = new RestTemplate();
	ResponseEntity<String> responseEntity = restTemplate.getForEntity(
	     "http://USER­-SERVICE/user?name= {1}", String. class, "didi");
	//返回的 ResponseEntity 对象中的 body 内容类型会根据第二个参数转换为 String 类型
	String body = responseEntity. getBody();
	
	RestTemplate restTemplate = new RestTemplate();
	//返回的 body 是一个 User 对象类型
	ResponseEntity<User> responseEntity  = restTemplate.getForEntity(
		"http://USER­-SERVICE/user?name= {l}", User.class, "didi");
	User body= responseEntity.getBody();

  getForEntity还有一种更常见的用法:

	//urlVariables用来传入多个参数
	getForEntity(String url, Class responseType, Map urlVariables)

  使用示例:

	RestTemplate restTemplate = new RestTemplate();
	Map<String, String> params = new HashMap<>();
	params.put("name", "dada");
	ResponseEntity<String> responseEntity = restTemplate.getForEntity(
		"http://USER­-SERVICE/user?name={name}", String.class, params);

  getForObject方法可以理解为对getForEntity的进一步封装,实现请求直接返回包装好的对象内容,示例:

	RestTemplate restTemplate = new RestTemplate();
	String result= restTemplate.getForObject(uri, String.class);
	//当 body 是一个 User 对象时, 可以直接这样实现
	RestTemplate restTemplate = new RestTemplate();
	User result = restTemplate.getForObject(uri,User.class);

  当不需要关注请求响应除body外的其他内容时,使用该函数比较方便,可以少一个从Response中获取body的步骤。
  类似于getForEntity,getForObject也有传入Map类型参数的重载方法:

	getForObject(String url, Class responseType, Map urlVariables)
3.1.2 POST请求(postForEntity/postForObject)

  在RestTemplate中,对POST请求可以通过以下三个方法调用。
  第一种:postForEntity函数。该方法同GET请求中的getForEntity类似, 会在调用后返回ResponseEntity<T>对象,T为请求响应的body类型。示例:

	RestTemplate restTemplate = new RestTemplate();
	User user = new User("didi", 30);
	//提交的 body内容为 user 对象, 请求响应返回的 body类型为 String
	ResponseEntity<String> responseEntity =
		restTemplate.postForEntity("http://USER-SERVICE/user", user, String.class);
	String body = responseEntity.getBody();

  第二种:postForObject函数。该方法也跟getForObject类似,用来简化postForEntity的处理,通过直接将请求响应中的body内容包装成对象来使用(postForObject返回值是HTTP协议的响应体)。示例:

	RestTemplate res七Template = new RestTemplate();
	User user = new User("didi", 20);
	//返回值字符串为响应体body的json格式字符串
	String postResult = restTemplate.postForObject("http: //USER-SERVICE/user", user,String.class);

  第三种:postForLocation函数。该方法实现了以POST请求提交资源,并返回新资源的URI。实例:

	User user = new User("didi", 40);
	URI responseURI = restTemplate.postForLocation("http://USER-SERVICE/user", user);
3.1.3 PUT请求(put)

  在RestTemplate中,对PUT请求可以通过put方法调用。示例:

	RestTemplate restTemplate = new RestTemplate();
	Long id = 100011;
	User user = new User("didi", 40);
	restTemplate.put("http://USER-SERVICE/user/{l}", user, id);

  put函数为void类型,所以没有返回内容。

3.1.4 DELETE请求(delete)

  在RestTemplate中,对DELETE请求可以通过delete方法调用。示例:

	RestTemplate restTemplate = new RestTemplate();
	Long id= 10001L;
	restTemplate.delete("http://USER-SERVICE/user/{1)", id);

  由于在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息。

3.2 Ribbon的配置与使用

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

  • 1、全局配置
      使用ribbon.<key>=<value>格式进行配置即可。比如可以如下全局配置Ribbon创建连接的超时时间:
ribbon.ConnectTimeout=250
  • 2、指定客户端配置
      使用<client>.ribbon.<key>=<value>的格式进行配置。
  • 3、与Eureka结合
      当在Spring Cloud的应用中同时引入Spring Cloud Ribbon和Spring Cloud Eureka依赖时,会触发Eureka中实现的对Ribbon的自动化配置。
      在与Spring Cloud Eureka结合使用的时候, 我们的配置将会变得更加简单,因为Eureka将会为我们维护所有服务的实例清单。

3.3 重试机制

  由于Spring Cloud Eureka实现的服务治理机制强调了CAP原理中的AP,即可用性与可靠性。Eureka为了实现更高的服务可用性,牺牲了一定的一致性,在极端情况下它宁愿接受故障实例也不要丢弃“健康”实例。比如, 当服务注册中心的网络发生故障断开时, 由于所有的服务实例无法维持续约心跳, 在强调 AP的服务治理中将会把所有服务实例都剔除掉,而Eureka则会因为超过85%的实例丢失心跳而会触发保护机制,注册中心将会保留此时的所有节点, 以实现服务间依然可以进行互相调用的场景, 即使其中有部分故障节点, 但这样做可以继续保障大多数的服务正常消费。

3.4 Ribbon负载均衡

3.4.1 负载均衡策略(随机选择/轮询:默认策略/在指定时间内重试/带有加权的轮询策略)

  RandomRule:随机选择。也就是说Ribbon会随机从服务器列表中选择一个进行访问。
  RoundRobinRule轮询(Ribbon默认采用的策略)。该策略实现了按照线性轮询的方式依次选择每个服务实例的功能。若经过一轮轮询没有找到可用的Provider,则继续下一轮,最多轮询10轮。若最终还没有找到,则返回null 。
  RetryRule:重试。先按照RoundRobinRule策略获取Provider。若获取失败,则在指定的时限内重试(默认的时限为500毫秒);若在时限内仍然获取不到可用的Provider则返回null。
  WeightedResponseTimeRule:带有加权的轮询策略。对各个服务器响应时间进行加权处理,然后在采用轮询的方式来获取相应的服务器。
  BestAvailableRule:最大可用策略。先过滤出故障服务器器后,选择一个当前并发请求数最小的。
  PredicateBasedRule:先过滤一部分服务实例,再线性轮询。
  AvailabilityFilteringRule:可用过滤策略。先过滤出故障的或并发请求大于阈值一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个。

  是否故障, 即断路器是否生效已断开。
  实例的并发请求数大于阙值,默认值为 232 -1, 该配置可通过参数<clientName>.<nameSpace>.ActiveConnectionsLimit 来修改。

  ZoneAvoidanceRule:区域感知策略。其处理逻辑:

   使用主过滤条件对所有实例过滤并返回过滤后的实例清单。
  依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤。
  每次过滤之后(包括主过滤条件和次过滤条件),都需要判断下面两个条件, 只要有一个符合就不再进行过滤, 将当前结果返回供线性轮询算法选择:
    过滤后的实例总数>=最小过滤实例数(默认为 1)。
    过滤后的实例比例>最小过滤百分比 (默认为 0)。

3.4.2 指定负载均衡算法(生成IRule实例 + @RibbonClient)

  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);
    }
}

四、容错保护Hystrix

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

  Hystrix 的设计原则:

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

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

4.1 Hystrix的基本使用(@Enable­CircuitBreaker + @HystrixCommand)

  1、添加Hystrix需要的依赖:

<dependency>
	<groupid>org.springframework.cloud</groupid>
	<artifactid>spring-cloud-starter-hystrix</artifactid>
</dependency>

  2、然后在代码中使用@Enable­CircuitBreaker注解开启断路器功能,示例:

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[) args) {
		SpringApplication.run(ConsumerApplication.class, args);
	}
}

  3、在业务代码中使用 @HystrixCommand注解来指定回调方法。示例:

@Service
public class HelloService {
	@Autowired
	RestTemplate restTemplate;

	@HystrixCommand(fallbackMethod = "helloFallback")
	public String helloService() {
		return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class) .getBody();public String helloFallback () (
		return "error";
	}
}

4.2 Hystrix的工作流程

4.2.1 创建HystrixCommand或HystrixObservableCommand对象

  熔断器中用到了命令模式,在具体实现时会构建一个HystrixCommand或是HystrixObservableCommand对象,用来表示对依赖服务的操作请求, 同时传递所有需要的参数。 这两个 Command 对象分别针对不同的应用场景。

  HystrixCommand: 用在依赖的服务返回单个操作结果的时候。
  HystrixObservableCommand: 用在依赖的服务返回多个操作结果的时候。

  命令模式, 将来自客户端的请求封装成一个对象, 从而让你可以使用不同的请求对客户端进行参数化。 它可以被用于实现“ 行为请求者 ” 与“ 行为实现者 ” 的解耦, 以便使两者可以适应变化。

4.2.2 命令执行

  一共存在4种命令的执行方式,而 Hystrix在执行 时 会根据创建的Command对象以及具体的情况来选择一个执行。其中HystrixComrnand实现了下面两个执行方式。
  execute(): 同步执行, 从依赖的服务 返回 一个单 一的结果对象, 或是在发生错误的时候抛出异常。
  queue(): 异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一 结果对象。示例:

R value = command.execute() ;
Future<R> fValue = command.queue() ;

  HystrixObservableCommand实现了另外两种执行方式。
  observe(): 返回Observable对象,它代表了操作的多个结果,它是一个HotObservable。
  toObservable(): 同样会返回Observable对象, 也代表了操作的多个结果,但它返回的是 一个Cold Observable。

Observable<R> ohValue = command.observe(};
Observable<R> ocValue = command. toObservable(};

  在Hystrix 的底层实现中大量地使用了RxJava,RxJava中使用了观察者-订阅者模式。

4.2.3 结果是否被缓存

  若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以Observable 对象的形式返回。

4.2.4 断路器是否打开

  在命令结果没有缓存命中的时候, Hystrix在执行命令前需要检查断路器是否为打开状态:

  如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(对应下面第8步)。
  如果断路器是关闭的, 那么Hystrix跳到第5步,检查是否有可用资源来 执行命令。

4.2.5 线程池/请求队列/信号量是否占满

  如果与命令相关的线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么Hystrix也不会执行命令, 而是转接到fallback处理逻辑(对应下面第8步)。

  这里Hystrix所判断的线程池并非容器的线程池,而是每个依赖服务的专有线程池。 Hystrix为了保证不会因为某个依赖服务的间题影响到其他依赖服务而采用了“ 舱壁模式" 来隔离每个依赖的服务。

4.2.6 HystrixObservableCommand.construct()或HystrixCommand.run()

  Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。

  HystrixCommand.run(): 返回一个单一 的结果,或者抛出异常。
  HystrixObservableCommand.construct(): 返回一个Observable对象来返回多个结果,或通过onError发送错误通知。

  如果run()或construct()方法的执行时间超过了命令设置的超时阙值, 当前处理线程将会抛出一个TimeoutException。在这种情况下,Hystrix会转接到fallback处理逻辑(第8步)。同时, 如果当前命令没有被取消或中断, 那么它最终会忽略run()或者construct ()方法的返回。
  如果命令没有抛出异常并返回了结果,那么Hystrix在记录一些日志并采集监控报告之后将该结果返回。在使用run()的情况下,Hystrix会返回一个Observable, 它返回单个结果并产生onCompleted的结束通知; 而在使用construct ()的情况下,Hystrix会直接返回该方法产生的Observable对象。

4.2.7 计算断路器的健康度

  Hystrix会将 “ 成功 ”、 “ 失败 ”、 “ 拒绝 ”、“ 超时” 等信息报告给断路器, 而断路器会维护 一组计数器来统计这些数据。
  断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “ 熔断/短路 ”,直到恢复期结束。 若在恢复期结束后, 根据统计数据判断如果还是未达到健康指标, 就再次“ 熔断/短路 ”。

4.2.8 fallback处理

  当命令执行失败的时候, Hystrix会进入fallback尝试回退处理, 我们通常也称该操作为“ 服务降级”。而能够引起服务降级处理的清况有下面几种:

  第4步, 当前命令处于“ 熔断I短路 ” 状态, 断路器是打开的时候。
  第5步, 当前命令的线程池、 请求队列或者信号量被占满的时候。
  第6步,HystrixObservableCommand.construct()或HystrixCommand.run()抛出异常的时候。

  在服务降级逻辑中, 我们需要实现一个通用的响应结果, 并且该结果的处理逻辑应当是从缓存或是根据一些静态逻辑来获取,而不是依赖网络请求获取。如果一定要在降级逻辑中包含网络请求,那么该请求也必须被包装在HystrixCommand或是HysstrixObservableCommand中, 从而形成级联的降级策略, 而最终的降级逻辑一定不是一个依赖网络请求的处理, 而是一个能够稳定地返回结果的处理逻辑。
  在 HystrixCommand和HystrixObservableCommand中实现降级逻辑时还略有不同:

  当使用HystrixCommand的时候,通过实现HystrixCommand.getFallback()来实现服务降级逻辑。
  当使用 HystrixObservableCommand 的时 候 , 通过 HystrixObservable­Command. resumeW江hFallback()实现 服 务 降 级 逻辑, 该方法会返回一个Observable对象来表示 一个或多个降级结果。

  当命令的降级逻辑返回结果之后, Hystrix就将该结果返回给调用者 。 当使用HystrixCommand.getFallback()的时候, 它会返回 一个Observable对象, 该对象表示getFallback()的处理结果 。 而使 用 HystrixObservableCommand.resumeWithFallback()实现的时候, 它会将Observable对象直接返回。
  如果我们没有为命令实现降级逻辑或者降级处理逻辑中抛出了异常, Hystrix依然会返回一个Observable对象, 但是它不会发射任何结果数据, 而是通过onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。实现一个有可能失败的降级逻辑是一种非常糟糕的做法, 我们应该在实现降级策略时尽可能避免失败的情况。
  如果降级执行发现失败的时候,Hystrix会根据不同的执行方法做出不同的处理:

  execute(): 抛出异常。
  queue(): 正常返回Future对象,但是当 调用get()来获取结果的时候会抛出异常。
  observe(): 正常返回Observable对象, 当订阅它的时候, 将立即通过调用订阅者的onError方法来通知中止请求。
  toObservable(): 正常返回Observable对象, 当订阅它的时候, 将通过调用订阅者的onError方法来通知中止请求。

4.2.9 返回成功的响应

  当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。

4.3 依赖隔离(将请求隔离)

  资源隔离,即把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了。比如说商品服务,现在同时发起的调用量已经到了1000,但是分配给商品服务线程池内就10个线程,最多就只会用这10个线程去执行。不会因为对商品服务调用的延迟,将 Tomcat 内部所有的线程资源全部耗尽。
  Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩。

  服务雪崩效应是一种因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程。

  Hystrix实现资源隔离,主要有两种技术:线程池、信号量。默认情况下,Hystrix使用线程池模式

4.3.1 线程池(默认配置)

  Hystrix通过“舱壁模式”实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。
  通过实现对依赖服务的线程池的隔离,可以带来如下优势:

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

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

4.3.2 信号量(普通的限流)

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

  在Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销远比线程池的开销小,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下,才使用信号量。
  使用信号量的场景:

  1. 命令执行。如果将隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发。
  2. 降级逻辑。 当Hystrix尝试降级逻辑时,它会在调用线程池中使用信号量。
4.3.3 线程池与信号量区别

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

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

  适用场景:

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

4.4 Hystrix的高级使用

4.4.1 创建请求命令(继承HystrixCommand)

  比如可以使用继承的方式实现自定义的Hystrix命令(HystrixCommand),示例:

public class UserCommand extends HystrixCommand<User> {
	private RestTemplate restTemplate;
	private Long id;
	public UserCommand(Setter setter, RestTemplate restTemplate, Long id) {
		super(setter);
		this.restTemplate = restTemplate;
		this.id= id;
	}
	
	@Override
	protected User run() {
		return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class, id);
	}
}

  通过UserCommand,既可以实现请求的同步执行也可以实现异步执行。

//同步执行
User u = new UserCommand(restTemplate, 1L).execute();
//异步执行,通过futureUser.get()获取结果
Future<User> futureUser = new UserCommand(restTemplate,1L).queue();

  当然,直接使用@HystrixCommand也可以达到同步或异步的效果。示例:

public class UserService {
	@Autowired
	private RestTemplate restTemplate;
	//同步
	@HystrixCommand
	public User getUserByid(Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class, id);
	}
	//异步
	@HystrixCommand
	public Future<User> getUserByidAsync(final String id) {
		return new AsyncResult<User>() {
			@Override
			public User invoke() {
				return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class, id);
			}
		}
	}
}
4.4.2 定义服务降级(重写getFallback方法)

  fallback 是 Hystrix 命令执行失败时使用的后备方法, 用来实现服务的降级处理逻辑。
  在HystrixCommand中,可以通过重载 getFallback ()方法来实现服务降级逻辑, Hystrix会在 run() 执行过程中出现错误、 超时、 线程池拒绝、 断路器熔断等情况时, 执行getFallback ()方法内的逻辑。示例:

public class UserCommand extends HystrixCommand<User> {
	private RestTemplate restTemplate;
	private Long id;
	public UserCommand(Setter setter, RestTemplate restTemplate, Long id) {
		super(setter);
		this.restTemplate = restTemplate;
		this.id= id;
	}
	@Override
	protected User run() {
		return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
	}
	@Override
	protected User getFallback() {
		return new User();
	}
}

  在HystrixObservableCommand实现的 Hystrix 命令中, 可以通过重载resumeWithFallback 方法来实现服务降级逻辑。 该方法会返回 一个 Observable 对象, 当命令执行失败的时候, Hystrix 会将 Observable中的结果通知给所有的订阅者。
  若要通过注解实现服务降级只需要使用@HystrixCommand 中的 fallbackMethod参数来指定具体的服务降级实现方法。示例:

public class UserService {
	@Autowired
	private RestTemplate restTemplate;
	@HystrixCommand(fallbackMethod = "defaultUser")
	public User getUserByid(Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/(1)",User.class, id);
	}
	public User defaultUser() {
		return new User();
	}
}

  在使用注解来定义服务降级逻辑时, 我们需要将具体的 Hystrix 命令与 fallback 实现函数定义在同 一个类中, 并且 fallbackMethod 的值必须与实现 fallback 方法的名字相同。 由于必须定义在一个类中, 所以对于 fallback 的访问修饰符没有特定的要求, 定义为private、 protected、 public 均可。

4.4.3 异常处理(配置不需要触发降级的异常)

  在 HystrixComrnand 实现的 run() 方法中抛出异常时, 除了 HystrixBadRequestExceptioon 之外,其他异常均会被 Hystrix 认为命令执行失败并触发服务降级的处理逻辑,所以当需要在命令执行中抛出不触发服务降级的异常时来使用它。
  而在使用注册配置实现 Hystrix 命令时,它还支持忽略指定异常类型功能, 只需要通过设置 @HystrixComrnand注解的 ignoreExceptioons 参数, 示例:

	//当 getUserByld 方法抛出了类型为 BadRequestException的异常时, Hystrix 会将它包装在 
	//HystrixBadRequestException 中抛出, 这样就不会触发后续的 fallback 逻辑。
	@HystrixCommand(ignoreExceptions = {BadRequestException.class})
	public User getUserByid(Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class,id);
	}

  当 Hystrix 命令因为异常(除了 HystrixBadRequestExceptioon 的异常)进入服务降级逻辑之后, 往往需要对不同异常做针对性的处理。
  除了传统的实现方式之外, 注解配置方式也同样可以实现异常的获取。 它的实现也非常简单, 只需要在 fallback 实现方法的参数中增加 Throwable e 对象的定义, 这样在方法内部就可以获取触发服务降级的具体异常内容。示例:

	@HystrixCommand(fallbackMethod = "fallbackl")
	User getUserByid(String id) {
		throw new RuntimeException("getUserByid command failed");
	}
	User fallbackl{String id, Throwable e) {
		assert "getUserByid command failed".equals{e. getMessage{));
	}
4.4.4 请求缓存(重写getCacheKey方法开启请求缓存)

  当系统用户不断增长时, 每个微服务需要承受的并发压力也越来越大。 在分布式环境下, 通常压力来自于对依赖服务的调用, 因为请求依赖服务的资源需要通过通信来实现,这样的依赖方式比起进程内的调用方式会引起一部分的性能损失, 同时HTTP相比于其他高性能的通信协议在速度上没有任何优势, 所以它有些类似于对数据库这样的外部资源进行读写操作, 在高并发的情况下可能会成为系统的瓶颈。
  在高并发的场景之下, Hystrix 中提供了请求缓存的功能, 我们可以方便地开启和使用请求缓存来优化系统, 达到减轻高并发时的请求线程消耗、 降低请求响应时间的效果。
  Hystrix 请求 缓 存 的使 用非常简单, 我们只需要在实现HystrixCommand或HystrixObservableCommand 时, 通过重载 getCacheKey ()方法来开启请求缓存。示例:

	public class UserConmmand extends HystrixCommand<User> {
		private RestTemplate restTempla七e;
		private Long id;
		public UserCommand(RestTemplate restTemplate, Long id) {
			super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserGroup")));
			this.restTemplate= restTemplate;
			this.id= id;
		}

	@Override
	protected User run() {
		return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class, id);
	}

	@Override
	protected String getCacheKey() {
		return String.valueOf(id);
	}
}

  当不同的外部请求处理逻辑调用了同 一 个依赖服务时, Hystrix 会根据 getCacheKey 方法返回的值来区分是否是重复的请求,如果它们的 cacheKey 相同, 那么该依赖服务只会在第 一个请求到达时被真实地调用一次, 另外 一个请求则是直接从请求缓存中返回结果, 所以通过开启请求缓存可以让我们实现的 Hystrix 命令具备下面几项好处:

  减少重复的请求数, 降低依赖服务的并发度。
  在同 一用户请求的上下文中, 相同依赖服务的返回数据始终保持一致。
  请求缓存在 run() 和 construct ()执行之前生效, 所以可以有效减少不必要的线程开销。

  使用请求缓存时, 如果只是读操作, 那么不需要考虑缓存内容是否正确的问题, 但是如果请求命令中还有更新数据的写操作, 那么缓存中的数据就需要我们在进行写操作时进行及时处理, 以防止读操作的请求命令获取到了失效的数据。在 Hystrix 中, 我们可以通过 HystrixRequestCache.clear() 方法来进行缓存的清理。示例:

public class UserGetCommand extends HystrixCommand<User> {
	private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("CommandKey");
	private RestTemplate restTemplate;
	private Long id;
	public UserGetCommand(RestTemplate restTemplate, Long id) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetSetGet")).andCommandKey(GETTER_KEY));
		this.restTemplate = restTemplate;
		this.id= id;
	};
	
	@Override
	protected User run() (
		return restTemplate.getForObject("http://USER-SERVICE/users/(l}",User.class, id);
	}
	//根据id置入缓存
	@Override
	protected String getCacheKey() {
		return String.valueOf(id);
	}
	//根据id清理缓存
	public static void flushCache(Long id) {
		HystrixRequestCache.getInstance(GETTER_KEY,HystrixConcurrencyStrategyDefault.getinstance()).clear(String.valueOf(id));
	}
}

public class UserPostCommand extends HystrixCommand<User> {
	private RestTemplate restTemplate;
	private User user;
	public UserPostCommand(RestTemplate restTemplate, User user) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetSetGet")));
		this.restTemplate = restTemplate;
		this.user = user;
	}
	
	@Override
	protected User run() {
		//写操作
		User r = restTemplate.pos七ForObject("http://USER-SERVICE/users", user,User.class);
		//清理缓存中失效的User
		UserGetCommand.flushCache(user.getId());
		return r;
	}
}

  Hystrix的请求缓存除了可以通过上面传统的方式实现之外, 还可以通过注解的方式进行配置实现。

注解描述属性
@CacheResult该注解用来标记请求命令返回的结果应该被缓存, 它必须与@HystrixCommand注解结合使用cacheKeyMethod
@CacheRemove该注解用来让请求命令的缓存失效, 失效的缓存根据定义的Key决定commandKey,cacheKeyMethod
@CacheKey该注解用来在诮求命令的参数上标记, 使其作为缓存的Key值, 如果没有标注则会使用所有参数。 如果同时还使用了@CacheResult和@CacheRemove注解的cacheKeyMethod方法指定缓存Key的生成, 那么该注解将不会起作用value

  只使用@CacheResult 注解就可以开启缓存,它的缓存 Key 值会使用所有的参数。示例:

	@CacheResult
	@HystrixCommand
	public User getUserByid(Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/{l}",User. class, id);
	}

  定义缓存 Key: 当使用注解来定义请求缓存时,若要为请求命令指定具体的缓存 Key生成规则, 我们可以使 用 @CacheResu止 和@CacheRemove 注解 的cacheKeyMethod 方法来指定具体的生成函数;也可以通过使用@CacheKey 注解在方法参数中指定用于组装缓存 Key 的元素。示例:

	@CacheResult(cacheKeyMethod = "getUserByidCacheKey")
	@HystrixCommand
	public User getUserById(Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class, id);
	}
	private Long getUserByidCacheKey(Long id) {
		return id;
	}

  通过@CacheKey 注解 实现的 方式更加简单。 但是在使用@CacheKey 注解的时候需要注意,它的优先级比 cacheKeyMethod 的优先级低,如果已经使用了 cacheKeyMethod 指定缓存 Key 的生成函数, 那么@CacheKey 注解不会生效。示例:

	@CacheResult
	@HystrixCommand
	public User getUserByid(@CacheKey("id") Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/{l}",User.class, id);
	}

  @CacheKey 注解除了可以指定方法参数作为缓存 Key 之外, 它还允许访问参数对象的内部属性作为缓存 Key。比如下面的例子,它指定了 User 对象的 id 属性作为缓存 Key。示例:

	@CacheResult
	@HystrixCommand
	public User getUserByid(@CacheKey("id") User user) {
		return restTemplate.getForObject("http://USER-SERVICE/users/ { 1} ", User.class,user.getid());
	}

  在 Hystrix 的注解配置中,可以通过@CacheRemove 注解来实现失效缓存的清理。示例:

	@CacheResult
	@HystrixCommand
	public User getUserByid(@CacheKey("id") Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/ { 1} ", User.class,id);
	}

	@CacheRemove(commandKey = "getUserByid")
	@HystrixCommand
	public void update(@CacheKey ("id") User user) {
		return restTempla七e.postForObject("http://USER-SERVICE/users", user,User.class);
	}

  @CacheRemove 注解的 commandKey 属性是必须要指定的, 它用来指明需要使用请求缓存的请求命令, 因为只有通过该属性的配置, Hystrix 才能找到正确的请求命令缓存位置。

4.5 Hystrix相关属性

  根据实现 HystrixCommand 的不同方式将配置方法分为如下两类:
  1、当通过继承的方式实现时, 可使用 Setter 对象来对请求命令的属性进行设置,。示例:

	public HystrixCommandinstance(int id) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
			.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
			.withExecutionTimeoutinMilliseconds(SOO)));
			this.id = id;
	}

  2、当通 过注解的方法实现 时 , 只需使 用 @HystrixCommand 中的 command­Properties 属性来设置。示例:

	@HystrixConunand(conunandKey = "helloKey",
		commnandProperties = {
			@HystrixProperty(name="execution.is0lation.thread.timeoutinMilliseconds",value = "5000")
		}
	public User getUserByid(Long id) {
		return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class,id);
	}
4.5.1 Command属性

  Command属性主要用来控制HystrixCommand命令的行为。
  execution.isolation.strategy: 该属性用来设置HystrixCommand.run()执行的隔离策略, 它有如下两个选项:

  THREAD(默认值): 通过线程池隔离的策略。 它在独立的线程上执行, 并且它的并发限制受线程池中线程数量的限制。
  SEMAPHORE: 通过信号量隔离的策略。 它在调用线程上执行, 并且它的并发限制受信号量计数的限制。

  execution.isolation. thread.timeoutinMilliseconds: 该属性用来配置HystrixCommand执行的超时时间, 单位为毫秒。 当HystrixCommand执行时间超过该配置值之后, Hystrix会将该执行命令标记为TIMEOUT并进入服务降级处理逻辑。默认值是1000。
  execution.timeout.enabled: 该属性用来配置HystrixCommand.run()的执行是否启用超时时间。 默认为true。如果设置为false, 那么 execution.isolation.thread.timeoutinMilliseconds属性的配置将不再起作用。
  execution.isolation.thread.interruptOnTimeout: 该属性用来配置当HystrixCommand.run()执行超时的时候是否要将它中断。默认值为true。
  execution.isolation. thread.interruptOnCancel: 该属性用来配置当HystrixCommand.run()执行被取消的时候是否要将它中断。默认值为true。
  execution.isolation.semaphore.maxConcurrentRequests: 当HystrixCommand的隔离策略使用信号量的时候, 该属性用来配置信号量的大小(并发请求数)。 当最大并发请求数达到该设置值时, 后续的请求将会被拒绝。默认值为10。

4.5.2 fallback(降级)属性

  下面这些属性用来控制HystrixComrnand.getFallback ()的执行。 这些属性同时适用于线程池和信号量的隔离策略。
  fallback.isolation.semaphore.maxConcurrentRequests: 该属性用来设置从调用线程中允许HystrixComrnand.get Fallback()方法执行的最大并发请求数。 当达到最大并发请求数时, 后续的请求将会被拒绝并抛出异常(因为它已经没有后续的fallback可以被调用了)。默认值为10。
  fallback.enabled: 该属性用来设置服务降级策略是否启用, 如果设置为false,那么当请求失败或者拒绝发生时, 将不会调用HystrixComrnand.getFallback ()来执行服务降级逻辑。默认值为true。

4.5.3 circuitBreaker(断路器,用于熔断功能)属性

  下面这些是断路器的属性配置, 用来控制HystrixCircuitBreaker的行为。
  circuitBreaker.enabled: 该属性用来确定当服务请求命令失败时, 是否使用断路器来跟踪其健康指标和熔断请求。默认值为true。
  circuitBreaker.requestVolumeThreshold: 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。 例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。默认值为20。
  circuitBreaker.sleepWindowinMilliseconds: 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为“ 半开 ” 状态, 尝试熔断的请求命令,如果依然失败就将断路器继续设置为“ 打开” 状态,如果成功就设置为“ 关闭” 状态。默认值为5000。
  circuitBreaker.errorThresholdPercentage: 该属性用来设置断路器打开的错误百分比条件。 例如,默认值为 5000 的情况下,表示在滚动时间窗中,在请求数量超过circuitBreaker.requestVolumeThreshold阅值的前提下,如果错误请求数的百分比超过50, 就把断路器设置为“ 打开 ” 状态, 否则就设置为“ 关闭” 状态。默认值为50。
  circuitBreaker.forceOpen: 如果将该属性设置为 true, 断路器将强制进入“ 打开 ” 状态, 它会拒绝所有请求。 该属性优先于 circuitBreaker.forceClosed属性。默认值为false。
  circuitBreaker.forceClosed: 如果将该属性设置为 true, 断路器将强制进入“ 关闭 ” 状态, 它会接收所有请求。 如果 c江cuitBreaker.forceOpen 属性为true, 该属性不会生效。默认值为false。

4.6 Hystrix相关问题

4.6.1 当服务无法访问时,是直接熔断还是降级(先降级,后熔断)
  • 熔断和降级的区别
      1‌、触发条件不同‌。
      ‌熔断‌:熔断通常是由于某个服务(下游服务)故障引起的。当某个服务的响应时间过长或异常次数超过预设阈值时,熔断器会自动切断对该服务的请求,避免对整个系统造成更大的影响‌。
      ‌降级‌:降级主要是从整体负荷考虑,当系统整体负荷过高时,通过降低某些服务的功能或性能来保证核心服务的正常运行。降级不是由于单个服务故障引起的,而是为了应对整体负载过高的情况‌
      2‌、管理目标的层次不同‌。
      ‌熔断‌:熔断是一个框架级的处理,每个微服务都需要进行熔断处理,没有层级之分。熔断机制是为了保护整个服务框架的稳定性,防止单个服务的故障影响到整个系统‌。
      ‌降级‌:降级通常需要对业务有层级之分,一般从最外围服务开始进行降级处理。降级是为了在系统负荷过高时,优先保证核心服务的运行,逐步降低外围服务的性能或功能‌
    3‌、实现方式不同‌。
      熔断‌:熔断具有自我检测和恢复的功能。当检测到服务状态正常后,熔断器会自动恢复对该服务的请求‌。
      ‌降级‌:降级通常通过定义一个fallback方法来实现。当请求的远程服务出现异常时,可以直接使用fallback方法返回预设的异常信息,而不调用远程服务‌。
4.6.2 Hystrix如何实现熔断(统计请求失败比例,达到一定比例则断路器开启)

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

  如果检查出来频繁超时,就把consumer调⽤用provider的请求,直接短路路掉,不实际调用,而是直接返回一个mock的值。

4.6.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.6.4 熔断的原理,以及如何恢复(熔断器动态统计调用失败的比例,达到阈值时开启;然后一定时间后再半开;请求成功后则熔断器关闭)

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

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

4.6.5 Hystrix断路器的三种状态(关闭/打开/半开)

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值