在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,最终导致自身服务的瘫痪。
除了可能导致自身服务的崩溃还可能导致服务雪崩,即服务D是一个辅助类型服务,整个业务不依赖于D服务,某天D服务突然响应时间变长,导致了核心服务C响应时间变长,其上请求越积越多,C服务也出现了响应变慢的情况,由于A,B强依赖于服务C,故而一个无关紧要的服务却影响了整个系统的可用。雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估,做好熔断,隔离,限流。
在Spring Cloud中使用了Hystrix 来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
1.断路器工作原理
两种资源隔离模式
(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
(2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
默认使用线程池 如果不涉及远程RPC调用(没有网络开销),比如访问内存缓存,则使用信号量来隔离,更为轻量,开销更小。
服务降级、回退:降级就是当依赖的服务产生故障时,把产生故障的丢了,换一个轻量级的方案,是一种退而求其次的方法,比如调用积分系统发生熔断,那么在熔断方法里将这次的请求放到数据库或者消息队列里面,等积分系统好了之后再处理。
服务的健康状况 = 请求失败数 / 请求总数.
熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的.
- 当熔断器开关关闭时, 请求被允许通过熔断器. 如果当前健康状况高于设定阈值, 开关继续保持关闭. 如果当前健康状况低于设定阈值, 开关则切换为打开状态.
- 当熔断器开关打开时, 请求被禁止通过.
- 当熔断器开关处于打开状态, 经过一段时间后, 熔断器会自动进入半开状态, 这时熔断器只允许一个请求通过. 当该请求调用成功时, 熔断器恢复到关闭状态. 若该请求失败, 熔断器继续保持打开状态, 接下来的请求被禁止通过.
熔断器的开关能保证服务调用者在调用异常服务时, 快速返回结果, 避免大量的同步等待. 并且熔断器能在一段时间后继续侦测请求执行结果, 提供恢复服务调用的可能.
具体流程:
1、 创建一个HystrixCommand或HystrixObservableCommand实例
第一步就是构建一个HystrixCommand或HystrixObservableCommand实例来向其他的组件发出操作请求,通过构造方法创建实例。
HystrixCommand:返回一个单响应
HystrixObservableCommand: 返回一个观察者发出的响应
2. 这里 有 4个 方法,前两只适用于HystrixCommand不适用于HystrixObservableCommand
execute():阻塞型方法,返回单个结果(或抛出异常)
queue(): 异步方法,返回一个Future对象,可以从中取出单个结果(或抛出异常)
observe(): 返回Observable对象
toObserve(): 返回Observable对象
3. 缓存判断
检查缓存内是否有对应指令的结果,如果有的话,将缓存的结果直接以Observable对象形式返回。
4. 断路器判断
检查 Circuit Breakerr的状态如果 Circuit Breaker的状态为开启 Hystrix将不会执行对应指令,而是直接进入失败处理状态(图中 8)。如果Circuit Breaker的状态为关闭,Hystrix会继续执行(图 5)
5. 线程池、任务队列、信号量的检查
确认是否有足够的资源执行操作指令。当线程池和队列(或者是信号量不使用隔离模式的时候 资源满的时候 ,Hystrix将不会执行对应指令并且会直接进入失败处理状态 (图 8)
6. HystrixObservableCommand.construct()和HystrixCommand.run()
如果资源充足,Hystrix将会执行操作指令,操作指令的调用最终都会到这两个方法:
HystrixCommand.run():返回一个响应或者抛出一个异常。
HystrixObservableCommand.construct(): 返回一个可观测的发出响应(s)或发送一个onError通知
如果执行指令的时间超时,执行线程会抛出TimeException异常。Hystrix会抛弃结果并直接进入失败状态。如果执行指令成功,Hystrix会进行一系列的数据记录,然后返回执行的结果。
7. 统计断路器的健康情况
Hystrix会根据记录的数据来计算失败比率,一旦失败比率达到某一阀值将自动开启Circuit Breaker
8. 回退
如果我们再Command中实现了HystrixCommand..getFallback方法(或HystrixObservableCommand.resumeWithFallback())方法,Hystrix会返回对应方法的结果,如果没有实现这些方法,Hystrix仍然会返回一个空的Observable对象,并且可以通过onError来终止并处理错误。
调用不同的方法放回不同的结果:
execute(): 将会抛出异常
queue(): 将会返回一个Future对象,如果调用它的get()方法将会抛出异常。
observe()和toObservable():都会返回上述的Observable对象
9. 返回成功
如果Hystrix执行成功,返回的响应取决于在步骤2中调用命令。
execute():阻塞型方法,返回单个结果(或抛出异常)
queue(): 异步方法,返回一个Future对象,可以从中取出单个结果(或抛出异常)
observe(): 返回Observable对象
toObserve(): 返回Observable对象
2.Ribbon中引入Hystrix
pom里面引用hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
改造原来的服务消费方式,在使用ribbon消费服务的函数上增加@HystrixCommand注解来指定回调方法。
@RequestMapping("/getConfig")
@HystrixCommand(fallbackMethod = "errorFallback")
public String getConfig(){
restTemplate.getForObject("http://eureka-points:8083/getConfig",String.class);
return foo+"--"/*+userPoints.getPoints()*/;
}
public String errorFallback() {
return "error";
}
当访问getConfig时,如果eureka-points服务全部宕机或者hystrix线程池满了等,那么hystrix会直接执行熔断方法,而不是等待响应超时。
3.Feign中使用Hystrix
Feign中已经依赖了Hystrix,无需额外添加依赖。
改造我们的UserPointsFeign接口
package com.cjc.spring.cloud.user.service.feign;
import com.cjc.spring.cloud.user.service.hystrix.UserPointsHystrix;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "eureka-points",fallback = UserPointsHystrix.class)
public interface UserPointsFeign {
@RequestMapping("/getPoints")
public String getPoints();
}
在@FeignClient中加入fallback,UserPointsHystrix实现UserPointsFeign接口
package com.cjc.spring.cloud.user.service.hystrix;
import com.cjc.spring.cloud.user.service.feign.UserPointsFeign;
import org.springframework.stereotype.Service;
@Service
public class UserPointsHystrix implements UserPointsFeign {
@Override
public String getPoints() {
return "短路器熔断,请求降级";
}
}
即系统熔断时会走UserPointsFeign中对应的请求方法。
注:当使用Ribbon中的Hystrix时,如果判断服务宕机,那么会执行断路器,并且直接return当前方法,返回断路器的返回值,而使用Feign的Hystrix时,如果判断服务宕机,那么会执行断路器,而且会继续往下执行。