Spring cloud的保护机制(Hystrix)
一: 概述
很多系统中应考虑横向扩展,单点故障问题。在出现故障时,为了较少故障的影响,保障集群的高可用,应增加保护机制,Hystrix就是其中的一种保护机制
- Hystrix通过添加延迟阈值以及容错的逻辑,来帮助我们控制分布式系统间组件的交互。
- Hystrix通过隔离服务间的访问点、停止他们之间的级联故障,提供可回退操作来实现容错。
Hystrix的功能:
- 当所依赖的网络服务发生延迟或者失败时,对访问的客户端程序进行保护。
- 在分布式系统中,停止级联故障
- 网络服务恢复正常时,可以快速恢复客户端的访问能力
- 调用失败时执行服务回退
- 可支持实时监控、报警、和其他操作
1.1 Hystrix程序
1.2 Hystrix的使用
内部流程
从之前的例子看,当服务器出现无响应现象的时候,Hystrix会自动使用容错机制,看似简单,其实有一套较为复杂的执行逻辑
- 在命令开始执行时,会做一些准备工作(如:为命令创建相应的线程池等)
- 判断是否打开了缓存,打开了缓存就直接查找缓存并返回结果
- 判断断路器是否打开,如果打开了,就表示链路不可以用,直接执行回退方法
- 判断线程池、信号量等条件(如:线程池超负荷,则返回回退方法,否则,就去执行命令的内容)
- 执行命令,判断是否要对断路器进行处理,执行完成后,如果满足一定条件,则需要开启断路器
整个流程最主要的点,就是在于断路器是否被打开
1.3 命令执行
- toObservable:返回一个最原始的可观察的实例,是一个RxJava的类,可观察命令的执行过程,并且将执行的信息转递给订阅者(命令不会马上执行,只有当返回的可观察实例被订阅之后,才会真正执行)
- observe: 用toObservable获得最原始的可观察实例后,再使用一个replay subject作为原始toObservable的订阅者(会立刻执行)
- queue
- execute
1.4 配置属性
1.5 回退
列举三种情况下触发回退
- 断路器被打开
- 线程池、队列、信号量满载
- 实际执行命令失败
package com.atm.cloud.config;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
public class MyTimeOutConfig extends HystrixCommand<String> {
public MyTimeOutConfig() {
// 第二种方法:
// super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
// 超时时间设置成2s
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(2000)));
// 第一种方法:设置超时时间(设置成2s)
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(
20000);
}
@Override
protected String run() throws Exception {
// 手动延迟3s
// Thread.sleep(3000);
System.out.println("执行命令...");
return "Success";
}
// 回退方法
@Override
protected String getFallback() {
System.out.println("执行回退...");
// return super.getFallback();
return "Fallback...";
}
}
如果断路器打开,则会执行回退方法,
如果断路器打开但没有提供回退方法,系统则会报异常
1.6 回退的模式
Hystrix回退机制比较灵活,
你可以在A命令的回退方法中执行B命令,如果B命令执行也失败,同意也可以触发B命令的回退
1.7 断路器开启
断路器一旦开启,就会直接
- 调用回退方法,
- 不再执行命令,
- 也不会更新链路的健康状况
断路器开启要满足两个条件
- 整个链路达到一定阈值,默认情况下,10秒内产生超过20次请求,则符合第一个条件
- 满足第一个条件下,如果请求的错误百分比大于阈值,则会打开断路器,默认为50%
package com.atm.cloud.open;
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts;
import com.netflix.hystrix.HystrixCommandProperties;
public class CloseMain {
public static void main(String[] args) throws Exception {
// 10秒内大于3次请求,满足第一个条件
ConfigurationManager
.getConfigInstance()
.setProperty(
"hystrix.command.default.circuitBreaker.requestVolumeThreshold",
3);
boolean isTimeout = true;
for (int i = 0; i < 10; i++) {// 发送10次请求,一开始均为失败,则会打开断路器
TestCommand c = new TestCommand(isTimeout);// 总会超时
c.execute();
// 输出健康信息
HealthCounts hc = c.getMetrics().getHealthCounts();
System.out.println("断路器状态:" + c.isCircuitBreakerOpen() + ", 请求数量:"
+ hc.getTotalRequests());
if (c.isCircuitBreakerOpen()) {// 如果断路器打开
isTimeout = false;//
System.out.println("============ 断路器打开了,等待休眠期结束");
Thread.sleep(6000);// 等待休眠期结束,6s之后尝试性发一次请求
}
}
}
static class TestCommand extends HystrixCommand<String> {
private boolean isTimeout;
public TestCommand(boolean isTimeout) {
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(500)));// 500毫秒没有响应,则打开断路器
this.isTimeout = isTimeout;
}
@Override
protected String run() throws Exception {
if (isTimeout) {
Thread.sleep(800);// 方法执行时间为800毫秒
} else {
Thread.sleep(200);
}
return "";
}
@Override
protected String getFallback() {
return "fallback";
}
}
}
1.8 断路器关闭
- 当断路器打开的时候,不再执行命令,直接进行回退方法,这段时间称为休眠期,默认时间为5s
- 休眠期结束之后,会尝试性执行一次命令。
- 此时,断路器状态处于半开状态,尝试执行成功之后,就会关闭断路器并且清空健康信息,
- 如果失败,断路器会继续保持打开的状态
1.9 隔离机制
命令的真正执行,除了断路器要关闭外,还需要再过一关
- 执行命令的线程池或者信号量是否满载
- 如果满载,命令就不会执行,而是直接出发回退
Hystrix提供了两种隔离策略
- thread(线程,消耗可能大点,异步超时)
- semaphore(信号量,不支持超时、异步)
1.9.1 编写命令
package com.atm.cloud;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
public class MyCommand extends HystrixCommand<String> {
public int index;
public MyCommand(int index) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
.asKey("ExampleGroup")));
this.index = index;
}
@Override
protected String run() throws Exception {
Thread.sleep(500);
System.out.println("run(),当前索引:" + index);
return "success";
}
@Override
protected String getFallback() {
System.out.println("getFallback(),当前索引:" + index);
return "fallback";
}
}
- 线程隔离测试,编写测试类
package com.atm.cloud;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
public class MyCommand extends HystrixCommand<String> {
public int index;
public MyCommand(int index) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
.asKey("ExampleGroup")));
this.index = index;
}
@Override
protected String run() throws Exception {
Thread.sleep(500);
System.out.println("run(),当前索引:" + index);
return "success";
}
@Override
protected String getFallback() {
System.out.println("getFallback(),当前索引:" + index);
return "fallback";
}
}
- 信号量隔离测试
package com.atm.cloud;
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
public class SemaphoreMain {
public static void main(String[] args) throws Exception {
// 信号量策略,默认最大并发数10
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.default.execution.isolation.strategy",
ExecutionIsolationStrategy.SEMAPHORE);
// 设置最大并发数为2
ConfigurationManager
.getConfigInstance()
.setProperty(
"hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests",
2);
for (int i = 0; i < 6; i++) {
final int index = i;
Thread t = new Thread() {
public void run() {
MyCommand c = new MyCommand(index);
c.execute();
}
};
t.start();
}
Thread.sleep(5000);
}
}
1.10 请求缓存
在一起请求中,多个地方调用同一个接口,可考虑使用缓存
需要用到CommandKey
合并请求,请求缓存,在一次请求的过程中才能实现,因此需要先初始化请求上下文