多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”、如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒中内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
如下图:
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务熔断是对应雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会熔断该节点微服务的调用,快速返回错误的响应信息. 当检测到该节点微服务调用响应正常后恢复调用链路。Hystrix会监控微服务间调用的状况,当失败的调用到一定值,缺省是5秒内20次调用失败就会启动熔断机制,注解@HystrixCommand。
下面看看如何实现服务熔断:
1.首先准备好服务提供者(能向外提供服务):
2.导入依赖:
org.springframework.cloud
spring-cloud-starter-hystrix
1.4.6.RELEASE
3.在启动类上加上对熔断器的支持:
@EnableCircuitBreaker
4.现在写一个普通的查询接口(为模拟服务调用链路出现的无响应等情况,我在查询内容为空时抛出了异常):
@RestController
public class QcpageProviderController {
@Autowired
QcpageService qcpageService;
@GetMapping("/qcpage/getById/{id}")
public Qcpage getById(@PathVariable("id")Integer id){
Qcpage qcpage=qcpageService.getById(id);
if(qcpage==null){
throw new RuntimeException();
}
return qcpage;
}
}
看看正常查询结果:
{"id":5,"job":"Java开发工程师","company":"深圳鹏开信息技术有限公司","place":"深圳-罗湖区","salar":"1.3-1.6万/月","data":"12-13","dbsource":"pachong"}
再看看异常查询结果:
这里直接显示了异常信息,这是非常不友好的,在真实的调用链路中,如出现了响应慢的情况,可能会一致等待下去,这样很占用资源且很耗时间的,甚至有可能导致整个系统雪崩。那么就可以使用hystrix服务熔断实现调用备选方案,快速为客户返回一个正常的结果。
使用hystrix服务熔断机制(@HystrixCommand):
@RestController
public class QcpageProviderController {
@Autowired
QcpageService qcpageService;
@GetMapping("/qcpage/getById/{id}")
@HystrixCommand(fallbackMethod="hystrixGetById")
public Qcpage getById(@PathVariable("id")Integer id){
Qcpage qcpage=qcpageService.getById(id);
if(qcpage==null){
throw new RuntimeException();
}
return qcpage;
}
//Hystrix熔断器备用方法,当上述方法不可用时调用此方法迅速返回信息,实现服务熔断,不至于服务雪崩
public Qcpage hystrixGetById(@PathVariable("id")Integer id){
Qcpage qcpage=new Qcpage()
.setId(id)
.setJob("未查询到该id相关信息")
.setPlace("未查询到该id相关信息")
.setCompany("未查询到该id相关信息")
.setData("未查询到该id相关信息")
.setDbsource("未查询到该id相关信息")
.setSalar("未查询到该id相关信息");
return qcpage;
}
}
再次看异常查询结果:
{"id":0,"job":"未查询到该id相关信息","company":"未查询到该id相关信息","place":"未查询到该id相关信息","salar":"未查询到该id相关信息","data":"未查询到该id相关信息","dbsource":"未查询到该id相关信息"}
这样一旦调用失败,就会向客户端返回一个正常的结果,让客户知道当前系统发生了什么。
服务降级,就是当服务器压力剧增的情况下,将某些服务停掉或不进行业务处理,来维持主要服务的功能。
看看如何实现:
首先要写一个失败回调的FallbackFactory实现类:
import cn.xrj.springcloud.pojo.Qcpage;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ServiceFallBackFactory implements FallbackFactory {
@Override
public JobInfoService create(Throwable cause) {
return new JobInfoService() {
@Override
public List getAll() {
return null;
}
@Override
public Qcpage getById(Integer id) {
Qcpage qcpage=new Qcpage()
.setId(id)
.setJob("该服务暂时关闭,敬请谅解")
.setPlace("该服务暂时关闭,敬请谅解")
.setCompany("该服务暂时关闭,敬请谅解")
.setData("该服务暂时关闭,敬请谅解")
.setDbsource("该服务暂时关闭,敬请谅解")
.setSalar("该服务暂时关闭,敬请谅解");
return qcpage;
}
@Override
public boolean deleteById(Integer id) {
return false;
}
};
}
}
然后在接口的@FeignClient注解里加上fallbackFactory属性,属性值为ServiceFallBackFactory.class:
@FeignClient(value = "springcloud-provider-jobinfo",fallbackFactory = ServiceFallBackFactory.class)
@Component
public interface JobInfoService {
@GetMapping("/qcpage/getAll")
List getAll();
@GetMapping("/qcpage/getById/{id}")
Qcpage getById(@PathVariable("id")Integer id);
@DeleteMapping("/qcpage/deleteById/{id}")
boolean deleteById(@PathVariable("id")Integer id);
}
由于服务降级属于客户端行为,所以还需要在Fegin消费者的配置文件里开启服务降级:
#开启服务降级功能
feign:
hystrix:
enabled: true
然后测试正常访问结果:
{"id":9,"job":"Java开发工程师","company":"深圳市品思达科技有限公司","place":"深圳-南山区","salar":"1-2万/月","data":"12-13","dbsource":"pachong02"}
如果需要关闭这个服务,为其他的服务提供资源。那客户访问的时候是不是会看到异常?下面看一下,我关闭这个服务:
{"id":9,"job":"该服务暂时关闭,敬请谅解","company":"该服务暂时关闭,敬请谅解","place":"该服务暂时关闭,敬请谅解","salar":"该服务暂时关闭,敬请谅解","data":"该服务暂时关闭,敬请谅解","dbsource":"该服务暂时关闭,敬请谅解"}
这就是服务降级,虽说服务不可用了,但是却还是能向客户端返回正确的信息。
1.触发原因不一样:服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
2.管理目标的层次不太一样:熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)