一、概述
- 微服务之间可能会互相调用、级联调用,复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
- 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”
- 因此,对于一些延迟和错误,就需要有对应的处理服务,避免长时间等待造成整体服务的失败。
- Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
- 简单来说,熔断器Hystrix防止服务处理时间过长、异常而造成整体服务的失败。对于这些不正常的服务处理,采用备选方案fallback。赶紧先敷衍过去,别让这个服务导致整个服务器瘫痪。
- Hystrix官网
- 通过Hystrix可以实现服务降级、服务熔断。
二、服务降级
- 服务降级就是在调用服务的时候,调用
fallback
方法作为返回。 - 当程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满的时候,就会触发服务降级。
- 下面是实验过程
1. 导包
- 最重要的是
spring-cloud-starter-netflix-hystrix
这个包
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
复制代码
2. 配置文件
- 没什么特殊的,基本上都是标配
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://127.0.0.1:7001/eureka,http://127.0.0.1:7002/eureka
复制代码
3. 主启动类
- 在主启动类上添加一个
@EnableHystrix
注解,表示开启Hystrix
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@EnableHystrix
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
复制代码
4. 业务类
- 下面有一个运行正常的方法,有一个模拟耗时3s的方法
@Service
public class PaymentService {
public String runsWell(Integer id) {
return "一切正常,线程号:" + Thread.currentThread().getName() + ", id:" + id;
}
public String runsTimeOut(Integer id) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "执行超时,线程号:" + Thread.currentThread().getName() + ", id:" + id;
}
}
复制代码
- 请求处理。通过在方法上面标注
@HystrixCommand
声明降级后的处理方法及一些参数(比如说超时时长)
@Slf4j
@RestController
@RequestMapping("/hystrix")
public class HystrixController {
@Autowired
PaymentService paymentService;
@GetMapping("/ok/{id}")
public String runsWell(@PathVariable("id") Integer id) {
String result = paymentService.runsWell(id);
log.info("result: " + result);
return result;
}
// 目前设定了超时时间为3s,调用的方法是 runsTimeOutHandler
@HystrixCommand(fallbackMethod = "runsTimeOutHandler",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")})
@GetMapping("/timeOut/{id}")
public String runsTimeOut(@PathVariable("id") Integer id) {
String result = paymentService.runsTimeOut(id);
log.info("result: " + result);
return result;
}
// 设定了服务降级的处理方法是runsTimeOutHandler
@HystrixCommand(fallbackMethod = "runsTimeOutHandler")
@GetMapping("/exception/{id}")
public String runsException(@PathVariable("id") Integer id) {
Integer result = 10 / 0;
log.info("result: " + result);
return result.toString();
}
// 就是一个平平无奇的方法,值得一提的是,参数列表需要和被降级的方法相同
public String runsTimeOutHandler(Integer id) {
String result = "触发了服务降级,当前线程名为:" + Thread.currentThread().getName();
log.info(result);
return result;
}
}
复制代码
- 至此,完成了简单的服务降级。但有一个问题是,如果每个方法的参数都不同,就需要针对每个方法都写一个降级方法。
- 因此,可以配置一个默认的降级方法,当方法上面没有指定降级方法时,调用该方法。首先在类上面标注一个注解
@DefaultProperties
声明默认处理方法,再在需要fallback的方法上面标注@HystrixCommand
即可。
@Slf4j
@RestController
@RequestMapping("/hystrix")
@DefaultProperties(defaultFallback = "runsTimeOutHandler")
public class HystrixController {
@Autowired
PaymentService paymentService;
@GetMapping("/ok/{id}")
public String runsWell(@PathVariable("id") Integer id) {
String result = paymentService.runsWell(id);
log.info("result: " + result);
return result;
}
@HystrixCommand(
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")})
@GetMapping("/timeOut/{id}")
public String runsTimeOut(@PathVariable("id") Integer id) {
String result = paymentService.runsTimeOut(id);
log.info("result: " + result);
return result;
}
@HystrixCommand
@GetMapping("/exception/{id}")
public String runsException(@PathVariable("id") Integer id) {
Integer result = 10 / 0;
log.info("result: " + result);
return result.toString();
}
public String runsTimeOutHandler() {
String result = "触发了服务降级,当前线程名为:" + Thread.currentThread().getName();
log.info(result);
return result;
}
}
复制代码
5. 和Feign一起
- 对于Feign来说,也存在超时控制。对此,可以设置超时、异常、宕机之后的fallback
- 首先,需要在配置文件中开启
feign:
hystrix:
enabled: true # 在Feign中开启Hystrix
复制代码
- 这是feign的远程调用接口,在注解内声明了一个接口的fallback方法
@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = HystrixServiceFallback.class)
public interface HystrixService {
@GetMapping("/hystrix/ok/{id}")
String runsWell(@PathVariable("id") Integer id);
@GetMapping("/hystrix/timeOut/{id}")
String runsTimeOut(@PathVariable("id") Integer id);
@GetMapping("/hystrix/exception/{id}")
String runsException(@PathVariable("id") Integer id);
}
复制代码
- 针对上面的接口,写一个实现类,对应它声明的fallback方法
@Component
@Slf4j
public class HystrixServiceFallback implements HystrixService {
@Override
public String runsWell(Integer id) {
String result = "熔断版runsWell";
log.info(result);
return result;
}
@Override
public String runsTimeOut(Integer id) {
String result = "熔断版runsTimeOut";
log.info(result);
return result;
}
@Override
public String runsException(Integer id) {
String result = "熔断版runsException";
log.info(result);
return result;
}
}
复制代码
- 至此,完成了服务降级的基本内容。总的来说,对于微服务无论是调用方还是被调用方,都可以使用服务降级。可以针对某个方法写一个fallback方法,也可以写一个默认的fallback供多个方法使用,也可以针对feign的远程调用接口写fallback方法。
三、服务熔断
- 服务熔断就是,当在单位时间内触发了超过N次服务降级,并且服务降级占比超过阈值之后,就会触发服务熔断。当服务过来的时候,直接拒绝尝试服务调用,直接让服务降级处理。之后再慢慢尝试恢复。
- 相当于保险丝,当多个大功率电器过来之后,发现已经没办法正常工作了,那就直接烧断保险丝,暂时不处理服务。
- 相关论文
- 熔断机制:熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
- 对于Hystrix来说,当5s内出现20次调用失败,就会触发熔断。
- 熔断一般有三种状态:
- 熔断关闭:不会对服务进行熔断
- 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
- 熔断器三个重要的参数:
- 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
- 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
- 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
@HystrixCommand(fallbackMethod = "divFallback", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),
}) //开启,10次,10s, 60%
@GetMapping("/div/{i}")
public String div(@PathVariable("i") Integer i) {
int result = 10 / i;
log.info("result: " + result);
return Integer.toString(result);
}
复制代码
四、工作流程
- 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象。
- 命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 “事件源” 是否有 “订阅者”,都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 “订阅者” 都有可能是从 “事件源” 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 “订阅者” 的时候并不会发布事件,而是进行等待,直到有 “订阅者” 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
- 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式 返回。
- 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)。
- 线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)。
- Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知。
- Hystrix会将 “成功”、”失败”、”拒绝”、”超时” 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “熔断/短路”。
- 当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 “服务降级”。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于”熔断/短路”状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。
- 当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。
五、服务监控 hystrixDashboard
- 对于Hystrix的状态,可以通过hystrixDashboard这个工具进行查看。
- 首先,在配置类下添加一个组件
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
复制代码
- 打开
localhost:9001/hystrix
监视,并且设置参数。- Delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗。
- Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END