熔断的意义
在微服务架构中,服务之间相互调用,当有一个服务出错时,会导致依赖它的服务也错误,进而导致整个微服务集群都出现问题,所以采用熔断机制,当服务调用出现错误时,满足熔断策略,调用方主动熔断服务,返回预制的错误信息。
熔断的用法
robbion方式
在启动类上添加注解@EnableCircuitBreaker
ribbon方式是显示的在方法上添加@HystrixCommand注解,表示方法会调用远程方法,远程方法调用失败时,按照注解中的配置进行熔断,比较好理解
@GetMapping("/hello")
@HystrixCommand(fallbackMethod = "helloFaile")
public String hello(){
totle++;
restTemplate.getForObject("http://APP/app/exception",String.class);
return totle + ":" + error;
}
public String helloFail(){
error++;
return totle + ":" + error;
}
当restTemplate调用失败(超时或者出现异常)时,就会调用helloFail方法进行返回。
fallbackMethod中的参数为失败时回调方法名,helloFail的参数必须和hello方法的参数个数、类型完全一样。
Feign方式
Feign方式是将我们需要调用的远程方法定义到一个接口中,在接口中定义远程服务名,远程url,调用失败时的回调类。在我们的业务代码中需要调用远程方法时,不在像ribbon一样直接通过RestTemplate调用远程方法,而是通过我们自己定义的接口。
- 启动类上添加@EnabledFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@EnableFeignClients
public class DemoStarter {
public static void main(String[] args) {
SpringApplication.run(DemoStarter.class,args);
}
}
- 定义一个接口,接口中方法为我们需要调用的远程方法
@FeignClient(name = "app",fallback = RemoteMethodHystrix.class)
public interface IRemoteMethod {
@GetMapping("/app/exception")
String hello();
}
@FeignClient注解中name为远程服务名(eureka中注册的服务名),fallback为接口熔断时的回调方法。
方法上@Getmapping中的url为远程服务中提供的接口地址
- 定义一个接口实现,实现我们定义的IRemoteMethod接口,作为远程接口调用失败时的回调
@Component
public class RemoteMethodHystrix implements IRemoteMethod{
private int count;
private int error;
@Override
public String hello() {
return "error:"+(++error);
}
}
- 服务调用
我们调用的时候,只需要注入我们定义的接口IRemoteMethod(spring 默认给我做了实现)
@Autowired
private IRemoteMethod remoteMethod;
@GetMapping("/helloFeign")
public String helloFeign(){
String rest = remoteMethod.hello();
if(rest.startsWith("success")){
totle++;
return "totle:"+totle;
}else{
return rest;
}
}
- 添加配置
在application.yml中添加开启feign的配置
feign:
hystrix:
enabled: true
熔断验证
验证熔断我们至少需要三个项目,erueka-server,app,demo。app作为服务提供方,demo作为服务调用方。
app提供接口
@GetMapping("/exception")
public String hello(){
totle++;
if(System.currentTimeMillis() % 2 == 0){
System.out.println(totle + "--"+ error);
error++;
Integer.parseInt("a");
}
System.out.println(totle + "--"+ error);
return "success";
}
我们这里采用随机报异常的方式,当此方法报异常时,会触发服务调用方(demo)的熔断。
当没有配置熔断或者熔断不起效时,调用方会直接报错返回到页面(没有异常处理的情况下)
开启熔断,接口没有异常,正常返回
接口报错,返回熔断的callback方法
疯狂刷新页面调用接口,接口多次失败,达到熔断阈值,此时看app和demo控制台发现demo根本不会调用app服务,直接返回了callback方法中的值(始终是error),过一会在刷新页面,发现demo又开始调用app接口了,此时是熔断关闭,又开始正常调用了。
熔断原理
熔断主要是为了解决雪崩问题,我们先搞清楚雪崩的原因,导致雪崩的原因有很多种,以流量突增为例。
大量用户请求A,A依赖B,B依赖C。C在处理请求时,首先有个线程池来接收B的调用请求,然后转到处理器中处理请求。
如果C未对连接池设置大小限制,则C会为每个请求创建socket连接,维护在请求队列中,由于C的处理缓慢,最终会到导致C创建大量的socket连接而耗尽C的资料,进而C宕机,而此时B、A又积累了大量的请求,最终导致C、B、A依次宕机。
如果C设置了连接池大小,对超过C最大值的B请求拒绝掉,对于失败的请求B、A、用户都有可能进行重试,B、A中会积累大量用户请求,进而导致B、A中其他业务受影响,无资源可用。
熔断还需要处理程序报错的问题,就像上面演示的那样。
hystrix通过资源隔离的方式,将各个依赖调用相互隔离,保证一个依赖出了问题(被阻塞),不会过多的占用系统资源(也有限流作用),导致其他依赖不能使用,同时使用断路器保存每个依赖的调用情况,统计失败次数,达到预定阈值,打开断路器,后续调用不在执行。通过命令模式将依赖调用封装,调用失败时返回封装对象中的默认返回。
熔断处理流程
资源隔离(上图中的连接池判断、执行)
熔断监控(上图中的统计执行情况)
熔断配置
hystrix提供很多配置项,总体分为三大类,command properties 、Collasper properties、Thread pool propertis。大部分配置都属于Command Properties,共包括五类,
Execution:配置线程池和信号量相关参数
Fallback:配置回调相关参数
Circuit Breaker: 配置断路器相关参数
Metrics:配置汇总统计相关参数
Request Context:配置reqeust上下文相关参数
在熔断流程中,大概控制的环节如下