在微服务架构中,往往会涉及到众多的微服务,比如说现有三个微服务,微服务A,微服务B,微服务C,而它们之间的依赖关系为,微服务C依赖于微服务B,微服务B又依赖于微服务A,现假设微服务A,不可用,那么受此连累,微服务B会一直等待响应,最终由于线程资源耗尽,微服务B也挂掉,而微服务C受微服务B的影响,也被连带挂掉。此时就出现由于微服务A挂掉,导致整个微服务项目架构出现了雪崩式的全部挂掉,此时就需要一种服务消费者容错思想和模式,而Spring Cloud Netflix Hystrix就是在这种思想模式下诞生的。
Spring Cloud Netflix Hystrix的熔断机制主要包括服务隔离,服务熔断与服务回退
在微服务的架构设计中存在一种舱壁隔离模式(Bulkhead Isolation Pattern),顾名思义就是像舱壁一样对资源或失败单元进行隔离,如果一个船舱破了进水,只损失一个船舱,其它船舱可以不受影响。舱壁隔离模式在微服务架构中的应用就是各种服务隔离思想。
所谓的隔离,本质上是对系统或资源进行分割,从而实现当系统发生故障时能够限定传播范围和影响范围,即发生故障后只有出问题的服务不可用,而保证其他服务仍然可用。
而服务的隔离又分为两种情况,分别是线程隔离与进程隔离
线程隔离主要通过线程池(Thread Pool)进行隔离,在实际使用时我们会把业务进行分类并交给不同的线程池进行处理。当某个线程池处理一种业务请求发生问题时,不会将故障扩散到其它线程池,也就是说不会影响到其它线程池中所运行的业务,从而保证其它服务可用。
线程隔离机制通过为每个依赖服务分配独立的线程池以实现资源隔离。当其中的一个服务所分配的线程被耗尽,也就是该服务的线程全部处于同步等待状态,也不会影响其他服务的调用,因为其他服务运行在独立的线程池中。
进程隔离相较于线程隔离更加的容易理解,进程隔离就是将一个系统拆分为多个子系统以实现子系统间的物理隔离。由于各个子系统运行在独立的容器和JVM中,通过进程隔离使得某一个子系统出现问题不会影响到其它子系统。
从进程隔离的角度讲,对系统进行微服务建模和拆分就是一种具体的实现方式,每个服务独立部署和运行,各个服务之间实现了物理隔离。服务隔离还包括集群隔离、机房隔离和读写隔离等其他表现形式。
Spirng Cloud Netflix Hystrix实现了线程隔离机制,同时还提供了基于信号量的隔离方案。
服务熔断的概念来源于电路系统,在电路系统中存在一种熔断器(Circuit Breaker),当流经该熔断器的电路过大时就会自动切断电路。在微服务架构中,也存在类似现实世界中的服务熔断器,当某个异常条件被触发,直接熔断整个服务,而不是一直等待该服务超时。
这里需要注意的是,服务熔断器会把所有的调用结果都记录下来,如果发现异常的调用次数在单位时间内达到一定的阈值,那么服务熔断机制才会被触发,快速失败就会生效;反之则按照正常的流程执行远程调用。也就是说服务熔断器本身是有状态的,其状态主要分为Closed、Open、Half-Open三种情况。
熔断器关闭状态,不对服务调用进行限制,但会对调用失败次数进行积累,达到一定阈值或比例时会启动熔断机制。
熔断器打开状态,此时对服务的调用将直接返回错误,不执行真正的网络调用。同时,熔断器设置了一个时钟选项,当时钟达到一定时间(这个时间一般设置成平均故障处理时间,也就是MTTR)时会进入半熔断状态。
半熔断状态,允许一定量的服务请求,如果调用都成功或达到一定比例则认为调用链路已恢复,关闭熔断器;否则认为调用链路仍然存在问题,又回到熔断器打开状态。
服务回退(Fallback)是处理因为服务依赖而导致的服务调用失败的一种有效容错机制(服务调用失败的情况包括异常、拒绝、超时、短路)。当远程服务调用发生失败时,服务回退不是直接抛出该异常,而是产生另外的处理机制来应对该异常,相当于执行了另一条路径上的代码或返回一个默认的处理结果,而这条路径上的代码或这个默认处理结果一定满足业务逻辑的实现需求,它的作用只是告知服务的消费者当前调用中存在问题。
显然,服务回退不能解决由异常引起的实际问题,它只是一种权宜之计。
在Netflix Hystrix中,HystrixCommand是重中之重,在Netflix Hystrix的整个机制中涉及依赖边界的地方,都是通过这个HystrixCommand进行封装。我们在使用HystrixCommand时需要提供他的子类,子类主要实现的方法有两个,分别是run()
和getFallBack()
。
该方法用于实现所依赖的业务逻辑,或者说是实现微服务之间的调用。
使用Hystrix命令模式封装依赖逻辑并提供Fallback方法实现服务回退策略。
在注解中配置Hystrix请参考之前写的文章,源码在之前的文章SpringCloud集成NetflixRibbon实现负载均衡中有写到。其位于GoodsOrderService
类中,其代码片段如下。
/** * 获取第一页10条订单信息 * @param pageIndex 当前页索引值 * @param pageSize 每页的信息条数 * @return */
@HystrixCommand(
fallbackMethod = "getOrdersFallback" // 失败时执行的方法
,threadPoolKey = "orderThreadPool"
,threadPoolProperties = {
@HystrixProperty(name = "coreSize",value = "30")
,@HystrixProperty(name = "maxQueueSize",value = "10")
}
,commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
}
)
public List<Orders> getOrders(int pageIndex,int pageSize){
return goodsOrderRepository.findAll(new PageRequest(pageIndex - 1,pageSize)).getContent();
}
/** * Hystrix 失败回退方法(其中包含超时等情况) * @param pageIndex * @param pageSize * @return */
private List<Orders> getOrdersFallback(int pageIndex,int pageSize){
List<Orders> fallbackList = new ArrayList<>();
Orders orders = Orders.builder()
.id(1L)
.accountId(0L)
.item("Order list is not available")
.createTime(new Date())
.build();
fallbackList.add(orders);
return fallbackList;
}
说明:
fallbackMethod
配置的信息为失败回退方法,当getOrders()
方法执行异常时(也可能是超时),此时便会执行异常回退中的getOrdersFallback()
方法。
orderThreadPool
代表的是自定义的订单线程池,coreSize
代表的是并发执行的最大线程数,此处定义的是30个,maxQueueSize
代表最大队栈容量,此处设置为10。
通过上面的注解配置,我们可以看出,注解配置的优点是可以给每一个服务配置相应的Hystrix配置项,更加的灵活。但是其缺点也很明显,就是没有通用配置,这意味着该微服务中的众多服务项都得一一配置,维护起来相应的比较麻烦,不过下面的第二种方法却可以有效解决该痛点。
GoodsOrderService
中对应的代码变更为如下:
@HystrixCommand(
fallbackMethod = "getOrdersFallback" // 失败时执行的方法
,commandKey = "helloCommand"
,groupKey = "helloGroup"
)
public List<Orders> getOrders(int pageIndex,int pageSize){
// 当执行下面的代码时,程序将延时4秒执行,由于Hystrix的默认设置最高延迟为5秒,而helloCommand的延迟为3秒
// ,则此处的延迟4秒意味着下面查询方法将不会执行
/*try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); }*/
return goodsOrderRepository.findAll(new PageRequest(pageIndex - 1,pageSize)).getContent();
}
/** * Hystrix 失败回退方法(其中包含超时等情况) * @param pageIndex * @param pageSize * @return */
private List<Orders> getOrdersFallback(int pageIndex,int pageSize){
List<Orders> fallbackList = new ArrayList<>();
Orders orders = Orders.builder()
.id(1L)
.accountId(0L)
.item("Order list is not available")
.createTime(new Date())
.build();
fallbackList.add(orders);
return fallbackList;
}
application.yml
文件中对应的配置信息为:
# Hystrix 配置项
hystrix:
command:
helloCommand:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
threadpool:
helloGroup:
coreSize: 1
说明:
在上面的代码中,我们使用了Hystrix的通用配置,其中我们注意到,在配置文件中有并列的helloCommand
与default
,而其配置项分别为超时3秒与超时5秒,则如果我们配置了commandKey = "helloCommand"
,则我们使用的是超时3秒的设置项,而如果我们没有配置commandKey = "helloCommand"
,此时我们默认使用的是default
中的超时5秒设置项。