引入依赖
<dependency> <!-- 引入eureka client -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency> <!-- hystrix -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency> <!-- openfeign -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
开启熔断
在启动类上加上注解
@EnableCircuitBreaker // 开启断路器
@EnableFeignClients // 开启 Feign
添加配置
开启Fegin的hystrix
OpenFeign 是自带 Hystrix 的,但是默认没有开启,在 application.properties 中添加以下配置开启 Hystrix
# 开启feign下面的hystrix功能
feign.hystrix.enabled: true
对应开启配置的源码在 FeignClientsConfiguration.java中
// org.springframework.cloud.openfeign.FeignClientsConfiguration.HystrixFeignConfiguration
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
配置Hystrix参数
# 开启全局超时
hystrix.command.default.execution.timeout.enabled=true
# 开启访问缓存功能
hystrix.command.default.requestCache.enabled=true
# 隔离策略,默认线程隔离
hystrix.command.default.execution.isolation.strategy=THREAD
########################### 线程隔离 ##############################
# 线程隔离的超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000
# 超时以后中止线程
hystrix.command.default.execution.isolation.thread.interruptOnTimeout=true
# 取消的时候终止线程
hystrix.command.default.execution.isolation.thread.interruptOnFutureCancel=true
####################################################################
########################### 信息量隔离(与线程隔离不能同时存在) ###
# 最大并发请求数
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=100
####################################################################
# 开启熔断的功能
hystrix.command.default.circuitBreaker.enabled=true
# 在一定的时间窗口内,请示5个以上以后,才开始进行熔断判断
hystrix.command.default.circuitBreaker.requestVolumeThreshold=5
# 当熔断开启以后,经过多少秒再进入半开状态
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=15000
# 超过50%的失败请求,则熔断开关开启
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
# 强制开启熔断开关
hystrix.command.default.circuitBreaker.forceOpen=false
hystrix.command.default.circuitBreaker.forceClosed=false
# 设置一个rolling window被划分的时间
hystrix.command.default.metrics.rollingStats.timeInMilliseconds=20000
# 设置一个rolling window被划分的数量
hystrix.command.default.metrics.rollingStats.numBuckets=10
这些设置是默认全局的,如果想对单个方法进行设置则要把 default 换成 HystrixCommand.commandKey() ,如果注解上没有指定commandKey,默认是 类名#方法名(参数类型) 例如:
MyService#retry(Integer)
断路器调用
断路器的三种状态:
- 关闭状态
断路器处理关闭状态,统计调用失败次数,在一段时间内达到一定的阀值后断路器打开。
- 打开状态
对方法调用直接返回失败错误,不发生真正的方法调用,设置了一个重置时间,在重置时间结束后,断路器来到半开状态。
- 半开状态
此时允许进行方法调用,当调用成功了(或者成功达到一定的比例),关闭断路器,否则认为服务没有恢复,重新打开断路器。
编写Feign调用接口 FeignService.java
@FeignClient(name = "feign-client", fallback = Fallback.class)
public interface FeignService {
@GetMapping("/fallback")
String fallback();
@GetMapping("/continueFallback")
String continueFallback();
}
编写降级类 Fallback.java
@Slf4j
@Component
public class Fallback implements FeignService {
@Override
public String fallback() {
log.info("降级成功");
return "降级成功";
}
SecureRandom random = new SecureRandom();
@Override
// 连续降级
@HystrixCommand(fallbackMethod = "fallback2")
public String continueFallback() {
if (random.nextBoolean()) {
return "第一次降级返回的结果:success-1";
}
String s = "第一次降级";
log.error(s);
throw new RuntimeException("first fallback");
}
@HystrixCommand(fallbackMethod = "fallback3")
public String fallback2() {
if (random.nextBoolean()) {
return "第二次降级返回的结果:success-2";
}
log.error("第二次降级");
throw new RuntimeException("fallback again");
}
public String fallback3() {
if (random.nextBoolean()) {
log.error("第三次降级");
return "第三次降级返回的结果:success-3";
}
log.error("第三次降级 失败");
throw new RuntimeException("第三次降级 失败");
}
}
提供对外访问接口 Controller.java
@Slf4j
@RestController
public class Controller {
@Autowired
private FeignService feignService;
@GetMapping("/fallback")
public String randomError() {
return feignService.fallback();
}
@GetMapping("/continueFallback")
public String continueFallback() {
return feignService.continueFallback();
}
}
请求合并
Hystrix 还提供了请求合并的功能。多个请求被合并为一个请求进行一次处理,可以有效减少网络通信和线程池资源。
它提供两种方式进行请求合并:request-scoped 收集一个 HystrixRequestContext 中的请求集合成一个批次;而 globally-scoped 将多个 HystrixRequestContext 中的请求集合成一个批次,这需要应用的下游依赖能够支持在一个命令调用中处理多个 HystrixRequestContext。
1. 通过注解方式进行请求合并
单个请求需要使用 @HystrixCollapser 注解修饰,并指明 batchMethod,可以设置合并请求的一些参数,这里我们设置请求合并的周期为100秒。由于请求合并中不能同步等待结果,所以单个请求返回的结果为Future,即需要异步等待结果。
batchMethod 方法的参数只能是List,不能是Set,因为Set是无序的,在合并请求的时候无法对应。
@HystrixCollapser(batchMethod = "getInstanceByIds",
// 设置请求合并的周期为100秒
collapserProperties = { @HystrixProperty(name = "timerDelayInMilliseconds", value = "10000") })
public Future<String> getInstanceById(String id) {
return null;
}
@HystrixCommand
public List<String> getInstanceByIds(List<String> ids) {
log.info("start batch");
List<String> list = new ArrayList<>();
for (String id : ids) {
list.add(id);
}
return list;
}
调用
@GetMapping("/batch")
public List<String> batch() throws InterruptedException, ExecutionException {
// 请求开始前需要为请求初始化 HystrixRequestContext, 用于在同一请求中的线程间共享数据,封装指请求,请求结束前需要关闭
HystrixRequestContext context = HystrixRequestContext.initializeContext();
Future<String> fc = collapserService.getInstanceById("c");
Future<String> fa = collapserService.getInstanceById("a");
Future<String> fd = collapserService.getInstanceById("d");
TimeUnit.MILLISECONDS.sleep(1000);
Future<String> fb = collapserService.getInstanceById("b");
List<String> list = Arrays.asList(fa.get(), fb.get(), fc.get(), fd.get());
context.close();
return list;
}
如果在请求合并周期内直接调用 Future#get 方法阻塞等待同步结果,将会强行合并请求。
2. 继承 HystrixCollapser
请求合并命令同样也可以通过自定义的方式实现,只需要继承 HystrixCollapser 抽象类。
public class MyHystrixCollapserCommand extends HystrixCollapser<List<String>, String, Integer> {
Integer requestId;
public MyHystrixCollapserCommand(Integer requestId) {
// 需要指定 CollapserKey, 用来标记被合并请求的键值
super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("myHystrixCollapserCommand")));
this.requestId = requestId;
}
@Override
public Integer getRequestArgument() {
return this.requestId;
}
@Override
protected com.netflix.hystrix.HystrixCommand<List<String>> createCommand(Collection<CollapsedRequest<String, Integer>> collapsedRequests) {
// 合并请求参数
List<Integer> ids = collapsedRequests.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList());
HystrixCommandGroupKey batchGroup = HystrixCommandGroupKey.Factory.asKey("requestIdBatchGroup");
return new HystrixCommand<List<String>>(batchGroup) {
@Override
protected List<String> run() throws Exception {
// 发起远程批量请求,这里不是重点,忽略
List<String> list = ids.stream().map(String::valueOf).collect(Collectors.toList());
return list;
}
};
}
@Override
protected void mapResponseToRequests(List<String> batchResponse,
Collection<CollapsedRequest<String, Integer>> collapsedRequests) {
int count = 0;
for (CollapsedRequest<String, Integer> request : collapsedRequests) {
// 分拆对应请求的响应结果
request.setResponse(batchResponse.get(count++));
}
}
}
在Controller中使用
@GetMapping("/myBatch")
public List<String> myBatch() throws InterruptedException, ExecutionException {
// 请求开始前需要为请求初始化 HystrixRequestContext, 用于在同一请求中的线程间共享数据,封装指请求,请求结束前需要关闭
HystrixRequestContext context = HystrixRequestContext.initializeContext();
MyHystrixCollapserCommand c1 = new MyHystrixCollapserCommand(1);
MyHystrixCollapserCommand c2 = new MyHystrixCollapserCommand(2);
MyHystrixCollapserCommand c3 = new MyHystrixCollapserCommand(3);
MyHystrixCollapserCommand c4 = new MyHystrixCollapserCommand(4);
Future<String> f1 = c1.queue();
Future<String> f2 = c2.queue();
Future<String> f3 = c3.queue();
TimeUnit.MILLISECONDS.sleep(1000);
Future<String> f4 = c4.queue();
List<String> list = Arrays.asList(f1.get(), f2.get(), f3.get(), f4.get());
context.close();
return list;
}
效果和 @HystrixCollapser 一样。