1.介绍
Hystrix是由Netflix公司开源的一个延迟和容错库,它通过隔离远程系统、服务或者第三方库之间的访问,防止级联失败并提供回退选项,从而提升系统的可用性、容错性与局部应用的弹性。
在分布式系统下,微服务之间不可避免地会发生相互调用,但每个系统都无法百分之百保证自身运行不出问题。在服务调用中,很可能面临依赖服务失效的问题(网络延时,服务异常,负载过大无法及时响应)。因此需要一个组件,能提供强大的容错能力,为服务间调用提供保护和控制。
我们的目的:当我自身 依赖的服务不可用时,服务自身不会被拖垮。防止微服务级联异常。
本质:就是隔离坏的服务,不让坏服务拖垮其他服务(调用坏服务的服务)。
2.功能介绍
2.1舱壁模式(资源隔离)
舱壁模式是一种隔离策略,隔离了每个工作负载或者说服务的关键资源,如连接池、内存、CPU和硬盘。每个工作单元都有独立的 连接池,内存,CPU。
- 使用舱壁避免了单个服务消耗掉所有资源,从而导致其他服务出现故障的场景。这种模式主要是通过防止由一个服务引起的级联故障来增加系统的弹性。
举例:
当UserController的请求过多,可能PersonController服务就没办法进行处理,也就是不让一个资源影响到其他资源模块。
思路:
可以对每个请求设置,单独的连接池,配置连接数,不要影响 别的请求。
优点:
- 服务提供者高延迟或异常,不会影响到整个系统的失败。
- 能够控制每个调用者的并发度。因为有独立的线程池。
2.2雪崩效应
如果服务提供者响应非常缓慢,那么服务消费者调用此提供者就会一直等待,直到提供者响应或超时。在高并发场景下,此种情况,如果不做任何处理,就会导致服务消费者的资源耗竭甚至整个系统的崩溃。一层一层的崩溃,导致所有的系统崩溃
雪崩三个流程(不可用的范围 被逐步放大):
- 服务提供者不可用:
- 重试会导致网络流量加大,更影响服务提供者。
导致服务调用者不可用,由于服务调用者 一直等待返回,一直占用系统资源。
服务不可用原因:
- 服务器宕机
- 网络故障
- 宕机
- 程序异常
- 负载过大,导致服务提供者响应慢
- 缓存击穿导致服务超负荷运行
2.3容错机制
容错性是一种特性,它使系统能够在某些组件发生错误时仍能继续正常地运行。
容错性一般包括:
- 超时(Timeouts)
- 重试(Retries)
- 熔断机制(Circuit Breaker)
- 截止时间(Deadlines)
- 限流器(Rate limiters)
2.4断路器(熔断)
当请求某个服务有大量的超时(说明该服务不可用)。再次请求也没有意义的,只会消耗资源,微服务在高并发场景中,因为对服务请求时间过长,可能就会出现
雪崩
。
-
断路器是对容易导致错误的操作的代理。这种代理能统计一段时间内的失败次数,并依据次数决定是正常请求依赖的服务还是直接返回。
-
断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(超时),就会在之后的一段时间,强迫对该服务的调用快速失败,即不再请求所调用的服务。这样对于消费者就无须再浪费CPU去等待长时间的超时。
-
断路器也可自动诊断依赖的服务是否恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。通过重置时间来决定断路器的重新闭合。
这样就实现了微服务的“自我修复”:当依赖的服务不可用时,打开断路器,让服务快速失败,从而防止雪崩。当依赖的服务恢复正常时,又恢复请求。
2.5降级
当前服务无法处理调用方法,向调用方法返回一个预期且可备选的响应方案。
2.6熔断vs降级
共同点:
1、为了防止系统崩溃,保证主要功能的可用性和可靠性。
2、用户体验到某些功能不能用。
不同点:
1、熔断由下级故障触发,主动惹祸。
2、降级由调用方从负荷角度触发,无辜被抛弃。
3.使用
3.1整合Resttemplate
服务调用方
@HystrixCommand(fallbackMethod = "back")
public String alive() {
// 自动处理URL
RestTemplate restTemplate = new RestTemplate();
String url ="http://user-provider/User/alive";
String object = restTemplate.getForObject(url, String.class);
return object;
}
public String back() {
return "请求失败~bbb...";
}
启动类要配置
@EnableCircuitBreaker
3.2整合Feign
配置
feign.hystrix.enabled=true
接口
@FeignClient(name = "user-provider",fallback = AliveBack.class)
public interface ConsumerApi {
@RequestMapping(value = "/User/alive",method = RequestMethod.GET)
public String alive();
@RequestMapping(value = "/User/getById",method = RequestMethod.GET)
public String getById(Integer id);
}
实现
package com.mashibing.UserConsumer;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
public class AliveBack implements ConsumerApi{
@Override
public String alive() {
// TODO Auto-generated method stub
return "aaa";
}
@Override
public String getById(Integer id) {
// TODO Auto-generated method stub
return null;
}
}
3.3使用fallbackFactory检查具体错误
实现类
@Component
public class WebError implements FallbackFactory<ConsumerApi> {
@Override
public ConsumerApi create(Throwable cause) {
// TODO Auto-generated method stub
return new ConsumerApi() {
@Override
public Person postPserson(Person person) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getById(Integer id) {
// TODO Auto-generated method stub
return null;
}
@Override
public String alive() {
// TODO Auto-generated method stub
System.out.println(cause.getLocalizedMessage());
cause.printStackTrace();
return ToStringBuilder.reflectionToString(cause);
}
@Override
public Map<Integer, String> postMap(Map<String, Object> map) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<Integer, String> getMap3(Map<String, Object> map) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<Integer, String> getMap2(Integer id, String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<Integer, String> getMap(Integer id) {
// TODO Auto-generated method stub
return null;
}
};
}
}
针对不同异常返回响应
@Override
public String alive() {
// TODO Auto-generated method stub
System.out.println(cause);
if(cause instanceof InternalServerError) {
System.out.println("InternalServerError");
return "远程服务报错";
}else if(cause instanceof RuntimeException) {
return "请求时异常:" + cause;
}else {
return "都算不上";
}
}
3.4信号量隔离与线程隔离
默认情况下hystrix使用线程池控制请求隔离
线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。
信号量隔离主要维护的是Tomcat的线程,不需要内部线程池,更加轻量级。
配置
hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore
thread 通过线程数量来限制并发请求数,可以提供额外的保护,但有一定的延迟。一般用于网络调用
semaphore 通过semaphore count来限制并发请求数,适用于无网络的高并发请求
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms
hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
semaphore应该占整个容器(tomcat)的线程池的一小部分。
Feign下配置
hystrix.command.default.execution.isolation.strategy=SEMAPHORE
可视化界面
启动类
@EnableHystrixDashboard
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>
spring-cloud-starter-netflix-hystrix-dashboard
</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
健康上报
http://localhost:90/actuator/hystrix.stream
图形化
http://localhost:90/hystrix