1、Hello World!
以下是 HystrixCommand
的最简单的 “Hello World” 实现:
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() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}
HystrixObservableCommand
等效操作:
一个等效的Hello World 解决方案是使用 HystrixObservableCommand
代替 HystrixCommand
来覆盖 construct
方法,如下所示:
public class CommandHelloWorld extends HystrixObservableCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected Observable<String> construct() {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
try {
if (!observer.isUnsubscribed()) {
// a real example would do work like a network call here
observer.onNext("Hello");
observer.onNext(name + "!");
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
} ).subscribeOn(Schedulers.io());
}
}
2、Synchronous Execution
您可以与 execute()
方法同步执行HystrixCommand
,如下例所示:
String s = new CommandHelloWorld("World").execute();
此表单的执行通过以下测试:
@Test
public void testSynchronous() {
assertEquals("Hello World!", new CommandHelloWorld("World").execute());
assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute());
}
使用 HystrixObservableCommand
可以达到同样的效果:
HystrixObservableCommand
没有简单的执行等价,但是如果你知道由这样一个命令产生的 Observable
对象必须总是只产生一个值,你可以通过对这个 Observable
对象应用 .toBlocking().toFuture().get()
来模仿 execute
的行为。
3、Asynchronous Execution
你可以使用 queue()
方法异步执行 HystrixCommand
,如下所示:
Future<String> fs = new CommandHelloWorld("World").queue();
你可以使用 Future
来检索命令的结果:
String s = fs.get();
以下单元测试演示了该行为:
@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());
}
以下两者是等价的:
String s1 = new CommandHelloWorld("World").execute();
String s2 = new CommandHelloWorld("World").queue().get();
HystrixObservableCommand
相等的:
没有简单的等同于 HystrixObservableCommand
的 queue
,但是如果你知道生产的 Observable
这种命令必须始终只产生一个值,你可以模仿的行为队列通过应用 RxJava
操作 .toBlocking().toFuture()
返回的Observable
。
4、Reactive Execution
你也可以使用以下方法之一来观察 HystrixCommand
的 Observable
结果:
- observe()——返回一个立即执行命令的“hot”
Observable
对象,不过由于Observable
对象是通过ReplaySubject
进行过滤的,所以在你有机会订阅之前,不会丢失它发出的任何项 toObservable()
——返回一个“cold”的Observable
对象,它不会执行这个命令,直到你订阅了这个Observable
对象才会开始发送它的结果
Observable<String> ho = new CommandHelloWorld("World").observe();
// or Observable<String> co = new CommandHelloWorld("World").toObservable();
然后你可以通过订阅 Observable
来获取该命令的值:
ho.subscribe(new Action1<String>() {
@Override
public void call(String s) {
// value emitted here
}
});
以下单元测试演示了该行为:
@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.toBlockingObservable().single());
assertEquals("Hello Bob!", fBob.toBlockingObservable().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() {
// nothing needed 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);
}
});
}
使用Java 8 lambdas/闭包更紧凑;它看起来是这样的:
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();
})
更多关于Observable的信息可以在 http://reactivex.io/documentation/observable.html 上找到
5、Reactive Commands
与其使用上述方法将 HystrixCommand
转换为 Observable
,还不如创建一个 HystrixObservableCommand
,它是 HystrixCommand
的专用版本,用来包装 Observable
。 HystrixObservableCommand
能够包装发出多个项的 Observable
,而普通的 HystrixCommands
,即使转换成 Observable
,也不会发出多个项。
在这种情况下,与其用命令逻辑覆盖 run
方法(就像你用普通的 HystrixCommand
那样),不如覆盖construct
方法,这样它就会返回你想包装的 Observable
对象。
- observe()——返回一个立即执行命令的“hot”
Observable
对象,不过由于Observable
对象是通过ReplaySubject
进行过滤的,所以在你有机会订阅之前,不会丢失它发出的任何项 toObservable()
——返回一个“cold”的Observable
对象,它不会执行这个命令,直到你订阅了这个Observable
对象才会开始发送它的结果
6、Fallback
您可以通过添加一个 fallback 方法来支持 Hystrix
命令的优雅降级,Hystrix 将调用这个回退方法来获取默认值或主命令失败时的值。你会想要实现一个 fallback ,大多数 Hystrix 命令可能会失败,有几个例外:
- 执行写操作的命令:
如果你Hystrix命令是为了做一个写操作,而不是返回一个值(一个命令通常会返回一个空的HystrixCommand或空可观测HystrixObservableCommand)的情况下,实现回退并没有太多意义。如果写入失败,您可能希望将失败传播回调用者。 - 批处理系统/离线计算
如果Hystrix命令正在填充缓存,或生成报告,或执行任何类型的脱机计算,通常更合适的做法是将错误传播回调用者,然后调用者可以稍后重试命令,而不是向调用者发送一个无声的响应。
无论您的命令是否有 fallback 方法,所有通常的 Hystrix 状态和断路器 状态/指标 都会更新,以指示命令失败。
在普通的 HystrixCommand
中,您可以通过 getFallback()
实现来实现回退。Hystrix 将对所有类型的失败执行这种回退,比如run()失败
、超时
、线程池或信号量拒绝
,以及断路器短路
。以下示例包含了这样的回退:
public class CommandHelloFailure extends HystrixCommand<String> {
private final String name;
public CommandHelloFailure(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
throw new RuntimeException("this command always fails");
}
@Override
protected String getFallback() {
return "Hello Failure " + name + "!";
}
}
这个命令的 run()
方法在每次执行时都会失败。然而,调用者将始终接收命令的 getFallback()
方法返回的值,而不是接收异常:
@Test
public void testSynchronous() {
assertEquals("Hello Failure World!", new CommandHelloFailure("World").execute());
assertEquals("Hello Failure Bob!", new CommandHelloFailure("Bob").execute());
}
HystrixObservableCommand 等效:
对于 HystrixObservableCommand
,你可以重写 resumeWithFallback
方法,这样它就会返回第二个Observable
,如果它失败了,这个 Observable
就会接管主 Observable
。注意,由于 Observable
对象可能会在已经发射了一个或多个项之后失败,所以你的回退不应该假定它会发射 Observable
看到的唯一值。
在内部,Hystrix 使用RxJava的 onErrorResumeNext 操作符,在出现错误的情况下,实现主 Observable
和备 Observable
之间的无缝转换。
时序图:
@adrianb11好心地提供了一个序列图,演示了超时然后回退是如何工作的。
7、Error Propagation
除了HystrixBadRequestException
之外,run()
方法抛出的所有异常都算作失败,并触发 getFallback()
和断路器逻辑。
你可以包装你想在 HystrixBadRequestException
中抛出的异常,并通过 getCause()
检索它。HystrixBadRequestException
用于报告非法参数或非系统故障的用例,这些故障不应该计算在故障度量上,也不应该触发回退逻辑。
HystrixObservableCommand
等效:
在HystrixObservableCommand
的例子中,不可恢复的错误是通过产生的 Observable
的 onError
通知返回的,而回退是通过回退到 Hystrix 通过你实现的 resumeWithFallback
方法获得的第二个 Observable
来完成的。
执行异常类型
Failure Type | Exception class | Exception.cause | subject to fallback |
---|---|---|---|
FAILURE | HystrixRuntimeException | underlying exception (user-controlled) | YES |
TIMEOUT | HystrixRuntimeException | j.u.c.TimeoutException | YES |
SHORT_CIRCUITED | HystrixRuntimeException | j.l.RuntimeException | YES |
THREAD_POOL_REJECTED | HystrixRuntimeException | j.u.c.RejectedExecutionException | YES |
SEMAPHORE_REJECTED | HystrixRuntimeException | HystrixRuntimeException | YES |
BAD_REQUEST | HystrixBadRequestException | underlying exception (user-controlled) | NO |
8、Command Name
默认情况下,Command 名称是从类名派生的:
getClass().getSimpleName();
通过 HystrixCommand
或 HystrixObservableCommand
构造函数来显式定义这个名字:
public CommandHelloWorld(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));
this.name = name;
}
为了保存每个命令分配的 Setter 分配,你也可以像这样缓存 Setter:
private static final Setter cachedSetter =
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
public CommandHelloWorld(String name) {
super(cachedSetter);
this.name = name;
}
HystrixCommandKey是一个接口,可以被实现为enum或常规类,但它也有一个要构造的 helper 工厂类和实习实例,例如:
HystrixCommandKey.Factory.asKey("HelloWorld")
9、Command Group
Hystrix使用 command group 键将报告、警报、仪表板或团队/库所有权等命令分组在一起。
默认情况下,Hystrix使用这个来定义命令线程池,除非定义了单独的线程池。
HystrixCommandGroupKey
是一个接口,可以实现为 enum 或常规类,但它也有帮助工厂类构造和实习实例,如:
HystrixCommandGroupKey.Factory.asKey("ExampleGroup")
10、Command Thread-Pool
线程池键表示用于监视、度量发布、缓存和其他此类用途的 HystrixThreadPool
。HystrixCommand
与单个HystrixThreadPool
相关联,通过注入HystrixThreadPoolKey
来获取,或者默认使用 HystrixCommandGroupKey
创建。
通过 HystrixCommand
或 HystrixObservableCommand
构造函数来显式定义这个名字:
public CommandHelloWorld(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
this.name = name;
}
HystrixThreadPoolKey
是一个接口,可以实现为 enum 或常规类,但它也有帮助工厂类构造和实习实例,如:
HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")
使用 HystrixThreadPoolKey
而不是不同的 HystrixCommandGroupKey
的原因是,多个命令可能属于同一所有权或逻辑功能的“组”,但某些命令可能需要相互隔离。
下面是一个简单的例子:
- 访问视频元数据的两个命令
- 组名是" VideoMetadata "
- 命令A反对资源#1
- 命令B与资源#2相反
如果命令A变得潜在并饱和其线程池,则不应该阻止命令B执行请求,因为它们每个都碰到不同的后端资源。
因此,从逻辑上讲,我们希望将这些命令组合在一起,但希望它们以不同的方式隔离,并使用HystrixThreadPoolKey
为每个命令提供不同的线程池。
11、Request Cache
通过在 HystrixCommand
或 HystrixObservableCommand
对象上实现 getCacheKey()
方法来启用请求缓存,如下所示:
public class CommandUsingRequestCache extends HystrixCommand<Boolean> {
private final int value;
protected CommandUsingRequestCache(int value) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.value = value;
}
@Override
protected Boolean run() {
return value == 0 || value % 2 == 0;
}
@Override
protected String getCacheKey() {
return String.valueOf(value);
}
}
因为这取决于请求上下文,所以我们必须初始化 HystrixRequestContext
。
在一个简单的单元测试中,你可以这样做:
@Test
public void testWithoutCacheHits() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
assertTrue(new CommandUsingRequestCache(2).execute());
assertFalse(new CommandUsingRequestCache(1).execute());
assertTrue(new CommandUsingRequestCache(0).execute());
assertTrue(new CommandUsingRequestCache(58672).execute());
} finally {
context.shutdown();
}
}
通常,这个上下文将通过封装用户请求或其他生命周期钩子的 ServletFilter
进行初始化和关闭。
下面是一个例子,展示了命令如何从缓存中获取它们的值(以及你如何查询一个对象来知道它的值是否来自缓存)在一个请求上下文中:
@Test
public void testWithCacheHits() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
CommandUsingRequestCache command2a = new CommandUsingRequestCache(2);
CommandUsingRequestCache command2b = new CommandUsingRequestCache(2);
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());
} finally {
context.shutdown();
}
// start a new request context
context = HystrixRequestContext.initializeContext();
try {
CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);
assertTrue(command3b.execute());
// this is a new request context so this
// should not come from cache
assertFalse(command3b.isResponseFromCache());
} finally {
context.shutdown();
}
}
12、Request Collapsing
请求合并允许多个请求被批处理成一个 HystrixCommand
实例执行。
折叠器可以使用批处理大小和创建批处理后的运行时间作为执行批处理的触发器。
Hystrix 支持两种类型的请求合并 :请求作用域和全局作用域。这是在折叠器构造时配置的,默认为请求范围。
请求作用域的折叠器每个HystrixRequestContext
收集一个批处理,而全局作用域的折叠器跨多个HystrixRequestContext
收集一个批处理。因此,如果您的下游依赖项不能在单个命令调用中处理多个HystrixRequestContexts
,那么请求作用域崩溃是合适的选择。
在 Netflix,我们只使用请求范围的折叠器,因为所有当前的系统都是建立在假设单个 HystrixRequestContext
将在每个命令中使用的基础上的。因为这些批只针对每个请求,所以当命令在同一个请求中同时发生且带有不同参数时,折叠是有效的。
下面是一个如何实现请求范围的 HystrixCollapser 的简单示例:
public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> {
private final Integer key;
public CommandCollapserGetValueForKey(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);
}
@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++));
}
}
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("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
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());
}
return response;
}
}
}
下面的单元测试展示了如何使用一个折叠器自动批处理 CommandCollapserGetValueForKey
的四个执行到一个单一的 HystrixCommand
执行:
@Test
public void testCollapser() throws Exception {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
Future<String> f1 = new CommandCollapserGetValueForKey(1).queue();
Future<String> f2 = new CommandCollapserGetValueForKey(2).queue();
Future<String> f3 = new CommandCollapserGetValueForKey(3).queue();
Future<String> f4 = new CommandCollapserGetValueForKey(4).queue();
assertEquals("ValueForKey: 1", f1.get());
assertEquals("ValueForKey: 2", f2.get());
assertEquals("ValueForKey: 3", f3.get());
assertEquals("ValueForKey: 4", f4.get());
// assert that the batch command 'GetValueForKey' was in fact
// executed and that it executed only once
assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size());
HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest().getExecutedCommands().toArray(new HystrixCommand<?>[1])[0];
// assert the command is the one we're expecting
assertEquals("GetValueForKey", command.getCommandKey().name());
// confirm that it was a COLLAPSED command execution
assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
// and that it was successful
assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
} finally {
context.shutdown();
}
}
13、Request Context Setup
要使用请求范围的特性(请求缓存、请求崩溃、请求日志),您必须管理 HystrixRequestContext
生命周期(或实现一个替代的 HystrixConcurrencyStrategy
)。
这意味着在请求之前必须执行以下操作:
HystrixRequestContext context = HystrixRequestContext.initializeContext();
然后这个在请求的最后:
context.shutdown();
在一个标准的 Java web 应用程序中,您可以使用Servlet 过滤器来初始化这个生命周期,方法是实现类似这样的过滤器:
public class HystrixRequestContextServletFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
}
您可以通过在 web.xml
中添加如下的部分来启用所有传入流量的过滤器:
<filter>
<display-name>HystrixRequestContextServletFilter</display-name>
<filter-name>HystrixRequestContextServletFilter</filter-name>
<filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HystrixRequestContextServletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
14、Common Patterns:
下面的章节是 HystrixCommand
和 HystrixObservableCommand
的常见用法和使用模式。
15、Fail Fast
最基本的执行是只做一件事,没有回退行为。如果发生任何类型的失败,它将抛出异常。
public class CommandThatFailsFast extends HystrixCommand<String> {
private final boolean throwException;
public CommandThatFailsFast(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "success";
}
}
这些单元测试显示了它的行为:
@Test
public void testSuccess() {
assertEquals("success", new CommandThatFailsFast(false).execute());
}
@Test
public void testFailure() {
try {
new CommandThatFailsFast(true).execute();
fail("we should have thrown an exception");
} catch (HystrixRuntimeException e) {
assertEquals("failure from CommandThatFailsFast", e.getCause().getMessage());
e.printStackTrace();
}
}
HystrixObservableCommand
等效:
HystrixObservableCommand
的等效快速失败解决方案包括覆盖 resumeWithFallback
方法,如下所示:
@Override
protected Observable<String> resumeWithFallback() {
if (throwException) {
return Observable.error(new Throwable("failure from CommandThatFailsFast"));
} else {
return Observable.just("success");
}
}
16、Fail Silent
静默失败等同于返回空响应或删除功能。它可以通过返回null、空映射、空列表或其他此类响应来实现。
你可以通过在 HystrixCommand
实例上实现 getFallback()
方法来做到这一点:
public class CommandThatFailsSilently extends HystrixCommand<String> {
private final boolean throwException;
public CommandThatFailsSilently(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "success";
}
}
@Override
protected String getFallback() {
return null;
}
}
@Test
public void testSuccess() {
assertEquals("success", new CommandThatFailsSilently(false).execute());
}
@Test
public void testFailure() {
try {
assertEquals(null, new CommandThatFailsSilently(true).execute());
} catch (HystrixRuntimeException e) {
fail("we should not get an exception as we fail silently with a fallback");
}
}
另一个返回空列表的实现如下所示:
@Override
protected List<String> getFallback() {
return Collections.emptyList();
}
HystrixObservableCommand
相应的操作:
HystrixObservableCommand
的等效失败静默解决方案包括覆盖 resumeWithFallback()
方法,如下:
@Override
protected Observable<String> resumeWithFallback() {
return Observable.empty();
}
17、Fallback: Static
回退可以返回静态嵌入在代码中的默认值。这不会像“fail silent”那样导致特性或服务被删除,而是导致默认行为的发生。
例如,如果一个命令根据用户凭据返回 true/false
,但命令执行失败,它可以默认为 true
:
@Override
protected Boolean getFallback() {
return true;
}
HystrixObservableCommand
相应的操作:
HystrixObservableCommand
的等效静态解决方案包括覆盖 resumeWithFallback
方法,如下所示:
@Override
protected Observable<Boolean> resumeWithFallback() {
return Observable.just( true );
}
18、Fallback: Stubbed
当命令返回包含多个字段的复合对象时,通常使用存根回退,其中一些字段可以从其他请求状态确定,而其他字段设置为默认值。
在这些存根值中,你可能会发现state适合使用的地方有:
- Cookie
- 请求参数和报头
- 来自当前服务请求失败之前的响应
你的回退可以从请求范围中静态地获取存根值,但通常建议在命令实例化时注入存根值,以便在需要时使用。下面的例子演示了如何处理 countryCodeFromGeoLookup
字段:
public class CommandWithStubbedFallback extends HystrixCommand<UserAccount> {
private final int customerId;
private final String countryCodeFromGeoLookup;
/**
* @param customerId
* The customerID to retrieve UserAccount for
* @param countryCodeFromGeoLookup
* The default country code from the HTTP request geo code lookup used for fallback.
*/
protected CommandWithStubbedFallback(int customerId, String countryCodeFromGeoLookup) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.customerId = customerId;
this.countryCodeFromGeoLookup = countryCodeFromGeoLookup;
}
@Override
protected UserAccount run() {
// fetch UserAccount from remote service
// return UserAccountClient.getAccount(customerId);
throw new RuntimeException("forcing failure for example");
}
@Override
protected UserAccount getFallback() {
/**
* Return stubbed fallback with some static defaults, placeholders,
* and an injected value 'countryCodeFromGeoLookup' that we'll use
* instead of what we would have retrieved from the remote service.
*/
return new UserAccount(customerId, "Unknown Name",
countryCodeFromGeoLookup, true, true, false);
}
public static class UserAccount {
private final int customerId;
private final String name;
private final String countryCode;
private final boolean isFeatureXPermitted;
private final boolean isFeatureYPermitted;
private final boolean isFeatureZPermitted;
UserAccount(int customerId, String name, String countryCode,
boolean isFeatureXPermitted,
boolean isFeatureYPermitted,
boolean isFeatureZPermitted) {
this.customerId = customerId;
this.name = name;
this.countryCode = countryCode;
this.isFeatureXPermitted = isFeatureXPermitted;
this.isFeatureYPermitted = isFeatureYPermitted;
this.isFeatureZPermitted = isFeatureZPermitted;
}
}
}
下面的单元测试演示了它的行为:
@Test
public void test() {
CommandWithStubbedFallback command = new CommandWithStubbedFallback(1234, "ca");
UserAccount account = command.execute();
assertTrue(command.isFailedExecution());
assertTrue(command.isResponseFromFallback());
assertEquals(1234, account.customerId);
assertEquals("ca", account.countryCode);
assertEquals(true, account.isFeatureXPermitted);
assertEquals(true, account.isFeatureYPermitted);
assertEquals(false, account.isFeatureZPermitted);
}
HystrixObservableCommand
相应的操作:
HystrixObservableCommand
的等效 stub 解决方案包括覆盖 resumeWithFallback
方法,以返回一个发出 stub响应的 Observable
。与前面例子等价的版本如下所示:
@Override
protected Observable<Boolean> resumeWithFallback() {
return Observable.just( new UserAccount(customerId, "Unknown Name",
countryCodeFromGeoLookup, true, true, false) );
}
但如果你希望从你的 Observable
对象中发射多个项目,你可能更感兴趣的是只为那些原始 Observable
在失败前还没有发射的项目生成存根。这里有一个简单的例子来展示你如何做到这一点——它会跟踪从主Observable发出的最后一项,这样回退就知道从哪里拾取来继续这个序列:
@Override
protected Observable<Integer> construct() {
return Observable.just(1, 2, 3)
.concatWith(Observable.<Integer> error(new RuntimeException("forced error")))
.doOnNext(new Action1<Integer>() {
@Override
public void call(Integer t1) {
lastSeen = t1;
}
})
.subscribeOn(Schedulers.computation());
}
@Override
protected Observable<Integer> resumeWithFallback() {
if (lastSeen < 4) {
return Observable.range(lastSeen + 1, 4 - lastSeen);
} else {
return Observable.empty();
}
}
19、Fallback: Cache via Network
有时,如果后端服务失败,可以从缓存服务(如memcached)检索过时的数据。
因为回退将通过网络,这是另一个可能的故障点,所以它也需要用 HystrixCommand
或HystrixObservableCommand
包装。
在单独的线程池上执行回退命令非常重要,否则,如果主命令成为潜在的并且填充了线程池,那么如果两个命令共享同一个线程池,就会阻止回退运行。
下面的代码显示了 CommandWithFallbackViaNetwork
如何在其 getFallback()
方法中执行FallbackViaNetwork
。
请注意,如果回退失败,它也有一个回退,它做“失败沉默”的方法返回null。
public class CommandWithFallbackViaNetwork extends HystrixCommand<String> {
private final int id;
protected CommandWithFallbackViaNetwork(int id) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueCommand")));
this.id = id;
}
@Override
protected String run() {
// RemoteServiceXClient.getValue(id);
throw new RuntimeException("force failure for example");
}
@Override
protected String getFallback() {
return new FallbackViaNetwork(id).execute();
}
private static class FallbackViaNetwork extends HystrixCommand<String> {
private final int id;
public FallbackViaNetwork(int id) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueFallbackCommand"))
// use a different threadpool for the fallback command
// so saturating the RemoteServiceX pool won't prevent
// fallbacks from executing
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback")));
this.id = id;
}
@Override
protected String run() {
MemCacheClient.getValue(id);
}
@Override
protected String getFallback() {
// the fallback also failed
// so this fallback-of-a-fallback will
// fail silently and return null
return null;
}
}
}
20、Primary + Secondary with Fallback
一些系统具有双模式行为 —— 主和辅助,或主和故障转移。
有时,辅助或故障转移被认为是一种故障状态,它仅用于后备;在这些场景中,它将符合上述“通过网络缓存”的相同模式。
然而,如果抛到辅助系统是常见的,如推出新代码的正常组成部分(有时这是有状态系统如何处理的一部分代码推)然后每次使用辅助系统主要处于故障状态,脱扣断路器和解雇警报。
这不是我们想要的行为,如果仅仅是为了避免“狼来了”的疲劳,那么当真正的问题发生时,警报就会被忽略。
因此,在这种情况下,我们的策略是将主次之间的切换视为正常、健康的模式,并在它们面前放置一个立面。
您不公开这两个命令,而是将它们隐藏在另一个与信号量隔离的 HystrixCommand
后面,该命令实现了调用主命令还是辅助命令的条件逻辑。如果主要的和次要的都发生故障,那么控制将切换到 facade
命令本身的回退。
门面 HystrixCommand
可以使用信号量隔离,因为它所做的所有工作都是通过另外两个已经是线程隔离的HystrixCommand
完成的。只要 facade 的 run()
方法不执行任何其他网络调用、重试逻辑或其他“容易出错”的事情,就没有必要再增加一层线程。
public class CommandFacadeWithPrimarySecondary extends HystrixCommand<String> {
private final static DynamicBooleanProperty usePrimary = DynamicPropertyFactory.getInstance().getBooleanProperty("primarySecondary.usePrimary", true);
private final int id;
public CommandFacadeWithPrimarySecondary(int id) {
super(Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
.andCommandKey(HystrixCommandKey.Factory.asKey("PrimarySecondaryCommand"))
.andCommandPropertiesDefaults(
// we want to default to semaphore-isolation since this wraps
// 2 others commands that are already thread isolated
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)));
this.id = id;
}
@Override
protected String run() {
if (usePrimary.get()) {
return new PrimaryCommand(id).execute();
} else {
return new SecondaryCommand(id).execute();
}
}
@Override
protected String getFallback() {
return "static-fallback-" + id;
}
@Override
protected String getCacheKey() {
return String.valueOf(id);
}
private static class PrimaryCommand extends HystrixCommand<String> {
private final int id;
private PrimaryCommand(int id) {
super(Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
.andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("PrimaryCommand"))
.andCommandPropertiesDefaults(
// we default to a 600ms timeout for primary
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(600)));
this.id = id;
}
@Override
protected String run() {
// perform expensive 'primary' service call
return "responseFromPrimary-" + id;
}
}
private static class SecondaryCommand extends HystrixCommand<String> {
private final int id;
private SecondaryCommand(int id) {
super(Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
.andCommandKey(HystrixCommandKey.Factory.asKey("SecondaryCommand"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("SecondaryCommand"))
.andCommandPropertiesDefaults(
// we default to a 100ms timeout for secondary
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100)));
this.id = id;
}
@Override
protected String run() {
// perform fast 'secondary' service call
return "responseFromSecondary-" + id;
}
}
public static class UnitTest {
@Test
public void testPrimary() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", true);
assertEquals("responseFromPrimary-20", new CommandFacadeWithPrimarySecondary(20).execute());
} finally {
context.shutdown();
ConfigurationManager.getConfigInstance().clear();
}
}
@Test
public void testSecondary() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", false);
assertEquals("responseFromSecondary-20", new CommandFacadeWithPrimarySecondary(20).execute());
} finally {
context.shutdown();
ConfigurationManager.getConfigInstance().clear();
}
}
}
}
21、Client Doesn’t Perform Network Access
当包装不执行网络访问,但存在延迟问题或线程开销不可接受的行为时,可以将 executionIsolationStrategy
属性设置为 executionIsolationStrategy.SEMAPHORE
和 Hystrix 将使用信号量隔离。
下面展示了如何通过代码将此属性设置为命令的默认属性(您也可以在运行时通过动态属性覆盖它)。
public class CommandUsingSemaphoreIsolation extends HystrixCommand<String> {
private final int id;
public CommandUsingSemaphoreIsolation(int id) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
// since we're doing an in-memory cache lookup we choose SEMAPHORE isolation
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)));
this.id = id;
}
@Override
protected String run() {
// a real implementation would retrieve data from in memory data structure
return "ValueFromHashMap_" + id;
}
}
22、Get-Set-Get with Request Cache Invalidation
如果你正在实现一个 Get-Set-Get
用例,在这个用例中,Get 接收到足够的请求缓存所需的流量,但有时在另一个命令上发生了一个集合,该集合应该使同一请求中的缓存失效,你可以通过调用HystrixRequestCache.clear()
来使缓存失效。
下面是一个示例实现:
public class CommandUsingRequestCacheInvalidation {
/* represents a remote data store */
private static volatile String prefixStoredOnRemoteDataStore = "ValueBeforeSet_";
public static class GetterCommand extends HystrixCommand<String> {
private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("GetterCommand");
private final int id;
public GetterCommand(int id) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetSetGet"))
.andCommandKey(GETTER_KEY));
this.id = id;
}
@Override
protected String run() {
return prefixStoredOnRemoteDataStore + id;
}
@Override
protected String getCacheKey() {
return String.valueOf(id);
}
/**
* Allow the cache to be flushed for this object.
*
* @param id
* argument that would normally be passed to the command
*/
public static void flushCache(int id) {
HystrixRequestCache.getInstance(GETTER_KEY,
HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(id));
}
}
public static class SetterCommand extends HystrixCommand<Void> {
private final int id;
private final String prefix;
public SetterCommand(int id, String prefix) {
super(HystrixCommandGroupKey.Factory.asKey("GetSetGet"));
this.id = id;
this.prefix = prefix;
}
@Override
protected Void run() {
// persist the value against the datastore
prefixStoredOnRemoteDataStore = prefix;
// flush the cache
GetterCommand.flushCache(id);
// no return value
return null;
}
}
}
确认该行为的单元测试是:
@Test
public void getGetSetGet() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
assertEquals("ValueBeforeSet_1", new GetterCommand(1).execute());
GetterCommand commandAgainstCache = new GetterCommand(1);
assertEquals("ValueBeforeSet_1", commandAgainstCache.execute());
// confirm it executed against cache the second time
assertTrue(commandAgainstCache.isResponseFromCache());
// set the new value
new SetterCommand(1, "ValueAfterSet_").execute();
// fetch it again
GetterCommand commandAfterSet = new GetterCommand(1);
// the getter should return with the new prefix, not the value from cache
assertFalse(commandAfterSet.isResponseFromCache());
assertEquals("ValueAfterSet_1", commandAfterSet.execute());
} finally {
context.shutdown();
}
}
}
23、Migrating a Library to Hystrix
当您迁移现有的客户端库以使用 Hystrix 时,您应该用 HystrixCommand
替换每个“服务方法”。
然后,服务方法应该将调用转发到 HystrixCommand
,其中不包含任何额外的业务逻辑。
因此,在迁移之前,服务库可能是这样的:
迁移之后,库的用户将能够通过委托给 HystrixCommands
的服务 facade 直接或间接地访问HystrixCommands
。
原文地址: