1、调用“Main”方法
2、业务验证
1)判断有没有缓存
2)判断熔断有没有开启
3)判断限流有没有触发
4)判断业务执行有没有失败
5)判断业务执行有没有超时
6)所有的失败都会触发fallback
3、业务直接返回
1、Hystrix是用于处理延迟和容错的开源库
2、Hystrix主要用于避免级联故障,提高系统弹性
3、Hystrix解决了由于扇出导致的“雪崩效应”
4、Hystrix的核心是“隔离术”和“熔断机制”
1、服务隔离和服务熔断
2、服务降级、限流和快速失败
3、请求合并和请求缓存
4、自带单体和集群监控
1、HystrixCommand会以隔离的形式完成run方法调用(默认线程池隔离)
2、HystrixObservableCommand使用当前线程进行调用(默认信号量隔离)
Command:
1、单次处理
1)execute:同步执行
2)queue:异步执行
2、多次处理
1)observe(Hot处理):
执行Command的run方法;
加载/注册Subscriber对象;
将run方法结果注入到Subscriber对象的onNext方法
2)toObserve(Cold处理:每次订阅都需要一个新的Command对象)
加载/注册Subscriber对象;
执行Command的run方法;
将run方法结果注入到Subscriber对象的onNext方法
Hystrix完整配置列表:Hystrix完整配置列表 - throwable - 博客园
配置文件中配置:
#全局配置示例
hystrix:
threadpool:
default:
coreSize: 1000 ##并发执行的最大线程数,默认10
maxQueueSize: 1000 ##BlockingQueue的最大队列数
queueSizeRejectionThreshold: 500 ##即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
编程式配置:可以继承HystrixCommand或者HystrixObservableCommand对Hystrix进行配置(如果不做修改使用的是默认配置):
public Command() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Group Key"))
.andCommandKey(HystrixCommandKey.Factory.asKey("Command Key")));
}
1、Hystrix配置之GroupKey
例如同一个请求,请求多次,可能需要统计和监控,所以需要做一个手动分组,这个分组指的就是GroupKey,指定同一个GroupKey就是指定同一个分组
如果配置了GroupKey,那么具有相同的GroupKey的使用同一个线程池
1)Hystrix中GroupKey是唯一必填项
2)GroupKey可以起到分组监控和报警的作用
3)GroupKey将作为线程池的默认名称
2、Hystrix配置之CommandKey
1)Hystrix可以不填写CommandKey
2)默认Hystrix会通过反射类名命名CommandKey
3)在Setting中加入andCommandKey进行命名
1、请求缓存
1)Hystrix支持将请求结果进行本地缓存
2)通过重写实现getCacheKey方法来判断是否取出缓存
3)请求缓存要求请求必须在同一个上下文
4)可以用RequestCacheEnabled开启请求缓存
2、请求合并
优化点:多个服务调用的多次HTTP请求合并
缺点:很少有机会对同一个服务进行多次HTTP调用,同时还要足够的“近”
1)Hystrix支持将多个请求合并成一次请求
2)Hystrix请求合并要求两次请求必须足够“近”
3)请求合并分为局部合并和全局合并两种
4)Collapser可以设置相关参数
1、隔离介绍
1)Hystrix提供了信号量和线程两种隔离手段
2)线程隔离会在单独的线程中执行业务逻辑
3)信号量隔离在调用线程上执行
4)官方推荐优先线程隔离
2、线程隔离
1)应用自身完全受保护,不会受其他依赖影响
2)有效降低接入新服务的风险
3)依赖服务出现问题,应用自身可以快速反应问题
4)可以通过实时刷新动态属性减少依赖问题影响
3、信号量隔离
1)信号量隔离是轻量级的隔离术
2)无网络开销的情况推荐使用信号量隔离
3)信号量是通过计数器与请求线程比对进行限流的
4、Hystrix隔离之ThreadPoolKey
1)Hystrix可以不填写ThreadPoolKey
2)默认Hystrix会使用GroupKey命名线程池
3)在Setting中加入andThreadPoolKey进行命名
1、降级介绍
1)降级是一种“无奈”的选择
2)Command降级需要实现fallback方法
3)ObservableCommand降级实现resumeWithFallback方法
2、降级触发原则
1)HystrixBadRequestException以外的异常
2)运行超时或熔断器处于开启状态
3)线程池或信号量已满
3、快速失败
1)Hystrix提供了快速失败的机制
2)当不实现fallback方法时会将异常直接抛出
3、fallback
1)fallback方法与run方法是一一对应的关系
2)fallback中一般都是定义自己的业务异常
hystrix:
command:
default:
execution:
timeout:
enabled: true ##开启熔断器
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 60000 ##超时时间,毫秒
1、熔断器介绍
熔断器是一种开关,用来控制流量是否执行业务逻辑
2、熔断器核心指标
1)快照时间窗(一个时间段)
2)请求总数阈值(核心时间窗内有多少请求总数)
3)错误百分比阈值(当达到请求总数阈值,同时错误内容达到一定比例,触发熔断开关)
3、熔断器状态
1)熔断器开启:所有请求都会进入fallback方法
2)熔断器半开启:间歇性让请求触发run方法
3)熔断器关闭:正常处理业务请求
默认情况瞎熔断器开启5秒后进入半开启状态
熔断器的计算是有耗时的,等待熔断健康检查
4、强制开启和关闭
如何设置线程池?
例:30rps * 0.2seconds = 6 + breathing room(为额外开销增加冗余量) = 10 threads
Threadpool Queue size:5 – 10(线程个数的1.5 – 2倍)
Command基础配置:
配置 |
配置描述 |
execution.isolation.strategy |
隔离类型:THREAD-线程隔离,SEMAPHORE-信号量隔离 |
execution.timeout.enabled |
超时检查是否开启 |
fallback.enabled |
是否开启降级处理 |
请求上下文配置:
配置 |
配置描述 |
requestCache.enabled |
是否开启请求缓存,默认为true |
requestLog.enabled |
是否开启请求日志,默认为true |
maxRequestsInBatch |
设置批处理中允许的最大请求数 |
timerDelayInMilliseconds |
设置批处理创建到执行之间的毫秒数 |
线程池相关配置:
配置 |
配置描述 |
coreSize |
配置线程池大小,默认为10 |
keepAliveTimeMinutes |
配置核心线程数空闲时keepAlived时长,默认1分钟 |
maxQueueSize |
配置线程池任务队列大小,默认为-1 |
maximumSize |
线程池中线程的最大数量,默认值是 10 |
queueSizeRejectionThreshold |
任务队列的请求上线,默认值是10 |
allowMaximumSizeToDivergeFromCoreSize |
是否开启最大线程数 |
execution.isolation.thread.timeoutInMilliseconds |
设置超时时间 |
execution.isolation.thread.interruptOnTimeout |
请求超时是否中断任务 |
execution.isolation.thread.interruptOnCancel |
请求取消是否终端任务 |
信号量隔离配置:
配置 |
配置描述 |
execution.isolation.semaphore.maxConcurrentRequests |
任务执行信号量最大数 |
fallback.isolation.semaphore.maxConcurrentRequests |
失败任务执行信号量最大数 |
熔断机制相关配置:
配置 |
配置描述 |
circuitBreaker.enabled |
是否开启熔断器 |
circuitBreaker.requestVolumeThreshold |
启用熔断器功能窗口时间内的最小请求数 |
circuitBreaker.sleepWindowInMilliseconds |
半熔断开启时间 |
circuitBreaker.errorThresholdPercentage |
开启熔断的失败率阈值 |
circuitBreaker.forceOpen |
强制开启熔断器 |
circuitBreaker.forceClosed |
强制关闭熔断器 |
metrics相关配置:
配置 |
配置描述 |
metrics.rollingStats.timeInMilliseconds |
此配置项指定了窗口的大小,单位是 ms,默认值是 1000 |
metrics.rollingStats.numBuckets |
生成统计数据流时滑动窗口应该拆分的桶数 |
metrics.rollingPercentile.enabled |
是否统计方法响应时间百分比,默认为 true |
metrics.rollingPercentile.timeInMilliseconds |
统计响应时间百分比时的窗口大小 |
metrics.rollingPercentile.numBuckets |
统计响应时间百分比时滑动窗口要划分的桶用,默认为6 |
metrics.rollingPercentile.bucketSize |
统计响应时间百分比时,每个滑动窗口的桶内要保留的请求数 |
metrics.healthSnapshot.intervalInMilliseconds |
它指定了健康数据统计器中每个桶的大小,默认是 500ms |
1、添加hystrix-dashboard依赖
2、在配置文件里添加配置,暴露正常的可访问的安全的端口:
management:
endpoints:
web:
exposure:
include:"*"
3、访问ip:port/hystrix
1、CommandDemo:
@Data
public class CommandDemo extends HystrixCommand {
private String name;
public CommandDemo(String name){
super(Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CommandHelloWorld"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.defaultSetter()
.withRequestCacheEnabled(false) // 请求缓存开关)
// 切换线程池隔离还是信号量隔离
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
.withExecutionIsolationSemaphoreMaxConcurrentRequests(2)
//.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
// .withCircuitBreakerForceOpen(true) // 强制开启熔断器
// 单位时间内的请求阈值
.withCircuitBreakerRequestVolumeThreshold(2)
// 当满足请求阈值时,超过50%则开启熔断
.withCircuitBreakerErrorThresholdPercentage(50)
).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MyThreadPool"))
// .andThreadPoolPropertiesDefaults(
// HystrixThreadPoolProperties.defaultSetter()
// .withCoreSize(2)
// .withMaximumSize(3).withAllowMaximumSizeToDivergeFromCoreSize(true)
// .withMaxQueueSize(2)
// )
);
this.name = name;
}
// 单次请求调用的业务方法
@Override
protected String run() throws Exception {
String result = "CommandHelloWorld name : "+ name;
// Thread.sleep(800l);
if(name.startsWith("zhenghaorui")){
int i = 6/0;
}
System.err.println(result+" , currentThread-"+Thread.currentThread().getName());
return result;
}
@Override
protected String getCacheKey() {
return String.valueOf(name);
}
}
2、CommandTest:
public class CommandTest {
@Test
public void executeTest(){
long beginTime = System.currentTimeMillis();
CommandDemo commandDemo = new CommandDemo("execute");
// 同步执行Command
String result = commandDemo.execute();
long endTime = System.currentTimeMillis();
System.out.println("result="+result+" , speeding="+(endTime-beginTime));
}
@Test
public void queueTest() throws ExecutionException, InterruptedException {
long beginTime = System.currentTimeMillis();
CommandDemo commandDemo = new CommandDemo("queue");
Future queue = commandDemo.queue();
long endTime = System.currentTimeMillis();
System.out.println("future end , speeding="+(endTime-beginTime));
long endTime2 = System.currentTimeMillis();
System.out.println("result="+queue.get()+" , speeding="+(endTime2-beginTime));
}
@Test
public void observeTest(){
long beginTime = System.currentTimeMillis();
CommandDemo commandDemo = new CommandDemo("observe");
Observable observe = commandDemo.observe();
// 阻塞式调用
String result = observe.toBlocking().single();
long endTime = System.currentTimeMillis();
System.out.println("result="+result+" , speeding="+(endTime-beginTime));
// 非阻塞式调用
observe.subscribe(new Subscriber() {
@Override
public void onCompleted() {
System.err.println("observe , onCompleted");
}
@Override
public void onError(Throwable throwable) {
System.err.println("observe , onError - throwable="+throwable);
}
@Override
public void onNext(String result) {
long endTime = System.currentTimeMillis();
System.err.println("observe , onNext result="+result+" speend:"+(endTime - beginTime));
}
});
}
@Test
public void toObserveTest() throws InterruptedException {
long beginTime = System.currentTimeMillis();
CommandDemo commandDemo1 = new CommandDemo("toObservable1");
Observable toObservable1 = commandDemo1.toObservable();
// 阻塞式调用
String result = toObservable1.toBlocking().single();
long endTime = System.currentTimeMillis();
System.out.println("result="+result+" , speeding="+(endTime-beginTime));
CommandDemo commandDemo2 = new CommandDemo("toObservable2");
Observable toObservable2 = commandDemo2.toObservable();
// 非阻塞式调用
toObservable2.subscribe(new Subscriber() {
@Override
public void onCompleted() {
System.err.println("toObservable , onCompleted");
}
@Override
public void onError(Throwable throwable) {
System.err.println("toObservable , onError - throwable="+throwable);
}
@Override
public void onNext(String result) {
long endTime = System.currentTimeMillis();
System.err.println("toObservable , onNext result="+result+" speend:"+(endTime - beginTime));
}
});
Thread.sleep(2000l);
}
/**
* @Description: 演示请求缓存
* @Param: []
* @return: void
* @Author: zhenghaorui
*/
@Test
public void requestCache(){
// 开启请求上下文
HystrixRequestContext requestContext = HystrixRequestContext.initializeContext();
long beginTime = System.currentTimeMillis();
CommandDemo c1 = new CommandDemo("c1");
CommandDemo c2 = new CommandDemo("c2");
CommandDemo c3 = new CommandDemo("c1");
// 第一次请求
String r1 = c1.execute();
System.out.println("result="+r1+" , speeding="+(System.currentTimeMillis()-beginTime));
// 第二次请求
String r2 = c2.execute();
System.out.println("result="+r2+" , speeding="+(System.currentTimeMillis()-beginTime));
// 第三次请求
String r3 = c3.execute();
System.out.println("result="+r3+" , speeding="+(System.currentTimeMillis()-beginTime));
// 请求上下文关闭
requestContext.close();
}
/**
* @Description: 演示线程池内容
* @Param: []
* @return: void
* @Author: zhenghaorui
*/
@Test
public void threadTest() throws ExecutionException, InterruptedException {
CommandDemo c1 = new CommandDemo("c1");
CommandDemo c2 = new CommandDemo("c2");
CommandDemo c3 = new CommandDemo("c3");
CommandDemo c4 = new CommandDemo("c4");
CommandDemo c5 = new CommandDemo("c5");
Future q1 = c1.queue();
Future q2 = c2.queue();
Future q3 = c3.queue();
Future q4 = c4.queue();
Future q5 = c5.queue();
String r1 = q1.get();
String r2 = q2.get();
String r3 = q3.get();
String r4 = q4.get();
String r5 = q5.get();
System.out.println(r1+","+r2+","+r3+","+r4+","+r5);
// 1,2,3,4,5
// core 1,2 max 1
// queue 2
}
/**
* @Description: 信号量隔离测试
* @Param: []
* @return: void
* @Author: zhenghaorui
*/
@Test
public void semTest() throws InterruptedException {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
MyThread t3 = new MyThread("t3");
MyThread t4 = new MyThread("t4");
MyThread t5 = new MyThread("t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
Thread.sleep(2000l);
}
/**
* @Description: 熔断演示
* @Param: []
* @return: void
* @Author: zhenghaorui
*/
@Test
public void CBTest() throws InterruptedException {
// 正确 - 业务
CommandDemo c1 = new CommandDemo("helloworld");
System.out.println(c1.execute());
// 错误 - 业务
CommandDemo c2 = new CommandDemo("zhenghaorui-1");
System.out.println(c2.execute());
// 正确 - 业务
Thread.sleep(1000l);
CommandDemo c3 = new CommandDemo("helloworld");
System.out.println(c3.execute());
// 半熔断状态
Thread.sleep(5000l);
// 错误 - 业务
// CommandDemo c4 = new CommandDemo("zhenghaorui-2");
// System.out.println(c4.execute());
// 正确 - 业务
// CommandDemo c5 = new CommandDemo("helloworld");
// System.out.println(c5.execute());
// 成功
CommandDemo c6 = new CommandDemo("helloworld");
System.out.println(c6.execute());
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
CommandDemo commandDemo = new CommandDemo(name);
System.out.println("commandDemo result="+commandDemo.execute());
}
}