客户端弹性模式
4种客户端弹性模式:
- 客户端负载均衡模式:Eureka Ribbon
- 断路器模式:当服务调用时,断路器将监视这个调用。如果用时太长,断路器会介入并中断调用,如果某一个远程资源的调用失败次数足够多,断路器将采取快速失败,阻止将来调用远程失败的资源
- 后备模式:当服务调用失败时,尝试通过其他方式执行操作,而不是生成一个异常
- 舱壁模式:可以把远程资源的调用分到线程池中,并降低一个缓慢的远程资源调用拖垮整个应用程序的风险
客户端弹性重要性
传统调用模式:
断路器优化后模式:
断路器模式为远程调用提供的关键能力:
- 快速失败:当远程服务处于降级状态时,应用程序将会快速失败,并防止会拖垮整个应用程序的资源好近问题的出现
- 优雅的失败:通过超时和快速失败,断路器模式寻找替代机制来执行用户意图
- 无缝恢复:断路器定期检查所请求的资源是否重新上线
Hystrix
底层原理:
服务端pom文件:
Spring Cloud Hystrix 依赖项
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
Netflix Hystrix 核心库
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.9</version>
</dependency>
不一定要引入 hystrix-javanica ,spring-cloud-starter-hystrix中已经包括了一个 hystrix-javanica ,此处是由于 Cloud 版本不一致导致
如果需要多个返回值,则构建HystrixObservableCommand对象
默认@HystrixCommand 每当调用超过1000ms时,断路器就会中断此方法的调用
HystrixCommandProperties.java中可以查看 HystrixCommand 下所有可配置的属性
注意:在不带任何属性时买这个注解会将所有远程服务调用都放在同一个线程池下。可能会导致程序出现问题
加入随机等待时间
失败:
成功:
定制断路器超时时间
默认超时时间12s
如果确实遇到一些比其他服务调用需要更长时间的服务调用,务必将这些服务调用隔离到单独的线程池中
后备处理
使用后备策略必须做2件事:
- @HystrixCommand中加入fallbackMethod
- 定义一个后备方法,此方法必须由@HystrixCommand保护的方法位于同一个类中,并且必须具有与原始方法完全相同的方法签名,因为传递给保护的原始方法的所有参数都将传递给后备方法
舱壁模式
在不使用舱壁模式下,服务的调用是使用同一批线程来执行调用的,存在大量请求的情况下,一个服务出现性能问题会导致所有线程等待处理工作,同事阻塞新请求,导致java容器崩溃。
舱壁模式将远程资源调用隔离在他们自己的线程池中,以便可以控制单个表现不佳的服务,而不会使该容器崩溃
maxQueueSize:如果设置为-1,将使用Java的SynchronousQueue来保存所有传入的请求,如果大于1,将使用java LinkedBlockingQueue
定义线程池的适当大小是多少?Netflix推荐公式:
服务在健康状态时每秒支撑的最大请求数 * 第99百分位延迟时间(以秒为单位)+ 用于缓冲的少量额外线程
基础进阶 微调 Hystrix
Hystrix 在远程资源调用失败时使用的决策过程:
默认10s计时器内验证失败调用次数是否达到20次
在配置Hystrix时,关键点是,开发人员可以使用Hystrix的3个配置级别:
- 整个应用程序级别的默认值
- 类级别的默认值
- 在类中定义线程池级别
类级别默认值:使用@DefultProperties 注解在类外层配置
@HystrixCommand所有配置值:
线程上下文和Hystrix
当一个@HystrixCommand被执行时,他可以使用两种不同的隔离策略 Thread(线程)和 Semaphore(信号量)来运行。
默认情况下以 Thread 隔离策略运行。
此配置修改默认隔离级别:
注意:Thread 隔离比 Semaphore 隔离更重,Semaphore隔离模型适用于服务量很大且正在使用异步I/O编程模型(假设使用的是像Net体验这样的异步I/O容器)运行的情况
ThreadLocal 与 Hystrix
默认情况下,Hystrix 不会将 父线程的上下文传播到由 Hystrix 命令管理的线程中
Hystrix 和 Spring Cloud 提供了一种机制,可以将父线程的上下文传播到由 Hystrix 线程池管理的线程。这种机制被称为 Hystrix ConcurrencyStrategy
Hystrix ConcurrencyStrategy 实战
实现Hystrix ConcurrencyStrategy需要执行以下3个操作:
- 定义自定义的 Hystrix 并发策略类
- 定义一个 Callable 类,将 UserContext 注入 Hystrix 命令中
- 配置SpringCloud 以使用自定义 Hystrix并发策略
1.定义自定义的 Hystrix 并发策略类
public class ThreadLocalAwareStrategy extends HystrixConcurrencyStrategy{
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
public ThreadLocalAwareStrategy(
HystrixConcurrencyStrategy existingConcurrencyStrategy) {
this.existingConcurrencyStrategy = existingConcurrencyStrategy;
}
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize)
: super.getBlockingQueue(maxQueueSize);
}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(
HystrixRequestVariableLifecycle<T> rv) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.getRequestVariable(rv)
: super.getRequestVariable(rv);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixProperty<Integer> corePoolSize,
HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize,
maximumPoolSize, keepAliveTime, unit, workQueue)
: super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
keepAliveTime, unit, workQueue);
}
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy
.wrapCallable(new DelegatingUserContextCallable<T>(callable, UserContextHolder.getContext()))
: super.wrapCallable(new DelegatingUserContextCallable<T>(callable, UserContextHolder.getContext()));
}
}
2.定义一个java Callable类 ,将UserContext注入 Hystrix命令中
public final class DelegatingUserContextCallable<V> implements Callable<V> {
private final Callable<V> delegate;
private UserContext originalUserContext;
public DelegatingUserContextCallable(Callable<V> delegate,
UserContext userContext) {
this.delegate = delegate;
this.originalUserContext = userContext;
}
public V call() throws Exception {
UserContextHolder.setContext( originalUserContext );
try {
return delegate.call();
}
finally {
this.originalUserContext = null;
}
}
public static <V> Callable<V> create(Callable<V> delegate,
UserContext userContext) {
return new DelegatingUserContextCallable<V>(delegate, userContext);
}
}
3.配置Spring Cloud 以使用自定义 Hystrix 并发策略
@Configuration
public class ThreadLocalConfiguration {
@Autowired(required = false)
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
@PostConstruct
public void init() {
// Keeps references of existing Hystrix plugins.
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
.getEventNotifier();
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
.getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
.getPropertiesStrategy();
HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance()
.getCommandExecutionHook();
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(new ThreadLocalAwareStrategy(existingConcurrencyStrategy));
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
}
}