Spring Cloud Hystrix
Hystrix是什么
Hystrix 能使你的系统在出现依赖服务失效的时候,通过隔离系统所依赖的服务,防止服务级联失败,同时提供失败回退机制,更优雅地应对失效,并使你的系统能更快地从异常中恢复。
Hystrix能做什么
- 在通过第三方客户端访问(通常是通过网络)依赖服务出现高延迟或者失败时,为系统提供保护和控制
- 在分布式系统中防止级联失败
- 快速失败(Fail fast)同时能快速恢复
- 提供失败回退(Fallback)和优雅的服务降级机制
- 提供近实时的监控、报警和运维控制手段
Hystrix设计原则
- 防止单个依赖耗尽容器(例如 Tomcat)内所有用户线程
- 降低系统负载,对无法及时处理的请求快速失败(fail fast)而不是排队
- 提供失败回退,以在必要时让失效对用户透明化
- 使用隔离机制(例如『舱壁』/『泳道』模式,熔断器模式等)降低依赖服务对整个系统的影响
- 针对系统服务的度量、监控和报警,提供优化以满足近实时性的要求
- 在 Hystrix 绝大部分需要动态调整配置并快速部署到所有应用方面,提供优化以满足快速恢复的要求
- 能保护应用不受依赖服务的整个执行过程中失败的影响,而不仅仅是网络请求
Hystrix 实现原理
1.舱壁隔离模式
顾名思义,该模式像舱壁一样对资源或失败单元进行隔离,如果一个船舱破了进水,只损失一个船舱,其它船舱可以不受影响 。线程隔离(Thread Isolation)就是舱壁隔离模式的一个例子,假定一个应用程序A调用了Svc1/Svc2/Svc3三个服务,且部署A的容器一共有120个工作线程,采用线程隔离机制,可以给对Svc1/Svc2/Svc3的调用各分配40个线程,当Svc2慢了,给Svc2分配的40个线程因慢而阻塞并最终耗尽,线程隔离可以保证给Svc1/Svc3分配的80个线程可以不受影响,如果没有这种隔离机制,当Svc2慢的时候,120个工作线程会很快全部被对Svc2的调用吃光,整个应用程序会全部慢下来。
隔离策略
- 应用在复杂的分布式结构中,可能会依赖许多其他的服务,并且这些服务都不可避免地有失效的可能。如果一个应用没有与依赖服务的失效隔离开来,那么它将有可能因为依赖服务的失效而失效。
- Hystrix将货仓模式运用到了服务调用者上。为每一个依赖服务维护一个线程池(或者信号量),当线程池占满,该依赖服务将会立即拒绝服务而不是排队等待。
- 每个依赖服务都被隔离开来,Hystrix 会严格控制其对资源的占用,并在任何失效发生时,执行失败回退逻辑。
2.观察者模式
- Hystrix通过观察者模式对服务进行状态监听。
- 每个任务都包含有一个对应的Metrics,所有Metrics都由一个ConcurrentHashMap来进行维护,Key是CommandKey.name()
private static final ConcurrentHashMap<String, HystrixCommandMetrics> metrics = new ConcurrentHashMap<String, HystrixCommandMetrics>();
- 在任务的不同阶段会往Metrics中写入不同的信息,Metrics会对统计到的历史信息进行统计汇总,供熔断器以及Dashboard监控时使用。
3.AOP配置命令切入
注解切入的AOP
@Aspect
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;
}
Hystrix 使用
/**
* Copyright 2012 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.hystrix.contrib.javanica.annotation;
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;
/**
* This annotation used to specify some methods which should be processes as hystrix commands.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
/**
* The command group key is used for grouping together commands such as for reporting,
* alerting, dashboards or team/library ownership.
* <p/>
* default => the runtime class name of annotated method
*
* @return group key
*/
String groupKey() default "";
/**
* Hystrix command key.
* <p/>
* default => the name of annotated method. for example:
* <code>
* ...
* @HystrixCommand
* public User getUserById(...)
* ...
* the command name will be: 'getUserById'
* </code>
*
* @return command key
*/
String commandKey() default "";
/**
* The thread-pool key is used to represent a
* HystrixThreadPool for monitoring, metrics publishing, caching and other such uses.
*
* @return thread pool key
*/
String threadPoolKey() default "";
/**
* Specifies a method to process fallback logic.
* A fallback method should be defined in the same class where is HystrixCommand.
* Also a fallback method should have same signature to a method which was invoked as hystrix command.
* for example:
* <code>
* @HystrixCommand(fallbackMethod = "getByIdFallback")
* public String getById(String id) {...}
*
* private String getByIdFallback(String id) {...}
* </code>
* Also a fallback method can be annotated with {@link HystrixCommand}
* <p/>
* default => see {@link com.netflix.hystrix.contrib.javanica.command.GenericCommand#getFallback()}
*
* @return method name
*/
String fallbackMethod() default "";
/**
* Specifies command properties.
*
* @return command properties
*/
HystrixProperty[] commandProperties() default {};
/**
* Specifies thread pool properties.
*
* @return thread pool properties
*/
HystrixProperty[] threadPoolProperties() default {};
/**
* Defines exceptions which should be ignored.
* Optionally these can be wrapped in HystrixRuntimeException if raiseHystrixExceptions contains RUNTIME_EXCEPTION.
*
* @return exceptions to ignore
*/
Class<? extends Throwable>[] ignoreExceptions() default {};
/**
* Specifies the mode that should be used to execute hystrix observable command.
* For more information see {@link ObservableExecutionMode}.
*
* @return observable execution mode
*/
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
/**
* When includes RUNTIME_EXCEPTION, any exceptions that are not ignored are wrapped in HystrixRuntimeException.
*
* @return exceptions to wrap
*/
HystrixException[] raiseHystrixExceptions() default {};
/**
* Specifies default fallback method for the command. If both {@link #fallbackMethod} and {@link #defaultFallback}
* methods are specified then specific one is used.
* note: default fallback method cannot have parameters, return type should be compatible with command return type.
*
* @return the name of default fallback method
*/
String defaultFallback() default "";
}
@Service
public class HelloService
{
@Autowired
RestTemplate restTemplate;
/**
* 设置命令名称,分组,线程池划分,用于船舱模式 线程池隔离对依赖服务的调用
* @param name
* @return
*/
@HystrixCommand(commandKey = "getHello", groupKey = "helloService", threadPoolKey = "getHelloThread", fallbackMethod = "defaultFallBack")
public String helloService(String name)
{
return restTemplate.getForEntity("http://eureka-client/name?name={0}", String.class, name).getBody();
}
public String defaultFallBack(String name)
{
return "get by eureka client error!";
}
}
参考借鉴:
https://blog.youkuaiyun.com/a298804870/article/details/53427873