上一篇介绍了Hystrix基本功能和单独使用的方式,今天继续学习如何将Hystrix融入SpringCloud组件中去。

在Ribbon上使用熔断器

pom.xml 文件中引入 hystrix 的 依赖spring-cloud-starter-hystrix

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

在应用的启动类上使用 @EnableHystrix 开启 hystrix 的功能。

package com.rickiyang.learn;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate; @EnableHystrix
@EnableDiscoveryClient
@SpringBootApplication
public class RibbonDemoApplication { public static void main(String[] args) {
SpringApplication.run(RibbonDemoApplication.class, args);
} @Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
} @Bean
public IRule ribbonRule() {
return new RandomRule();//这里配置策略,和配置文件对应
}
}

使用注解 @HystrixCommand 标记调用失败时需要熔断的方法,fallbackMethod 属性指定 降级方法方法名fallback

package com.rickiyang.learn.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.rickiyang.learn.entity.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; /**
* @author rickiyang
* @date 2019-10-08
* @Desc TODO
*/
@Service
public class DemoService { @Autowired
RestTemplate restTemplate; /**
* 支持服务降级
* 自定义降级处理的超时时间,并发数
* 自定义执行该任务的线程池参数
* 自定义执行熔断逻辑的异常
* @param name
* @return
*/
@HystrixCommand(
fallbackMethod = "hiError",
commandProperties={
// 降级处理超时时间设置
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
// 任意时间点允许的最高并发数。超过该设置值后,拒绝执行请求。
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "1000"),
},
// 配置执行的线程池
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "20"),
@HystrixProperty(name = "maxQueueSize", value = "-1"),
},
// 该异常不执行熔断,去执行该异常抛出的自己逻辑
ignoreExceptions = {NoSuchMethodException.class}
)
public String hello(String name) {
return restTemplate.getForEntity("http://eureka-client/hello/" + name, String.class).getBody();
} public String fail(String name) {
return "accu"+name+",error!";
} }

在Feign上使用熔断器

Feign 是自带 断路器 的,不过需要在 配置文件 中开启 hystrix 的配置。

feign:
hystrix:
enabled: true

Hystrix 支持 降级回退 操作,当 发生熔断出现错误 时,调用会执行 默认代码路径

package com.rickiyang.learn.service;

import com.rickiyang.learn.entity.Person;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*; /**
* @author: rickiyang
* @date: 2019/10/5
* @description:
*/
@FeignClient(name= "eureka-client",fallback = HelloFailBackService.class)
public interface HelloRemote { @RequestMapping(value = "/hello/{name}")
String hello(@PathVariable(value = "name") String name); @PostMapping(value ="/add",produces = "application/json; charset=UTF-8")
String addPerson(@RequestBody Person person); @GetMapping("/getPerson/{id}")
String getPerson(@PathVariable("id") Integer id); }

通过设置 fallback 属性为实现 降级回退,来启用 @FeignClient失败降级

package com.rickiyang.learn.service;

import com.rickiyang.learn.entity.Person;

public class HelloFailBackService implements HelloRemote {

    @Override
public String hello(String name) {
return "";
} @Override
public String addPerson(Person person) {
return null;
} @Override
public String getPerson(Integer id) {
return null;
}
}

如果需要获取导致 回退触发 的原因,可以指定 @FeignClient 注解内部的 fallbackFactory 属性,fallbackFactory 属性和 fallback 属性不能一起使用。

package com.rickiyang.learn.service;

import com.rickiyang.learn.entity.Person;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*; /**
* @author: rickiyang
* @date: 2019/10/5
* @description:
*/
@FeignClient(name= "eureka-client",fallback = HelloFailBackFacgtory.class)
public inte rface HelloRemote { @RequestMapping(value = "/hello/{name}")
String hello(@PathVariable(value = "name") String name); @PostMapping(value ="/add",produces = "application/json; charset=UTF-8")
String addPerson(@RequestBody Person person); @GetMapping("/getPerson/{id}")
String getPerson(@PathVariable("id") Integer id); }

然后提供一个 FallbackFactory实现类,实现类指定 泛型参数HelloService

package com.rickiyang.learn.service;

import com.rickiyang.learn.entity.Person;
import feign.hystrix.FallbackFactory; public class HelloFailBackFacgtory implements FallbackFactory<HelloRemote> { @Override
public HelloRemote create(Throwable throwable) {
return new HelloRemote() {
@Override
public String hello(String name) {
return "fail reason is : " + throwable.getMessage();
} @Override
public String addPerson(Person person) {
return "fail reason is : " + throwable.getMessage(); } @Override
public String getPerson(Integer id) {
return "fail reason is : " + throwable.getMessage(); }
};
}
}

Hystrix Dashboard监控熔断器的状态

Hystrix Dashboard 是一个 监控熔断器 状况的组件,提供了 数据监控图形界面

在Ribbon中使用Hystrix Dashboard

在加入 spring-cloud-starter-hystrix 依赖的基础上,加入下面的依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>

在应用程序 启动类 已经加上 @EnableHystrix 的基础上,加入 @EnableHystrixDashboard 注解,代码如下:

package com.rickiyang.learn;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate; @EnableHystrix
@EnableHystrixDashboard
@EnableDiscoveryClient
@SpringBootApplication
public class RibbonDemoApplication { public static void main(String[] args) {
SpringApplication.run(RibbonDemoApplication.class, args);
} @Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
} @Bean
public IRule ribbonRule() {
return new RandomRule();//这里配置策略,和配置文件对应
}
}

Hystrix源码分析

前文提到了Hystrix核心功能包括:隔离机制,熔断机制,降级机制。围绕着这3点我们一起看一下它们分别是如何实现的。

首先从 @EnableHystrix入手,看一下开启Hystrix功能的时候都做了什么。


package org.springframework.cloud.netflix.hystrix; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix { }

该注解的功能是为了引用@EnableCircuitBreaker注解。

package org.springframework.cloud.client.circuitbreaker;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /**
* Annotation to enable a CircuitBreaker implementation.
* http://martinfowler.com/bliki/CircuitBreaker.html
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableCircuitBreakerImportSelector.class)
public @interface EnableCircuitBreaker { }

这里使用了import注解引入EnableCircuitBreakerImportSelector:

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableCircuitBreakerImportSelector extends
SpringFactoryImportSelector<EnableCircuitBreaker> { @Override
protected boolean isEnabled() {
return getEnvironment().getProperty(
"spring.cloud.circuit.breaker.enabled", Boolean.class, Boolean.TRUE);
} }

作用是将spring.cloud.circuit.breaker.enabled设置为启用。那么在进行配置扫描的时候就会按照启用的配置去进行相应的操作。

再回到EnableCircuitBreaker,查看被引用的位置,能看到在spring-cloud-netflix-core包的配置文件中有一个引用:

org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\
org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration

将 EnableCircuitBreaker 指向了 HystrixCircuitBreakerConfiguration 类:

@Configuration
public class HystrixCircuitBreakerConfiguration { @Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
} @Bean
public HystrixShutdownHook hystrixShutdownHook() {
return new HystrixShutdownHook();
} @Bean
public HasFeatures hystrixFeature() {
return HasFeatures.namedFeatures(new NamedFeature("Hystrix", HystrixCommandAspect.class));
} /**
* {@link DisposableBean} that makes sure that Hystrix internal state is cleared when
* {@link ApplicationContext} shuts down.
*/
private class HystrixShutdownHook implements DisposableBean { @Override
public void destroy() throws Exception {
// Just call Hystrix to reset thread pool etc.
Hystrix.reset();
} } }

这里有一个关键的实例: HystrixCommandAspect ,从名称就能看出是扫描 HystrixCommand 注解的切面。

在 HystrixCommandAspect 定义了两个切面:

public class HystrixCommandAspect {

    private static final Map<HystrixPointcutType, MetaHolderFactory> META_HOLDER_FACTORY_MAP;

    static {
META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder()
.put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())
.put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())
.build();
} @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") public void hystrixCommandAnnotationPointcut() {
} @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
public void hystrixCollapserAnnotationPointcut() {
} @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
//拿到切面声明的方法
Method method = getMethodFromTarget(joinPoint);
Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
//如果方法的注解不是这两个不进入
if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
"annotations at the same time");
}
//从工厂对象中取出当前注解所对应的执行逻辑工厂类
MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
//执行工厂类逻辑
HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType(); Object result;
try {
//这里区分是要同步执行还是异步执行
if (!metaHolder.isObservable()) {
result = CommandExecutor.execute(invokable, executionType, metaHolder);
} else {
result = executeObservable(invokable, executionType, metaHolder);
}
} catch (HystrixBadRequestException e) {
throw e.getCause() != null ? e.getCause() : e;
} catch (HystrixRuntimeException e) {
throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
}
return result;
} ......
......
...... }

这里会扫描两个注解:

  • HystrixCommand
  • HystrixCollapser

methodsAnnotatedWithHystrixCommand()方法使用环绕通知拦截所有@HystrixCommand 和 @HystrixCollapser注解的方法。

这里用到的设计模式还是挺多:

  1. 首先构造了一个分别执行扫描到上面两个注解的方法工厂:CommandMetaHolderFactory,通过工厂类来决定执行哪个注解对应的逻辑,然后把对应的逻辑封装成MetaHolder
  2. 接着会使用HystrixCommandFactory工厂将每一个被扫描的方法统一封装成HystrixInvokable对象;
  3. 然后去执行对每一个被拦截的方法生成相应拦截配置的逻辑。

关于hystrix初始化的过程我们暂时就说这么多,了解到在哪里执行了注解扫描去做了初始化,下面我们分别详述各个功能在Hystrix中是如何实现。

隔离机制

Hystrix可以指定为每一个请求创建独立的线程池来执行,首先看一下@HystrixCommand的参数说明:

public @interface HystrixCommand {
// HystrixCommand 命令所属的组的名称:默认注解方法类的名称
String groupKey() default ""; // HystrixCommand 命令的key值,默认值为注解方法的名称
String commandKey() default ""; // 线程池名称,默认定义为groupKey
String threadPoolKey() default "";
// 定义回退方法的名称, 此方法必须和hystrix的执行方法在相同类中
String fallbackMethod() default "";
// 配置hystrix命令的参数
HystrixProperty[] commandProperties() default {};
// 配置hystrix依赖的线程池的参数
HystrixProperty[] threadPoolProperties() default {}; // 如果hystrix方法抛出的异常包括RUNTIME_EXCEPTION,则会被封装HystrixRuntimeException异常。我们也可以通过此方法定义哪些需要忽略的异常
Class<? extends Throwable>[] ignoreExceptions() default {}; // 定义执行hystrix observable的命令的模式,类型详细见ObservableExecutionMode
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER; // 如果hystrix方法抛出的异常包括RUNTIME_EXCEPTION,则会被封装HystrixRuntimeException异常。此方法定义需要抛出的异常
HystrixException[] raiseHystrixExceptions() default {}; // 定义回调方法:但是defaultFallback不能传入参数,返回参数和hystrix的命令兼容
String defaultFallback() default "";
}

threadPoolKey()可以指定线程池名称。

还记得上一篇中我们讲到 HystrixCommand / HystrixObservableCommand类,被以来的服务想要被Hystrix封装,只用继承这两个类中的一个即可。只是现在使用了 @HystrixCommand注解将这一部分逻辑封装了你无法看到。

首先我们从HystrixCommand类入手,可以看到它继承了 AbstractCommand,一般抽象类都会先默默地做一些事情为子类分担忧愁。我们看一下 AbstractCommand 中的逻辑:

protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool,
HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults,
HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore,
HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { this.commandGroup = initGroupKey(group);
this.commandKey = initCommandKey(key, getClass());
this.properties = initCommandProperties(this.commandKey, propertiesStrategy, commandPropertiesDefaults);
//线程池的key
this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get());
this.metrics = initMetrics(metrics, this.commandGroup, this.threadPoolKey, this.commandKey, this.properties);
this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics);
//初始化线程池
this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults); //Strategies from plugins
this.eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(this.commandKey, this.commandGroup, this.metrics, this.circuitBreaker, this.properties);
this.executionHook = initExecutionHook(executionHook); this.requestCache = HystrixRequestCache.getInstance(this.commandKey, this.concurrencyStrategy);
this.currentRequestLog = initRequestLog(this.properties.requestLogEnabled().get(), this.concurrencyStrategy); /* fallback semaphore override if applicable */
this.fallbackSemaphoreOverride = fallbackSemaphore; /* execution semaphore override if applicable */
this.executionSemaphoreOverride = executionSemaphore;
}

直接进入到线程池初始化的代码:


/*
* 初始化线程池的key
* 如果key为空,默认使用HystrixCommandGroup的名称作为key
*
*/
private static HystrixThreadPoolKey initThreadPoolKey(HystrixThreadPoolKey threadPoolKey, HystrixCommandGroupKey groupKey, String threadPoolKeyOverride) {
if (threadPoolKeyOverride == null) {
// we don't have a property overriding the value so use either HystrixThreadPoolKey or HystrixCommandGroup
if (threadPoolKey == null) {
/* use HystrixCommandGroup if HystrixThreadPoolKey is null */
return HystrixThreadPoolKey.Factory.asKey(groupKey.name());
} else {
return threadPoolKey;
}
} else {
// we have a property defining the thread-pool so use it instead
return HystrixThreadPoolKey.Factory.asKey(threadPoolKeyOverride);
}
} /*
* 初始化线程池
* HystrixThreadPool 中构造了一个ConcurrentHashMap来保存所有的线程池
*
*/
private static HystrixThreadPool initThreadPool(HystrixThreadPool fromConstructor, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) {
if (fromConstructor == null) {
// get the default implementation of HystrixThreadPool
return HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults);
} else {
return fromConstructor;
}
} /**
*
*从map中获取线程池,如果不存在则构造一个线程池对象存入
*/
static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) {
// get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work
String key = threadPoolKey.name(); // this should find it for all but the first time
HystrixThreadPool previouslyCached = threadPools.get(key);
if (previouslyCached != null) {
return previouslyCached;
} // 加锁 保证单机并发的安全性
synchronized (HystrixThreadPool.class) {
if (!threadPools.containsKey(key)) {
//通过HystrixThreadPoolDefault类来构造线程池
threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
}
}
return threadPools.get(key);
}

构造线程池的代码主要在 HystrixThreadPoolDefault 类中:

static class HystrixThreadPoolDefault implements HystrixThreadPool {

  public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) {
this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults);
HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
this.queueSize = properties.maxQueueSize().get(); this.metrics = HystrixThreadPoolMetrics.getInstance(threadPoolKey,
concurrencyStrategy.getThreadPool(threadPoolKey, properties),
properties);
this.threadPool = this.metrics.getThreadPool();
this.queue = this.threadPool.getQueue(); /* strategy: HystrixMetricsPublisherThreadPool */
HystrixMetricsPublisherFactory.createOrRetrievePublisherForThreadPool(threadPoolKey, this.metrics, this.properties);
}
......
......
...... }

注意这里有一个策略模式:HystrixConcurrencyStrategy,声明了不同的加载线程池的策略。

具体加载线程池是在:concurrencyStrategy.getThreadPool(threadPoolKey, properties)方法中:

public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
final ThreadFactory threadFactory = getThreadFactory(threadPoolKey); final boolean allowMaximumSizeToDivergeFromCoreSize = threadPoolProperties.getAllowMaximumSizeToDivergeFromCoreSize().get();
final int dynamicCoreSize = threadPoolProperties.coreSize().get();
final int keepAliveTime = threadPoolProperties.keepAliveTimeMinutes().get();
final int maxQueueSize = threadPoolProperties.maxQueueSize().get();
final BlockingQueue<Runnable> workQueue = getBlockingQueue(maxQueueSize); if (allowMaximumSizeToDivergeFromCoreSize) {
final int dynamicMaximumSize = threadPoolProperties.maximumSize().get();
if (dynamicCoreSize > dynamicMaximumSize) {
logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " +
dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " +
dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value");
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
}

这里构造线程池的方式就是我们熟悉的:new ThreadPoolExecutor()

至此隔离机制中的线程池隔离我们就弄清楚了,线程池是以HystrixCommandGroupKey进行划分的,不同的CommandGroup有不同的线程池来处理。

熔断机制

上一节我们写过开启熔断器的代码:

/**
* 一段简单的使用HystrixCommand封装服务隔离调用的实例
*/
public class QueryOrderIdCommand extends HystrixCommand<Integer> {
private final static Logger logger = LoggerFactory.getLogger(QueryOrderIdCommand.class);
private String orderId = ""; /**
* 构造函数中封装了一些参数设置
* @param orderId
*/
public QueryOrderIdCommand(String orderId) {
super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orderService"))
.andCommandKey(HystrixCommandKey.Factory.asKey("queryByOrderId"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(10)//至少有10个请求,熔断器才进行错误率的计算
.withCircuitBreakerSleepWindowInMilliseconds(5000)//熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
.withCircuitBreakerErrorThresholdPercentage(50)//错误率达到50开启熔断保护
)
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("orderServicePool"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties
.Setter().withCoreSize(10)));
this.orderId = orderId;
}
......
......
......
}

上面使用 @HystrixCommand 注解进行熔断的用法:

@HystrixCommand(
fallbackMethod = "hiError",
commandProperties={
// 降级处理超时时间设置
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
// 任意时间点允许的最高并发数。超过该设置值后,拒绝执行请求。
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "1000"),
},
// 配置执行的线程池
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "20"),
@HystrixProperty(name = "maxQueueSize", value = "-1"),
},
// 该异常不执行熔断,去执行该异常抛出的自己逻辑
ignoreExceptions = {NoSuchMethodException.class}
)
public String hello(String name) {
return restTemplate.getForEntity("http://eureka-client/hello/" + name, String.class).getBody();
}

仍旧是在AbstractCommand的构造函数中,有熔断器初始化的逻辑:

this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics);

/*
*调用 HystrixCircuitBreaker 工厂类方法执行初始化
*
*/
private static HystrixCircuitBreaker initCircuitBreaker(boolean enabled, HystrixCircuitBreaker fromConstructor,
HystrixCommandGroupKey groupKey, HystrixCommandKey commandKey,
HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
if (enabled) {
if (fromConstructor == null) {
// get the default implementation of HystrixCircuitBreaker
return HystrixCircuitBreaker.Factory.getInstance(commandKey, groupKey, properties, metrics);
} else {
return fromConstructor;
}
} else {
return new NoOpCircuitBreaker();
}
}

同样,在熔断器的保存逻辑中,也是将所有的熔断器存储在本地Map:

public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
// this should find it for all but the first time
HystrixCircuitBreaker previouslyCached = circuitBreakersByCommand.get(key.name());
if (previouslyCached != null) {
return previouslyCached;
} HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics));
if (cbForCommand == null) {
return circuitBreakersByCommand.get(key.name());
return cbForCommand;
}
}

putIfAbsent()方法中,保存的是一个 HystrixCircuitBreakerImpl 对象,这里定义的是断路器所有实现的逻辑状态。

断路器的状态分为3种:

enum Status {
CLOSED, OPEN, HALF_OPEN;
}
  • CLOSED关闭状态:允许流量通过。
  • OPEN打开状态:不允许流量通过,即处于降级状态,走降级逻辑。
  • HALF_OPEN半开状态:允许某些流量通过,并关注这些流量的结果,如果出现超时、异常等情况,将进入OPEN状态,如果成功,那么将进入CLOSED状态。

在构造函数初始化的时候做了监听 metrics 的HealthCountsStream信息的异步操作:

protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, final HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
this.properties = properties;
this.metrics = metrics; //On a timer, this will set the circuit between OPEN/CLOSED as command executions occur
Subscription s = subscribeToStream();
activeSubscription.set(s);
} private Subscription subscribeToStream() {
//这里会在每次执行onNext()事件的时候来评估是否需要打开或者关闭断路器
return metrics.getHealthCountsStream()
.observe()
.subscribe(new Subscriber<HealthCounts>() {
@Override
public void onCompleted() { } @Override
public void onError(Throwable e) { } @Override
public void onNext(HealthCounts hc) {
//首先校验的时在时间窗范围内的请求次数,如果低于阈值(默认是20),不做处理,如果高于阈值,则去判断接口请求的错误率
if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) { // 如果没有超过统计阈值的最低窗口值,就没有必要去改变断路器的状态
// 当前如果断路器是关闭的,那么就保持关闭状态无需更改;
// 如果断路器状态为半开状态,需要等待直到有成功的命令执行;
// 如果断路器是打开状态,需要等待休眠窗口过期。
} else {
//判断接口请求的错误率(阈值默认是50),如果高于这个值,则断路器打开
if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) { // 如果当前请求的错误率小于断路器设置的容错率百分比,也不会拦截请求
} else {
// 如果当前错误率太高则打开断路器
if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {
circuitOpened.set(System.currentTimeMillis());
}
}
}
}
});
}

每当metric 收到HealthCounts信息时就会调用next方法判断当前是否需要打开断路器。

HystrixCommandMetrics 是断路器最核心的东西,通过时间窗的形式,记录一段时间范围内(默认是10秒)的接口请求的健康状况(Command的执行状态,包括成功、失败、超时、线程池拒绝等)并得到对应的度量指标。

HealthCounts中保存了当前时间窗口内接口的请求状态包括请求总数、失败数和失败率,如果当前的请求总数和 失败率都达到阈值,则对接口进行熔断将断路器状态设置为打开状态记录当前打开断路器的时间。

失败回退

首先从HystrixCommand的queue()方法作为入口:

public Future<R> queue() {
final Future<R> delegate = toObservable().toBlocking().toFuture(); ......
......
...... }

queue()异步非堵塞的,它调用了toObservable().toBlocking().toFuture()方法,queue()执行完后,会创建一个新线程运行run()。

继续走到 toObservable() 方法:

public Observable<R> toObservable() {

  ......
......
...... final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {
return Observable.never();
}
return applyHystrixSemantics(_cmd);
}
}; ......
......
......
}

applyHystrixSemantics()封装Hystrix的核心流程,根据 HystrixCircuitBreaker 提供熔断器状态,确定执行run() 还是getFallback():

private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
//如果回调逻辑抛出异常,那么就不会执行回调逻辑而是执行快速失败机制
executionHook.onStart(_cmd); /* 断每个Hystrix命令的请求都通过它是否被执行 */
if (circuitBreaker.attemptExecution()) {
final TryableSemaphore executionSemaphore = getExecutionSemaphore();
final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
final Action0 singleSemaphoreRelease = new Action0() {
@Override
public void call() {
if (semaphoreHasBeenReleased.compareAndSet(false, true)) {
executionSemaphore.release();
}
}
}; final Action1<Throwable> markExceptionThrown = new Action1<Throwable>() {
@Override
public void call(Throwable t) {
eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey);
}
}; if (executionSemaphore.tryAcquire()) {
try {
/* 设置开始时间,用于监控执行超时*/
executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
//包装HystrixCommand Observable,注册观察者
return executeCommandAndObserve(_cmd)
.doOnError(markExceptionThrown)
.doOnTerminate(singleSemaphoreRelease)
.doOnUnsubscribe(singleSemaphoreRelease);
} catch (RuntimeException e) {
return Observable.error(e);
}
} else {
//信号量资源不足,拒绝执行,执行getFallBack()方法
return handleSemaphoreRejectionViaFallback();
}
} else {
//不可执行,快速失效,执行getFallBack()方法
return handleShortCircuitViaFallback();
}
}

在包装executeCommandAndObserve的逻辑中有关于发生异常的时候对各种类型异常的判断,然后在doOnError()回调的时候对异常进行相应的处理。

private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread(); ...... final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
@Override
public Observable<R> call(Throwable t) {
circuitBreaker.markNonSuccess();
//如果是异常情况,判断当前异常的类型
Exception e = getExceptionFromThrowable(t);
executionResult = executionResult.setExecutionException(e);
//线程提交拒绝异常
if (e instanceof RejectedExecutionException) {
return handleThreadPoolRejectionViaFallback(e);
//执行命令超时异常
} else if (t instanceof HystrixTimeoutException) {
return handleTimeoutViaFallback();
//请求异常
} else if (t instanceof HystrixBadRequestException) {
return handleBadRequestByEmittingError(e);
} else {
//hystrix自定义异常
if (e instanceof HystrixBadRequestException) {
eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
return Observable.error(e);
} return handleFailureViaFallback(e);
}
}
}; ......
...... Observable<R> execution;
if (properties.executionTimeoutEnabled().get()) {
execution = executeCommandWithSpecifiedIsolation(_cmd)
.lift(new HystrixObservableTimeoutOperator<R>(_cmd));
} else {
execution = executeCommandWithSpecifiedIsolation(_cmd);
} return execution.doOnNext(markEmits)
.doOnCompleted(markOnCompleted)
.onErrorResumeNext(handleFallback)
.doOnEach(setRequestContext);
}

注意到最后execution执行的是:executeCommandWithSpecifiedIsolation(_cmd),根据隔离级别选择是“线程方式”隔离执行还是“信号量方式”隔离执行:

private Observable<R> executeCommandWithSpecifiedIsolation(final AbstractCommand<R> _cmd) {
//如果当前配置的是线程池隔离的策略
if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) { return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
executionResult = executionResult.setExecutionOccurred();
if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) {
return Observable.error(new IllegalStateException("execution attempted while in state : " + commandState.get().name()));
} metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.THREAD); if (isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT) {
// the command timed out in the wrapping thread so we will return immediately
// and not increment any of the counters below or other such logic
return Observable.error(new RuntimeException("timed out before executing run()"));
}
if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.STARTED)) {
//we have not been unsubscribed, so should proceed
HystrixCounters.incrementGlobalConcurrentThreads();
threadPool.markThreadExecution();
// store the command that is being run
endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());
executionResult = executionResult.setExecutedInThread();
//这里注册线程执行的hook,开启异步执行
try {
executionHook.onThreadStart(_cmd);
executionHook.onRunStart(_cmd);
executionHook.onExecutionStart(_cmd);
return getUserExecutionObservable(_cmd);
} catch (Throwable ex) {
return Observable.error(ex);
}
} else {
//command has already been unsubscribed, so return immediately
return Observable.error(new RuntimeException("unsubscribed before executing run()"));
}
}
}).doOnTerminate(new Action0() {
@Override
public void call() {
if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.TERMINAL)) {
handleThreadEnd(_cmd);
}
if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.TERMINAL)) {
//if it was never started and received terminal, then no need to clean up (I don't think this is possible)
}
//if it was unsubscribed, then other cleanup handled it
}
}).doOnUnsubscribe(new Action0() {
@Override
public void call() {
if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.UNSUBSCRIBED)) {
handleThreadEnd(_cmd);
}
if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.UNSUBSCRIBED)) {
//if it was never started and was cancelled, then no need to clean up
}
//if it was terminal, then other cleanup handled it
} }).subscribeOn(threadPool.getScheduler(new Func0<Boolean>() {
@Override
public Boolean call() {
return properties.executionIsolationThreadInterruptOnTimeout().get() && _cmd.isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT;
}
}));
//这里使用信号量机制
} else {
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
executionResult = executionResult.setExecutionOccurred();
if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) {
return Observable.error(new IllegalStateException("execution attempted while in state : " + commandState.get().name()));
} metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.SEMAPHORE);
// semaphore isolated
// store the command that is being run
endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());
try {
executionHook.onRunStart(_cmd);
executionHook.onExecutionStart(_cmd);
return getUserExecutionObservable(_cmd); //the getUserExecutionObservable method already wraps sync exceptions, so this shouldn't throw
} catch (Throwable ex) {
//If the above hooks throw, then use that as the result of the run method
return Observable.error(ex);
}
}
});
}
}

Spring Cloud 学习--Hystrix应用的更多相关文章

  1. spring cloud 学习(4) - hystrix 服务熔断处理

    hystrix 是一个专用于服务熔断处理的开源项目,当依赖的服务方出现故障不可用时,hystrix有一个所谓的断路器,一但打开,就会直接拦截掉对故障服务的调用,从而防止故障进一步扩大(类似中电路中的跳 ...

  2. spring cloud学习(五)断路器 Hystrix

    断路器 Hystrix 断路器模式 (云计算设计模式) 断路器模式源于Martin Fowler的Circuit Breaker一文. 在分布式环境中,其中的应用程序执行访问远程资源和服务的操作,有可 ...

  3. 断路器Hystrix与Turbine集群监控-Spring Cloud学习第三天(非原创)

    文章大纲 一.Hystrix基础介绍二.断路器Hystrix简单使用三.自定义Hystrix请求命令四.Hystrix的服务降级与异常处理五.Hystrix的请求缓存与请求合并六.Hystrix仪表盘 ...

  4. spring cloud 学习(9) - turbine stream无法在eureka注册的解决办法

    turbine是啥就不多解释了,初次接触的可以移步spring cloud 学习(4) - hystrix 服务熔断处理 拉到最后看一下,turbine stream默认情况下启动成功后,eureka ...

  5. Spring Cloud学习(一):Eureka服务注册与发现

    1.Eureka是什么 Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的. Eureka ...

  6. Spring Cloud中Hystrix、Ribbon及Feign的熔断关系是什么?

    导读 今天和大家聊一聊在Spring Cloud微服务框架实践中,比较核心但是又很容易把人搞得稀里糊涂的一个问题,那就是在Spring Cloud中Hystrix.Ribbon以及Feign它们三者之 ...

  7. Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失问题分析

    最近spring boot项目中由于使用了spring cloud 的hystrix 导致了threadLocal中数据丢失,其实具体也没有使用hystrix,但是显示的把他打开了,导致了此问题. 导 ...

  8. spring cloud 学习资料

    spring cloud 学习资料 网址 拜托!面试请不要再问我Spring Cloud底层原理 https://mp.weixin.qq.com/s/ZH-3JK90mhnJPfdsYH2yDA

  9. Spring Boot和Spring Cloud学习资源推荐

    Spring Boot和Spring Cloud学习资源推荐   比较好的学习资源,分享一下. 1.Spring Boot官方文档:http://projects.spring.io/spring-b ...

随机推荐

  1. 基于RSA的WEB前端密码加密方案

    受制于WEB页面源码的暴露,因此传统的对称加密方案以及加密密钥都将暴露在JS文件中,同样可以被解密. 目前比较好的解决方案是WEB页面全程或用户登录等关键环节使用HTTPS进行传输. 另外一种解决方案 ...

  2. Spring中基于注解的IOC(一):基础介绍

    1. Spring中的常用注解 注解配置和xml配置要实现的功能都是一样的,都要降低程序的耦合,只是配置的形式不一样 xml中配置示例: 注解分类: 1.用于创建对象的注解 它们的作用就和在xml中编 ...

  3. Python 数学运算的函数

    不需要导入模块(内置函数) 函数 返回值 ( 描述 ) abs(x) 返回绝对值 max(x1, x2,...) 最大值,参数可以为序列. min(x1, x2,...) 最小值,参数可以为序列. p ...

  4. Busness Client 客户端配置

    1,打开Busness Client,点击新建按钮: 2,选择New System Connection(SAP Logon),这个是配置GUI登陆的,就相当于用BC登陆GUI. 这里就和GUI的配置 ...

  5. 【python】多任务(2. 进程)

    进程间通信 Queue import multiprocessing def download_from_web(q): # 模拟从网上下载数据 data = [11, 22, 33, 44] for ...

  6. Appium-desktop元素定位

    一.打开 appium-desktop ,点击 start session 二.打开后,点击屏幕右上角的搜索按钮 三.然后会打开配置页面,在本地服务配置信息同上面写的代码链接配置.填入正确的信息后,点 ...

  7. etcd和flannel实现docker跨物理机通信

    实验目标 跨物理机的容器之间能直接访问docker通过Flannel可以实现各容器间的相互通信,即宿主机和容器,容器和容器之间都能相互通信 实验环境 192.168.3.50 //etcd.flann ...

  8. 【转载】自定义View学习笔记之详解onMeasure

    网上对自定义View总结的文章都很多,但是自己还是写一篇,好记性不如多敲字! 其实自定义View就是三大流程,onMeasure.onLayout.onDraw.看名字就知道,onMeasure是用来 ...

  9. CentOS6.7搭建部署DNS服务 (详解主配置文件)

    -DNS服务器的基本原理 域名简介:使用tcp/udp协议,默认使用53端口号 默认情况下,客户端发起的查询请求都是用UDP/53查询的. 默认情况下,从从服务器到主服务器传输数据用的是TCP/53. ...

  10. POJ 3322 Bloxorz

    #include<cstring> #include<algorithm> #include<iostream> #include<cstdio> #i ...