Hystrix框架简介
Hystrix翻译成中文是“豪猪”的意思。豪猪身上长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制。因此Hystrix 的logo也是定义成了豪猪。
假设有如下场景:
比如我们现在有3个业务调用,分别是查询订单、查询商品、查询用户,且这三个业务请求都是依赖第三方服务—订单服务、商品服务、用户服务。三个服务均是通过RPC调用。当查询订单服务,假如线程阻塞了,这个时候后续有大量的查询订单请求过来,那么容器中的线程数量则会持续增加直至CPU资源耗尽,整个服务对外不可用,集群环境下就是雪崩。
Hystrix核心功能
线程隔离
- 正常情况下,tomcat或其他容器的线程对外提供服务,接收用户请求。
- Hystrix线程隔离的做法是:将tomcat线程处理的任务转交给Hystrix内部的线程去执行,这样tomcat线程就可以去做其他事情了。当Hystrix的线程将任务执行完后,将执行结果返回给tomcat线程。
信号量隔离
信号量的资源隔离只是起到一个开关的作用,例如,服务X的信号量大小为10,那么同时只允许10个tomcat的线程(此处是tomcat的线程,而不是服务X的独立线程池里面的线程)来访问服务X,其他的请求就会被拒绝,从而达到限流保护的作用。
线程隔离和信号量隔离对比
降级策略
- 当请求出现了异常,超时,或者服务不可用的时候,一般情况你会怎么做?返回空,抛异常给调用方还是什么都不做?
- Hystrix可以让你自定义降级策略。当发生异常的时候,返回你事先定义好的策略。比如空对象/默认值
熔断技术
- 一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。
- 如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
请求缓存
- 将请求缓存下来,当后续有相同的请求到来的时候,直接返回缓存中的响应,从而避免直接对服务进行调用,增加服务的压力
- 比如根据用户id查询用户信息,根据商品id查询商品信息等,查询全国所有城市的邮编。这类实体的属性不会频繁的变动。
请求合并
将相同类型的请求合并成一次调用,而不是分别调用服务提供方,目的是降低服务提供方的压力。
Hystrix demo
HelloWorld demo:
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
return "Hello " + name + "!";
}
public static class UnitTest {
@Test
public void testSynchronous() {
assertEquals("Hello World!", new CommandHelloWorld("World").execute());
assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute());
}
@Test
public void testAsynchronous1() throws Exception {
assertEquals("Hello World!", new CommandHelloWorld("World").queue().get());
assertEquals("Hello Bob!", new CommandHelloWorld("Bob").queue().get());
}
@Test
public void testAsynchronous2() throws Exception {
Future<String> fWorld = new CommandHelloWorld("World").queue();
Future<String> fBob = new CommandHelloWorld("Bob").queue();
assertEquals("Hello World!", fWorld.get());
assertEquals("Hello Bob!", fBob.get());
}
@Test
public void testObservable() throws Exception {
Observable<String> fWorld = new CommandHelloWorld("World").observe();
Observable<String> fBob = new CommandHelloWorld("Bob").observe();
// blocking
assertEquals("Hello World!", fWorld.toBlocking().single());
assertEquals("Hello Bob!", fBob.toBlocking().single());
// non-blocking
// - this is a verbose anonymous inner-class approach and doesn't do assertions
fWorld.subscribe(new Observer<String>() {
@Override
public void onCompleted() {
System.out.println("onCompleted here");
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(String v) {
System.out.println("onNext: " + v);
}
});
// non-blocking
// - also verbose anonymous inner-class
// - ignore errors and onCompleted signal
fBob.subscribe(new Action1<String>() {
@Override
public void call(String v) {
System.out.println("onNext: " + v);
}
});
// non-blocking
// - using closures in Java 8 would look like this:
// fWorld.subscribe((v) -> {
// System.out.println("onNext: " + v);
// })
// - or while also including error handling
// fWorld.subscribe((v) -> {
// System.out.println("onNext: " + v);
// }, (exception) -> {
// exception.printStackTrace();
// })
// More information about Observable can be found at https://github.com/Netflix/RxJava/wiki/How-To-Use
}
}
}
上例中execute()方法是通过同步的方式执行任务;queue()方法是通过异步的防止执行任务。
new Observer<String>() { @Override public void onCompleted() { System.out.println("onCompleted here"); } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(String v) { System.out.println("onNext: " + v); } }
是对任务注册的回调事件。onCompleted是在任务执行完的时候回调,onError是在出现异常时候回调,onNext是获取结果后回调。三者的执行顺序是:onNext/onError完成之后最后回调onCompleted。
线程隔离和信号量隔离的demo:
package com.example.demo.hystrixdemo.isolation;
import com.netflix.hystrix.*;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
*
* 测试线程池隔离策略
* 设置线程池里的线程数=3,然后循环>3次和<3次,最后查看当前所有线程名称
*
*/
public class HystrixCommand4ThreadPoolTest extends HystrixCommand<String> {
private final String name;
public HystrixCommand4ThreadPoolTest(String name) {
// super(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"));
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ThreadPoolTest"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(5000)
)
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withCoreSize(3) // 配置线程池里的线程数
)
);
this.name = name;
}
@Override
protected String run() throws Exception {
/*---------------会触发fallback的case-------------------*/
// int j = 0;
// while (true) {
// j++;
return "a";
// }
// 除零异常
// int i = 1/0;
// 主动抛出异常
// throw new HystrixTimeoutException();
// throw new RuntimeException("this command will trigger fallback");
// throw new Exception("this command will trigger fallback");
// throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, commandClass, message, cause, fallbackException);
/*---------------不会触发fallback的case-------------------*/
// 被捕获的异常不会触发fallback
// try {
// throw new RuntimeException("this command never trigger fallback");
// } catch(Exception e) {
// e.printStackTrace();
// }
// HystrixBadRequestException异常由非法参数或非系统错误引起,不会触发fallback,也不会被计入熔断器
// throw new HystrixBadRequestException("HystrixBadRequestException is never trigger fallback");
TimeUnit.MILLISECONDS.sleep(2000);
System.out.println(Thread.currentThread().getName() + ": " + name);
return name;
}
@Override
protected String getFallback() {
return "fallback: " + name;
}
public static class UnitTest {
@Test
public void testSynchronous() throws IOException {
for(int i = 0; i < 3; i++) {
try {
// assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
// System.out.println("===========" + new HystrixCommand4ThreadPoolTest("Hlx").execute());
//占有线程池中的线程
Future<String> future = new HystrixCommand4ThreadPoolTest("get available thread" + i).queue();
//强制阻塞
// System.out.println("future返回:" + future.get());
} catch(Exception e) {
System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
}
}
for(int i = 0; i < 10; i++) {
try {
// assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
System.out.println("===========" + new HystrixCommand4ThreadPoolTest(" not get available thread" + i).execute());
// Future<String> future = new HystrixCommand4ThreadPoolTest("Hlx1"+i).queue();
// System.out.println("===========" + future);
} catch(Exception e) {
System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
}
}
try {
TimeUnit.MILLISECONDS.sleep(2000);
}catch(Exception e) {}
System.out.println("------开始打印现有线程---------");
Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
for (Thread thread : map.keySet()) {
System.out.println(thread.getName());
}
System.out.println(map);
System.out.println("thread num: " + map.size());
// int numExecuted = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size();
// System.out.println("num executed: " + numExecuted);
}
}
}
package com.example.demo.hystrixdemo.isolation;
import com.example.demo.hystrixdemo.HelloWorldHystrixCommand;
import com.netflix.hystrix.*;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
import org.junit.Test;
import java.io.IOException;
/**
* 测试信号量隔离策略
* 默认执行run()用的是主线程,为了模拟并行执行command,这里我们自己创建多个线程来执行command
* 设置ExecutionIsolationSemaphoreMaxConcurrentRequests为3,意味着信号量最多允许执行run的并发数为3,超过则触发降级,即不执行run而执行getFallback
* 设置FallbackIsolationSemaphoreMaxConcurrentRequests为1,意味着信号量最多允许执行fallback的并发数为1,超过则抛异常fallback execution rejected
*/
public class HystrixCommand4SemaphoreTest extends HystrixCommand<String> {
private final String name;
public HystrixCommand4SemaphoreTest(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SemaphoreTestGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("SemaphoreTestKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("SemaphoreTestThreadPoolKey"))
.andCommandPropertiesDefaults( // 配置信号量隔离
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) // 信号量隔离
.withExecutionIsolationSemaphoreMaxConcurrentRequests(3)
.withFallbackIsolationSemaphoreMaxConcurrentRequests(1)
)
// 设置了信号量隔离后,线程池配置将变无效
// .andThreadPoolPropertiesDefaults(
// HystrixThreadPoolProperties.Setter()
// .withCoreSize(13) // 配置线程池里的线程数
// )
);
this.name = name;
}
@Override
protected String run() throws Exception {
return "run(): name=" + name + ",线程名是" + Thread.currentThread().getName();
}
@Override
protected String getFallback() {
return "getFallback(): name=" + name + ",线程名是" + Thread.currentThread().getName();
}
public static class UnitTest {
@Test
public void testSynchronous() throws IOException, InterruptedException {
try {
Thread.sleep(2000);
for (int i = 0; i < 5; i++) {
final int j = i;
// 自主创建线程来执行command,创造并发场景
// @Override
Thread thread = new Thread(() -> {
// 这里执行两类command:HystrixCommand4SemaphoreTest设置了信号量隔离、HelloWorldHystrixCommand未设置信号量
System.out.println("-----------" + new HelloWorldHystrixCommand("HelloWorldHystrixCommand" + j).execute());
System.out.println("===========" + new HystrixCommand4SemaphoreTest("HystrixCommand4SemaphoreTest" + j).execute()); // 被信号量拒绝的线程从这里抛出异常
System.out.println("-----------" + new HelloWorldHystrixCommand("HelloWorldHystrixCommand" + j).execute()); // 被信号量拒绝的线程不能执行到这里
});
thread.start();
}
} catch (Exception e) {
e.printStackTrace();
}
// try {
// TimeUnit.MILLISECONDS.sleep(2000);
// }catch(Exception e) {}
// System.out.println("------开始打印现有线程---------");
// Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
// for (Thread thread : map.keySet()) {
// System.out.println(thread.getName());
// }
// System.out.println("thread num: " + map.size());
// System.in.read();
Thread.sleep(4000);
}
}
}
降级策略demo:
/**
*
* 以下四种情况将触发getFallback调用:
* 1)run()方法抛出非HystrixBadRequestException异常
* 2)run()方法调用超时
* 3)熔断器开启拦截调用
* 4)线程池/队列/信号量是否跑满
*
* 实现getFallback()后,执行命令时遇到以上4种情况将被fallback接管,不会抛出异常或其他
*/
public class HystrixFallback4ExceptionTest extends HystrixCommand<String> {
private final String name;
public HystrixFallback4ExceptionTest(String name) {
super(HystrixCommandGroupKey.Factory.asKey("FallbackGroup"));
this.name = name;
}
@Override
protected String run() throws Exception {
/*---------------会触发fallback的case-------------------*/
// 无限循环,实际上属于超时
// int j = 0;
// while (true) {
// j++;
// }
// 除零异常
// int i = 1/0;
// 主动抛出异常
// throw new HystrixTimeoutException();
// throw new RuntimeException("this command will trigger fallback");
// throw new Exception("this command will trigger fallback");
// throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, commandClass, message, cause, fallbackException);
/*---------------不会触发fallback的case-------------------*/
// 被捕获的异常不会触发fallback
// try {
// throw new RuntimeException("this command never trigger fallback");
// } catch(Exception e) {
// e.printStackTrace();
// }
// HystrixBadRequestException异常由非法参数或非系统错误引起,不会触发fallback,也不会被计入熔断器
throw new HystrixBadRequestException("HystrixBadRequestException is never trigger fallback");
// return name;
}
@Override
protected String getFallback() {
return "fallback: " + name;
}
public static class UnitTest {
@Test
public void testSynchronous() throws IOException {
try {
System.out.println(new HystrixFallback4ExceptionTest("ExceptionTest").execute());
} catch(Exception e) {
System.out.println("run()抛出HystrixBadRequestException时,会被捕获到这里" + e.getCause());
}
// System.in.read();
}
}
}
package com.example.demo.hystrixdemo.fallback;
import com.netflix.hystrix.*;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
*
* 设置线程池里的线程数=3,然后循环>3次和<3次,最后查看当前所有线程名称
*
*/
public class HystrixCommand4ThreadPoolTest extends HystrixCommand<String> {
private final String name;
public HystrixCommand4ThreadPoolTest(String name) {
// super(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"));
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ThreadPoolTest"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(5000)
)
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withCoreSize(3) // 配置线程池里的线程数
)
);
this.name = name;
}
@Override
protected String run() throws Exception {
/*---------------会触发fallback的case-------------------*/
// int j = 0;
// while (true) {
// j++;
return "a";
// }
// 除零异常
// int i = 1/0;
// 主动抛出异常
// throw new HystrixTimeoutException();
// throw new RuntimeException("this command will trigger fallback");
// throw new Exception("this command will trigger fallback");
// throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, commandClass, message, cause, fallbackException);
/*---------------不会触发fallback的case-------------------*/
// 被捕获的异常不会触发fallback
// try {
// throw new RuntimeException("this command never trigger fallback");
// } catch(Exception e) {
// e.printStackTrace();
// }
// HystrixBadRequestException异常由非法参数或非系统错误引起,不会触发fallback,也不会被计入熔断器
// throw new HystrixBadRequestException("HystrixBadRequestException is never trigger fallback");
TimeUnit.MILLISECONDS.sleep(2000);
System.out.println(Thread.currentThread().getName() + ": " + name);
return name;
}
@Override
protected String getFallback() {
return "fallback: " + name;
}
public static class UnitTest {
@Test
public void testSynchronous() throws IOException {
for(int i = 0; i < 3; i++) {
try {
// assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
// System.out.println("===========" + new HystrixCommand4ThreadPoolTest("Hlx").execute());
//占有线程池中的线程
Future<String> future = new HystrixCommand4ThreadPoolTest("get available thread" + i).queue();
//强制阻塞
// System.out.println("future返回:" + future.get());
} catch(Exception e) {
System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
}
}
for(int i = 0; i < 10; i++) {
try {
// assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
System.out.println("===========" + new HystrixCommand4ThreadPoolTest(" not get available thread" + i).execute());
// Future<String> future = new HystrixCommand4ThreadPoolTest("not get available thread" + i).queue();
// System.out.println("===========" + future.get());
} catch(Exception e) {
System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
}
}
try {
TimeUnit.MILLISECONDS.sleep(2000);
}catch(Exception e) {}
System.out.println("------开始打印现有线程---------");
Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
for (Thread thread : map.keySet()) {
System.out.println(thread.getName());
}
System.out.println(map);
System.out.println("thread num: " + map.size());
// int numExecuted = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size();
// System.out.println("num executed: " + numExecuted);
}
}
}
package com.example.demo.hystrixdemo.fallback;
import com.netflix.hystrix.*;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
/**
*
* CircuitBreakerRequestVolumeThreshold设置为3,意味着10s内请求超过3次就触发熔断器
* run()中无限循环使命令超时进入fallback,执行3次run后,将被熔断,进入降级,即不进入run()而直接进入fallback
* 如果未熔断,但是threadpool被打满,仍然会降级,即不进入run()而直接进入fallback
*/
public class HystrixCommand4CircuitBreakerTest extends HystrixCommand<String> {
private final String name;
public HystrixCommand4CircuitBreakerTest(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CircuitBreakerTestGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerTestKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerTest"))
.andThreadPoolPropertiesDefaults( // 配置线程池
HystrixThreadPoolProperties.Setter()
.withCoreSize(200) // 配置线程池里的线程数,设置足够多线程,以防未熔断却打满threadpool
)
.andCommandPropertiesDefaults( // 配置熔断器
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(true)
.withCircuitBreakerRequestVolumeThreshold(3)
.withCircuitBreakerErrorThresholdPercentage(80)
// .withCircuitBreakerForceOpen(true) // 置为true时,所有请求都将被拒绝,直接到fallback
// .withCircuitBreakerForceClosed(true) // 置为true时,将忽略错误
// .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) // 信号量隔离
// .withExecutionTimeoutInMilliseconds(5000)
)
);
this.name = name;
}
@Override
protected String run() throws Exception {
System.out.println("running run():" + name);
int num = Integer.valueOf(name);
if(num % 2 == 0 && num < 10) { // 直接返回
return name;
} else { // 无限循环模拟超时
int j = 0;
while (true) {
j++;
}
}
// return name;
}
@Override
protected String getFallback() {
return "CircuitBreaker fallback: " + name;
}
public static class UnitTest {
@Test
public void testSynchronous() throws IOException {
for(int i = 0; i < 50; i++) {
try {
System.out.println("===========" + new HystrixCommand4CircuitBreakerTest(String.valueOf(i)).execute());
// try {
// TimeUnit.MILLISECONDS.sleep(1000);
// }catch(Exception e) {}
// Future<String> future = new HystrixCommand4CircuitBreakerTest("Hlx"+i).queue();
// System.out.println("===========" + future);
} catch(Exception e) {
System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
}
}
System.out.println("------开始打印现有线程---------");
Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
for (Thread thread : map.keySet()) {
System.out.println(thread.getName());
}
System.out.println("thread num: " + map.size());
System.in.read();
}
}
}
Hystrix执行流程图:
请求合并的demo:
package com.example.demo.hystrixdemo.collapsing;
import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Sample {@link HystrixCollapser} that automatically batches multiple requests to execute()/queue()
* into a single {@link HystrixCommand} execution for all requests within the defined batch (time or size).
*/
public class HelloWorldHystrixCollapser extends HystrixCollapser<List<String>, String, Integer> {
private final Integer key;
public HelloWorldHystrixCollapser(Integer key) {
this.key = key;
}
@Override
public Integer getRequestArgument() {
return key;
}
// 创建一个批量请求命令
@Override
protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
return new BatchCommand(requests); // 把批量请求传给command类
}
// 把批量请求的结果和对应的请求一一对应起来
@Override
protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
int count = 0;
for (CollapsedRequest<String, Integer> request : requests) {
request.setResponse(batchResponse.get(count++));
}
}
// command类
private static final class BatchCommand extends HystrixCommand<List<String>> {
private final Collection<CollapsedRequest<String, Integer>> requests;
private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CollepsingGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CollepsingKey")));
this.requests = requests;
}
@Override
protected List<String> run() {
ArrayList<String> response = new ArrayList<String>();
// 处理每个请求,返回结果
for (CollapsedRequest<String, Integer> request : requests) {
// artificial response for each argument received in the batch
response.add("ValueForKey: " + request.getArgument() + " thread:" + Thread.currentThread().getName());
}
return response;
}
}
}
package com.example.demo.hystrixdemo.collapsing;
import com.netflix.hystrix.HystrixEventType;
import com.netflix.hystrix.HystrixInvokableInfo;
import com.netflix.hystrix.HystrixRequestLog;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.junit.Test;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* 相邻两个请求可以自动合并的前提是两者足够“近”:启动执行的间隔时间足够小,默认10ms
*
*/
public class HystrixCommand4RequestCollapsingTest {
@Test
public void testCollapser() throws Exception {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
Future<String> f1 = new HelloWorldHystrixCollapser(1).queue();
Future<String> f2 = new HelloWorldHystrixCollapser(2).queue();
// System.out.println(new HelloWorldHystrixCollapser(1).execute()); // 这条很可能会合并到f1和f2的批量请求中
// System.out.println(new HelloWorldHystrixCollapser(1).execute()); // 由于上面有IO打印,这条很可能不会合并到f1和f2的批量请求中
Future<String> f3 = new HelloWorldHystrixCollapser(3).queue();
Future<String> f4 = new HelloWorldHystrixCollapser(4).queue();
Future<String> f5 = new HelloWorldHystrixCollapser(5).queue();
// f5和f6,如果sleep时间够小则会合并,如果sleep时间够大则不会合并,默认10ms
// TimeUnit.MILLISECONDS.sleep(10);
Future<String> f6 = new HelloWorldHystrixCollapser(6).queue();
System.out.println(System.currentTimeMillis() + " : " + f1.get());
System.out.println(System.currentTimeMillis() + " : " + f2.get());
System.out.println(System.currentTimeMillis() + " : " + f3.get());
System.out.println(System.currentTimeMillis() + " : " + f4.get());
System.out.println(System.currentTimeMillis() + " : " + f5.get());
System.out.println(System.currentTimeMillis() + " : " + f6.get());
// 下面3条都不在一个批量请求中
// System.out.println(new HelloWorldHystrixCollapser(7).execute());
// System.out.println(new HelloWorldHystrixCollapser(8).queue().get());
// System.out.println(new HelloWorldHystrixCollapser(9).queue().get());
// note:numExecuted表示共有几个命令执行,1个批量多命令请求算一个,这个实际值可能比代码写的要多,因为due to non-determinism of scheduler since this example uses the real timer
int numExecuted = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size();
System.out.println(System.currentTimeMillis() + " : " + "num executed: " + numExecuted);
int numLogs = 0;
for (HystrixInvokableInfo<?> command : HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()) {
numLogs++;
// assert the command is the one we're expecting
// assertEquals("CollepsingKey", command.getCommandKey().name());
System.out.println(System.currentTimeMillis() + " : " + command.getCommandKey().name() + " => command.getExecutionEvents(): " + command.getExecutionEvents());
// confirm that it was a COLLAPSED command execution
// assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
}
assertEquals(numExecuted, numLogs);
} finally {
context.shutdown();
}
}
}
hystrix支持N个请求自动合并为一个请求,这个功能在有网络交互的场景下尤其有用,比如每个请求都要网络访问远程资源,如果把请求合并为一个,将使多次网络交互变成一次,极大节省开销。重要一点,两个请求能自动合并的前提是两者足够“近”,即两者启动执行的间隔时长要足够小,默认为10ms,即超过10ms将不自动合并。
以demo为例,我们连续发起多个queue请求,依次返回f1~f6共6个Future对象,根据打印结果可知f1~f5同处一个线程,说明这5个请求被合并了,而f6由另一个线程执行,这是因为f5和f6中间隔了一个sleep,超过了合并要求的最大间隔时长。请求熔断的demo:
package com.example.demo.hystrixdemo.circuitbreak;
import com.netflix.hystrix.*;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
/**
*
* CircuitBreakerRequestVolumeThreshold设置为3,意味着10s内请求超过3次就触发熔断器
* run()中无限循环使命令超时进入fallback,执行3次run后,将被熔断,进入降级,即不进入run()而直接进入fallback
* 如果未熔断,但是threadpool被打满,仍然会降级,即不进入run()而直接进入fallback
*/
public class HystrixCommand4CircuitBreakerTest extends HystrixCommand<String> {
private final String name;
public HystrixCommand4CircuitBreakerTest(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CircuitBreakerTestGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerTestKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerTest"))
.andThreadPoolPropertiesDefaults( // 配置线程池
HystrixThreadPoolProperties.Setter()
.withCoreSize(200) // 配置线程池里的线程数,设置足够多线程,以防未熔断却打满threadpool
)
.andCommandPropertiesDefaults( // 配置熔断器
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(true)
.withCircuitBreakerRequestVolumeThreshold(3)
.withCircuitBreakerErrorThresholdPercentage(80)
// .withCircuitBreakerForceOpen(true) // 置为true时,所有请求都将被拒绝,直接到fallback
// .withCircuitBreakerForceClosed(true) // 置为true时,将忽略错误
// .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) // 信号量隔离
// .withExecutionTimeoutInMilliseconds(5000)
)
);
this.name = name;
}
@Override
protected String run() throws Exception {
System.out.println("running run():" + name);
int num = Integer.valueOf(name);
if(num % 2 == 0 && num < 10) { // 直接返回
return name;
} else { // 无限循环模拟超时
int j = 0;
while (true) {
j++;
}
}
// return name;
}
@Override
protected String getFallback() {
return "CircuitBreaker fallback: " + name;
}
public static class UnitTest {
@Test
public void testSynchronous() throws IOException {
for(int i = 0; i < 50; i++) {
try {
System.out.println("===========" + new HystrixCommand4CircuitBreakerTest(String.valueOf(i)).execute());
// try {
// TimeUnit.MILLISECONDS.sleep(1000);
// }catch(Exception e) {}
// Future<String> future = new HystrixCommand4CircuitBreakerTest("Hlx"+i).queue();
// System.out.println("===========" + future);
} catch(Exception e) {
System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
}
}
System.out.println("------开始打印现有线程---------");
Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
for (Thread thread : map.keySet()) {
System.out.println(thread.getName());
}
System.out.println("thread num: " + map.size());
}
}
}
熔断机制相当于电路的跳闸功能,举个栗子,我们可以配置熔断策略为当请求错误比例在10s内>50%时,该服务将进入熔断状态,后续请求都会进入fallback。
以demo为例,我们通过withCircuitBreakerRequestVolumeThreshold配置10s内请求数超过3个时熔断器开始生效,通过withCircuitBreakerErrorThresholdPercentage配置错误比例>80%时开始熔断,然后for循环执行execute()触发run(),在run()里,如果name是小于10的偶数则正常返回,否则超时,通过多次循环后,超时请求占所有请求的比例将大于80%,就会看到后续请求都不进入run()而是进入getFallback(),因为不再打印"running run():" + name了。
请求缓存的demo:
package com.example.demo.hystrixdemo.cache;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* cache只有同在一个context中才生效
* 通过HystrixRequestContext.initializeContext()初始化context,通过shutdown()关闭context
*/
public class HystrixCommand4RequestCacheTest extends HystrixCommand<Boolean> {
private final int value;
private final String value1;
protected HystrixCommand4RequestCacheTest(int value, String value1) {
super(HystrixCommandGroupKey.Factory.asKey("RequestCacheCommandGroup"));
this.value = value;
this.value1 = value1;
}
// 返回结果是cache的value
@Override
protected Boolean run() {
return value == 0 || value % 2 == 0;
}
// 构建cache的key
@Override
protected String getCacheKey() {
return String.valueOf(value) + value1;
}
public static class UnitTest {
@Test
public void testWithoutCacheHits() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
assertTrue(new HystrixCommand4RequestCacheTest(2,"HLX").execute());
assertFalse(new HystrixCommand4RequestCacheTest(1,"HLX").execute());
assertTrue(new HystrixCommand4RequestCacheTest(0,"HLX").execute());
assertTrue(new HystrixCommand4RequestCacheTest(58672,"HLX").execute());
} finally {
context.shutdown();
}
}
@Test
public void testWithCacheHits() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
HystrixCommand4RequestCacheTest command2a = new HystrixCommand4RequestCacheTest(2,"HLX");
HystrixCommand4RequestCacheTest command2b = new HystrixCommand4RequestCacheTest(2,"HLX");
HystrixCommand4RequestCacheTest command2c = new HystrixCommand4RequestCacheTest(2,"HLX1");
assertTrue(command2a.execute());
// this is the first time we've executed this command with the value of "2" so it should not be from cache
assertFalse(command2a.isResponseFromCache());
assertTrue(command2b.execute());
// this is the second time we've executed this command with the same value so it should return from cache
assertTrue(command2b.isResponseFromCache());
assertTrue(command2c.execute());
assertFalse(command2c.isResponseFromCache());
} finally {
context.shutdown();
}
// start a new request context
context = HystrixRequestContext.initializeContext();
try {
HystrixCommand4RequestCacheTest command3a = new HystrixCommand4RequestCacheTest(2,"HLX");
HystrixCommand4RequestCacheTest command3b = new HystrixCommand4RequestCacheTest(2,"HLX");
assertTrue(command3a.execute());
// this is a new request context so this should not come from cache
assertFalse(command3a.isResponseFromCache());
// 从command3a.execute()执行中得到的cache
command3b.execute();
assertTrue(command3b.isResponseFromCache());
} finally {
context.shutdown();
}
}
}
}
hystrix支持将一个请求结果缓存起来,下一个具有相同key的请求将直接从缓存中取出结果,减少请求开销。要使用hystrix cache功能,第一个要求是重写getCacheKey(),用来构造cache key;第二个要求是构建context,如果请求B要用到请求A的结果缓存,A和B必须同处一个context。通过HystrixRequestContext.initializeContext()和context.shutdown()可以构建一个context,这两条语句间的所有请求都处于同一个context。
以demo的testWithCacheHits()为例,command2a、command2b、command2c同处一个context,前两者的cache key都是2HLX(见getCacheKey()),所以command2a执行完后把结果缓存,
command2b执行时就不走run()而是直接从缓存中取结果了,而command2c的cache key是2HLX1,无法从缓存中取结果。此外,通过isResponseFromCache()可检查返回结果是否来自缓存。
源码地址:
https://github.com/PerseveranceForever/hystrix_demo.git