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等效
使用HystrixObservableCommand而不是HystrixCommand的等效Hello World解决方案将涉及重写构造方法如下:
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());
}
}
同步执行
您可以与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应用. toblock (). tofuture ().get()来模拟execute的行为。
异步执行
可以使用queue()方法异步执行HystrixCommand,如下例所示:
Future<String> fs = new CommandHelloWorld("World").queue();
你可以使用将来时检索命令的结果:
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的队列,但是如果您知道这样的命令生成的Observable必须总是只生成一个值,那么您可以通过对Observable应用RxJava操作符. toblock (). tofuture()来模拟队列的行为。
被动的执行
您还可以使用以下方法之一来观察HystrixCommand的结果:
observe()
—返回一个立即执行该命令的“hot”可观察对象,但由于可观察对象是通过ReplaySubject筛选的,因此在您有机会订阅之前,不会有丢失它发出的任何项的危险
toObservable()
—返回一个“cold”可观察对象,它不会执行该命令并开始发送结果,直到您订阅了可观察对象
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();
})
被动的命令
您也可以创建一个HystrixObservableCommand,它是HystrixCommand的一个专门版本,用于包装可观察的对象,而不是使用上面描述的方法将一个hystrixobservxcommand转换为一个可观察的对象。HystrixObservableCommand能够包装发出多个项的可观察对象,而普通的hystrixcommand,即使转换为可观察对象,也不会发出多个项。
在这种情况下,与其用命令逻辑覆盖run方法(就像用普通的HystrixCommand那样),还不如覆盖construct方法,这样它就会返回要包装的Observable。
要获得HystrixObservableCommand的可观察表示,请使用以下两种方法之一:
observe()
—返回订阅底层可观察对象的“hot”可观察对象,但由于它是通过ReplaySubject筛选的,因此在您有机会订阅结果可观察对象之前,您不会有丢失它发出的任何项的危险
toObservable()
—返回一个“cold”可观察对象,该对象在订阅结果可观察对象之前不会订阅底层可观察对象
Fallback
通过添加一个回退方法,您可以在Hystrix命令中支持适当的降级,Hystrix将调用该回退方法来获取默认值或主命令失败时的值。您将希望为大多数可能会失败的Hystrix命令实现回退,但有几个例外:
1、执行写操作的命令
如果您的Hystrix命令被设计为执行写操作而不是返回值(对于HystrixCommand,这样的命令通常会返回一个空值,对于HystrixObservableCommand,则返回一个空的Observable),那么实现回退就没有多大意义了。如果写入失败,您可能希望将失败传播回调用方。
批处理系统/离线计算
如果您的Hystrix命令正在填充缓存、生成报告或执行任何类型的脱机计算,通常更合适的做法是将错误传播回调用方,然后调用方可以稍后重试该命令,而不是向调用方发送一个无声降级的响应。
无论您的命令是否有回退,都会更新所有常用的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方法,以便它返回第二个可观察的对象,如果失败,该对象将接管主可观察对象。请注意,由于可观察对象可能在发出一个或多个项之后失败,所以您的回退不应该假定它将发出观察者将看到的唯一值。
在内部,Hystrix使用RxJava onerrorrecoverenext操作符在发生错误时无缝地在主操作符和回退操作符之间转换。
误差传播
除了HystrixBadRequestException之外,从run()方法抛出的所有异常都算作失败,并触发getFallback()和断路器逻辑。
您可以包装想要在HystrixBadRequestException中抛出的异常,并通过getCause()检索它。HystrixBadRequestException用于报告非法参数或非系统故障等用例,这些用例不应计入故障指标,也不应触发回退逻辑。
HystrixObservableCommand等效
在HystrixObservableCommand的情况下,不可恢复的错误通过来自最终可观察对象的onError通知返回,而回退是通过退回到第二个可观察对象来完成的,这个可观察对象是Hystrix通过您实现的resumeWithFallback方法获得的。
执行异常类型
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 | j.l.RuntimeException | YES |
BAD_REQUEST | HystrixBadRequestException | underlying exception (user-controlled) | NO |
Command Name
默认情况下,命令名来自类名:
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是一个接口,可以实现为枚举或常规类,但它也有帮助工厂类来构造和实习实例,如:
HystrixCommandKey.Factory.asKey("HelloWorld")
Command Group
Hystrix使用命令组键将报告、警报、仪表板或团队/库所有权等命令分组在一起。
默认情况下,Hystrix使用它来定义命令线程池,除非定义了一个单独的线程池。
HystrixCommandGroupKey是一个接口,可以作为枚举或常规类实现,但它也有帮助工厂类来构造和实习实例,如:
HystrixCommandGroupKey.Factory.asKey("ExampleGroup")
Command Thread-Pool
线程池键表示用于监视、指标发布、缓存和其他此类用途的HystrixThreadPool。HystrixCommand与注入其中的HystrixThreadPoolKey检索到的单个HystrixThreadPool相关联,或者默认为使用创建它的HystrixCommandGroupKey创建的一个HystrixThreadPool。
要显式定义名称,请通过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是一个接口,可以作为枚举或常规类实现,但它也有帮助工厂类来构造和实习实例,如:
HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")
您可以使用HystrixThreadPoolKey而不仅仅是不同的HystrixCommandGroupKey的原因是多个命令可能属于相同的所有权或逻辑功能的“组”,但是某些命令可能需要彼此隔离。
下面是一个简单的例子:
- 用于访问视频元数据的两个命令
- 组名为“VideoMetadata”
- 命令A与资源1相反
- 命令B与资源2冲突
如果命令A变为潜伏的,并使其线程池饱和,则不应阻止命令B执行请求,因为它们每个都触及不同的后端资源。
因此,我们在逻辑上希望将这些命令分组在一起,但希望它们以不同的方式隔离,并将使用HystrixThreadPoolKey为每个命令提供不同的线程池。
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();
}
}
Request Collapsing
请求崩溃允许将多个请求批处理到单个HystrixCommand实例执行中。
折叠器可以使用批处理大小和创建批处理以来的时间作为执行批处理的触发器。
Hystrix支持两种类型的请求崩溃:请求范围的和全局范围的。这是在折叠器构造中配置的,默认为请求范围。
请求范围的折叠器为每个HystrixRequestContext收集批处理,而全局范围的折叠器为多个HystrixRequestContext收集批处理。因此,如果您的下游依赖项不能在单个命令调用中处理多个hystrixrequestcontext,那么请求范围的崩溃是正确的选择。
在Netflix,我们只使用请求范围的折叠器,因为所有当前系统都是建立在每个命令将使用单个HystrixRequestContext的假设之上的。由于批处理仅针对每个请求,所以当命令与同一请求中的不同参数同时出现时,崩溃是有效的。
下面是一个如何实现请求范围的hystrix折叠器的简单示例:
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;
}
}
}
下面的单元测试展示了如何使用一个折叠器来自动将commandsystsergetvalueforkey的四个执行批处理为一个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();
}
}
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>
Common Patterns
以下部分是HystrixCommand和HystrixObservableCommand的常用用法和使用模式。
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");
}
}
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();
}
Fallback: Static
回退可以静态地返回嵌入在代码中的默认值。这并不会像“fail silent”经常做的那样,导致特性或服务被删除,而是会导致默认行为的发生。
例如,如果一个命令基于用户凭证返回true/false,但是命令执行失败,它可以默认为true:
@Override
protected Boolean getFallback() {
return true;
}
HystrixObservableCommand等效
The equivalent Static solution for a HystrixObservableCommand
would involve overriding the resumeWithFallback
method as follows:
@Override
protected Observable<Boolean> resumeWithFallback() {
return Observable.just( true );
}
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的等效存根解决方案涉及重写resumeWithFallback方法,以返回发出存根响应的Observable。与前一个示例等价的版本如下:
@Override
protected Observable<Boolean> resumeWithFallback() {
return Observable.just( new UserAccount(customerId, "Unknown Name",
countryCodeFromGeoLookup, true, true, false) );
}
但是,如果您希望从您的可观察对象发出多个项,那么您可能更感兴趣的是仅为原始可观察对象在失败之前尚未发出的那些项生成存根。下面是一个简单的例子,展示了如何实现这一点——它跟踪从主可观察对象发出的最后一项,以便回退知道从哪里继续该序列:
@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();
}
}
Fallback: Cache via Network
有时,如果后端服务失败,可以从memcached等缓存服务检索过时的数据版本。
由于回退将通过网络,它是另一个可能的故障点,因此还需要用HystrixCommand或HystrixObservableCommand包装它。
881/5000
在单独的线程池上执行回退命令很重要,否则,如果主命令变为潜伏的并填充线程池,那么如果两个命令共享同一个线程池,将阻止回退运行。
下面的代码显示CommandWithFallbackViaNetwork如何在其getFallback()方法中执行FallbackViaNetwork。
请注意,如果回退失败,它还有一个回退,该回退执行返回null的“fail silent”方法。
要将FallbackViaNetwork命令配置为运行在不同于从HystrixCommandGroupKey派生的默认RemoteServiceX的线程池上,它将HystrixThreadPoolKey.Factory.asKey(“RemoteServiceXFallback”)注入构造函数中。
这意味着CommandWithFallbackViaNetwork将运行在名为RemoteServiceX的线程池上,而FallbackViaNetwork将运行在名为RemoteServiceXFallback的线程池上。
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;
}
}
}
一级+二级+后退
有些系统具有双模式行为——主模式和辅助模式,或者主模式和故障转移模式。
有时次要故障转移或故障转移被视为故障状态,仅用于回退;在这些场景中,它将符合与上面描述的“通过网络缓存”相同的模式。
然而,如果抛到辅助系统是常见的,如推出新代码的正常组成部分(有时这是有状态系统如何处理的一部分代码推)然后每次使用辅助系统主要处于故障状态,脱扣断路器和解雇警报。
如果只是为了避免“喊狼来了”的疲劳(当真正的问题发生时,这种疲劳会导致警报被忽略),那么这不是理想的行为。
因此,在这种情况下,我们的策略是将主模式和次模式之间的切换视为正常、健康的模式,并在它们前面放置一个门面。
主要和次要的HystrixCommand实现是线程隔离的,因为它们执行的是网络流量和业务逻辑。它们可能具有非常不同的性能特征(通常辅助系统是静态缓存),因此针对它们的单独命令的另一个好处是可以对它们进行单独调优。
您不公开这两个命令,而是将它们隐藏在另一个HystrixCommand后面,该命令是信号量隔离的,它实现了调用主命令还是辅助命令的条件逻辑。如果主命令和次命令都失败,那么控制将切换到facade命令本身的回退。
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();
}
}
}
}
客户端不执行网络访问
当您包装不执行网络访问的行为,但延迟是一个问题或线程开销是不可接受的,您可以将executionIsolationStrategy属性设置为executionIsolationStrategy。信号量和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;
}
}
具有请求缓存无效的Get-Set-Get
如果您正在实现一个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();
}
}
}
将库迁移到Hystrix
当您迁移现有的客户端库以使用Hystrix时,您应该使用HystrixCommand替换每个“服务方法”。
然后,服务方法应该转发对HystrixCommand的调用,其中不包含任何额外的业务逻辑。
因此,在迁移之前,服务库可能是这样的:
迁移之后,库的用户将能够通过委托给HystrixCommands的服务facade直接或间接地访问HystrixCommands。