以下是一个最基本的”Hello World”实现,基于HystrixCommand
:
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 + "!";
}
}
一个等价的Hello Wrold实现是使用HystrixObservableCommand
代替HystrixCommand
重构代码如下:
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 construct() {
return Observable.create(new Observable.OnSubscribe() {
@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());
}
}
你可以执行HystrixCommand
的同步方法execute(),如下所示:
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
没有简单的对等的execute
,但是你知道HystrixObservableCommand
的调用总会得到一个Observable
,因此你可以通过Observable
调用.toBlocking().toFuture().get()
来模仿execute
发生。
你可以使用HystrixCommand的queue() 方法来异步执行。如下例所示:
Future 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 fWorld = new CommandHelloWorld("World").queue();
Future 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
调用.toBlocking().toFuture()
模仿queue
行为。
你还可以使用如下方法将HystrixCommand
的结果,作为Observable
进行观察:
observe() —— 返回一个’hot’Observable
它会立即执行,即使Observable通过ReplaySubject进行过滤,在您有机会订阅之前,您不会丢失它所发出的任何项目
toObservable() —— 返回一个’cold’Observable
,不会立即执行,直到你订阅Observable
才会开始发布结果 。
Observable ho = new CommandHelloWorld("World").observe();
// or Observable co = new CommandHelloWorld("World").toObservable()
然后,通过订阅Observable来检索命令的值:
ho.subscribe(new Action1() {
@Override
public void call(String s) {
// value emitted here
}
});
通过单元测试进行演示:
@Test
public void testObservable() throws Exception {
Observable fWorld = new CommandHelloWorld("World").observe();
Observable fBob = new CommandHelloWorld("Bob").observe();
// 阻塞
assertEquals("Hello World!", fWorld.toBlockingObservable().single());
assertEquals("Hello Bob!", fBob.toBlockingObservable().single());
// 非阻塞
// - 这是一个冗余的匿名内部类,不主张推荐
fWorld.subscribe(new Observer() {
@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);
}
});
// 非足赛
// - 也是使用匿名内部类
// - 忽视 errors 和 onCompleted 信号
fBob.subscribe(new Action1() {
@Override
public void call(String v) {
System.out.println("onNext: " + v);
}
});
}
使用JAVA 8 lambdas/closures 更紧凑,如下所示:
fWorld.subscribe((v) -> {
System.out.println("onNext: " + v);
})
// - 可以包含错误处理
fWorld.subscribe((v) -> {
System.out.println("onNext: " + v);
}, (exception) -> {
exception.printStackTrace();
})
有关Observable的更多信息,请访问:
http://reactivex.io/documentation/observable.html
您可以创建一个HystrixObservableCommand
,它是HystrixCommand
的专用版本,用于包装Observables
,而不是使用上述方法将HystrixCommand
转换为Observable
。HystrixObservableCommand
能够包装发出多个项目的Observable
,而普通的HystrixCommands
,即使转换为Observables
,也不会发出多个项目。
在这种情况下,不要使用命令逻辑覆盖run
方法(就像使用普通的HystrixCommand
一样),而是覆盖construct
方法,以便返回要包装的Observable
。
获取HystrixObservableCommand的Observable,可以使用下面两种方法之一:
observe() —— 返回一个’hot’Observable
它会立即订阅底层的Observable,即使Observable通过ReplaySubject进行过滤,在您有机会订阅之前,您不会丢失它所发出的任何项目
toObservable() —— 返回一个’cold’Observable
,不会立即订阅底层的Observable,直到你订阅Observable
才会开始发布结果 。
您可以通过在Hystrix
添加将调用的回退方法来支持Hystrix
命令中的优雅降级,以便在主命令失败时获取默认值。您可能希望为大多数可能导致失败的Hystrix
命令实现回退,但有几个例外:
执行写操作的命令
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 + "!";
}
}
View Source
每次执行时,此命令的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
接替,请注意,因为Observabl
e在已经发出一个或多个项目后可能会失败,所以您的fallback不应该假设它将发出观察者将看到的唯一值。
在内部,Hystrix
使用RxJava
onErrorResumeNext运算符在发生错误时在主要和fallback Observable
之间无缝过渡。
sequence diagram演示如何超时,然后回退工作。
### Error Propagation(错误传播)
从run()
方法抛出的所有异常除了HystrixBadRequestException都计为失败并触发getFallback()
和断路器逻辑。
您可以在HystrixBadRequestException
中包装要抛出的异常并通过getCause()
检索它.HystrixBadRequestException
适用于报告非法参数或非系统故障等用例,这些故障不应计入故障指标,也不应触发回退逻辑。
HystrixObservableCommand 类似
在使用HystrixObservableCommand
时,通过来自生成的Observable的onError
通知返回不可恢复的错误,并通过回退到Hystrix
通过您实现的resumeWithFallback
方法获得的第二个Observable
来完成回退逻辑。
Execution Exception types(执行异常类型)
Failure Type | Exception class | Exception.cause(异常原因) | subject to fallback(触发回退) |
---|---|---|---|
FAILURE | HystrixRuntimeException | 底层exception(用户控制) | 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 | 底层exception(用户控制) | No |
注意:j.u.c 代表并发异常,j.l代表java.lang包的异常。
默认情况下,命令名称是从类名派生的:
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是一个接口,可以实现为枚举或常规类,但是它必须借助工厂Factory来进行构造实例,例如:
HystrixCommandKey.Factory.asKey("HelloWorld")
Hystrix
使用command group key将命令组合在一起,例如报告,警报,仪表板或团队/库所有权.
默认情况下,Hystrix
使用内部定义命令线程池(Command Thread-Pool),除非定义了你自定义线程池。
HystrixCommandKey是一个接口,可以实现为枚举或常规类,但是它必须借助工厂Factory来进行构造实例,例如:
HystrixCommandGroupKey.Factory.asKey("ExampleGroup")
Thread-Pool key 代表一个HystrixThreadPool主要用于监视,指标发布,缓存和其他此类用途。HystrixCommand
与注入其中的HystrixThreadPoolKey
检索到的单个HystrixThreadPool
相关联,或者它使用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是一个接口,可以实现为枚举或常规类,但是它必须借助工厂Factory来进行构造实例,例如:
HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")
之所以使用HystrixThreadPoolKey
代替使用不同的HystrixCommandGroupKey
的原因是多个命令可能属于同一“所有权或逻辑功能”组,但某些命令可能需要彼此隔离。
这里有一些简单的例子:
如果command A变得潜伏并使其线程池(thread-pool)饱和,它不应该阻止如果command B执行请求,因为它们各自命中不同的后端资源。
因此,我们逻辑上希望将这些命令组合在一起,但希望它们以不同方式隔离,并使用HystrixThreadPoolKey
为它们中的每一个提供不同的线程池。
您可以通过在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);
}
}
View Source
由于这依赖于请求上下文,我们必须初始化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());
// 第一次执行,缓存中不存在
assertFalse(command2a.isResponseFromCache());
assertTrue(command2b.execute());
// 第二次执行,直接从缓存中获取
assertTrue(command2b.isResponseFromCache());
} finally {
context.shutdown();
}
// 启动一个新的上下文
context = HystrixRequestContext.initializeContext();
try {
CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);
assertTrue(command3b.execute());
//由于是一个新的请求,不会走缓存。
assertFalse(command3b.isResponseFromCache());
} finally {
context.shutdown();
}
}
请求压缩开启后,允许将多个请求批处理到单个HystrixCommand
实例中执行。
一个压缩可以使用批次大小和自创建批次以来经过的时间来触发一个批处理的执行。
Hystrix
支持两种请求压缩样式:请求范围和全局范围。这是在collapser构造中配置的,默认为请求范围(request-scoped
)。
请求范围的压缩通过每个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> createCommand(final Collection> requests) {
return new BatchCommand(requests);
}
@Override
protected void mapResponseToRequests(List batchResponse, Collection> requests) {
int count = 0;
for (CollapsedRequest request : requests) {
request.setResponse(batchResponse.get(count++));
}
}
private static final class BatchCommand extends HystrixCommand<List<String>> {
private final Collection> requests;
private BatchCommand(Collection> requests) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
this.requests = requests;
}
@Override
protected List run() {
ArrayList response = new ArrayList();
for (CollapsedRequest request : requests) {
// 对批次中收到的每个参数进行人工响应
response.add("ValueForKey: " + request.getArgument());
}
return response;
}
}
}
View Source
以下单元测试显示如何使用collapser自动将CommandCollapserGetValueForKey
的四次执行批量处理为单个HystrixCommand
执行:
@Test
public void testCollapser() throws Exception {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
Future f1 = new CommandCollapserGetValueForKey(1).queue();
Future f2 = new CommandCollapserGetValueForKey(2).queue();
Future f3 = new CommandCollapserGetValueForKey(3).queue();
Future 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();
}
}
使用请求范围的功能(请求缓存,请求压缩,请求日志)您必须管理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>HystrixRequestContextServletFilterdisplay-name>
<filter-name>HystrixRequestContextServletFilterfilter-name>
<filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HystrixRequestContextServletFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
以下部分是HystrixCommand
和HystrixObservableCommand
的常用用法和使用模式。
最基本的执行是执行单一操作并且没有回退行为发生,如果发生任何类型的故障,它将抛出异常。
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";
}
}
}
View Source
这些单元测试显示了它的行为方式:
@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
的等效Fail-Fast
解决方案将涉及重写resumeWithFallback
方法,如下所示:
@Override
protected Observable resumeWithFallback() {
if (throwException) {
return Observable.error(new Throwable("failure from CommandThatFailsFast"));
} else {
return Observable.just("success");
}
}
静默失败相当于返回空响应或删除功能,它可以通过返回null,空Map,空List或其他此类响应来完成。
您可以通过在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;
}
}
View Source
@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 getFallback() {
return Collections.emptyList();
}
HystrixObservableCommand Equivalent
HystrixObservableCommand
的等效Fail-Silently
解决方案将涉及重写resumeWithFallback()方法,如下所示:
@Override
protected Observable resumeWithFallback() {
return Observable.empty();
}
回退可以返回静态嵌入代码中的默认值。这不会导致以“fail silent”的方式删除功能或服务,相反会导致默认行为发生。
例如,如果命令根据用户凭据返回true / false,但命令执行失败,则默认为true:
@Override
protected Boolean getFallback() {
return true;
}
HystrixObservableCommand Equivalent
HystrixObservableCommand
的等效静态解决方案将涉及重写resumeWithFallback
方法,如下所示:
@Override
protected Observable resumeWithFallback() {
return Observable.just( true );
}
当命令返回包含多个字段的复合对象时,通常使用存根回退,其中一些可以从其他请求状态确定,而其他字段设置为默认值。
在你可能觉得适合的地方使用这些存根值的示例:
您的回退可以从请求范围静态检索存根值,但通常建议在命令实例化时注入它们以便在需要时使用,例如下面的示例演示了它处理countryCodeFromGeoLookup
字段的方式:
ublic class CommandWithStubbedFallback extends HystrixCommand {
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;
}
}
}
View Source
以下单元测试演示了其行为:
@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 Equivalent
HystrixObservableCommand
的等效Stubbed
解决方案将涉及重写resumeWithFallback
方法以返回发出存根响应的Observable
。与前一个示例等效的版本如下所示:
@Override
protected Observable resumeWithFallback() {
return Observable.just( new UserAccount(customerId, "Unknown Name",
countryCodeFromGeoLookup, true, true, false) );
}
但是如果您希望从Observable中发出多个项目,那么您可能更感兴趣的是仅为原始Observable
在失败之前尚未发出的项目生成存根。 下面是一个简单的示例,说明如何实现此目的 - 它跟踪主Observable
发出的最后一项,以便fallback知道在哪里拾取以继续序列:
@Override
protected Observable construct() {
return Observable.just(1, 2, 3)
.concatWith(Observable. error(new RuntimeException("forced error")))
.doOnNext(new Action1() {
@Override
public void call(Integer t1) {
lastSeen = t1;
}
})
.subscribeOn(Schedulers.computation());
}
@Override
protected Observable resumeWithFallback() {
if (lastSeen < 4) {
return Observable.range(lastSeen + 1, 4 - lastSeen);
} else {
return Observable.empty();
}
}
有时,如果后端服务失败,则可以从缓存服务(例如memcached
)中检索过时的数据版本。
由于回退将通过网络,这是另一个可能的失败点,因此它也需要由HystrixCommand
或HystrixObservableCommand
包装.
在单独的线程池上执行fallback
命令很重要。否则,如果主命令变为潜在并填充线程池,如果两个命令共享同一个池,这将阻止fallback
运行。
以下代码显示了CommandWithFallbackViaNetwork
如何在其getFallback()
方法中执行FallbackViaNetwork
。
请注意如果fallback
失败,它还有一个fallback
,它执行返回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;
}
}
}
View Source
某些系统具有双模行为 - 主和副,或主要和故障转移。
有时,次要或故障转移被视为失败状态,仅适用于fallback;在那些场景中,它或许适用于类似上面提到的 “Cache via Network”
但是,如果切换到副系统是常见的,例如推出新代码的正常部分(有时这是有状态系统如何处理代码推送的一部分)那么每次使用副系统时,主服务器将处于故障状态 ,断路器短路和发射警报。
这不是理想的行为,如果没有其他原因,除了避免“cry wolf”疲劳,这将导致在发生真正问题时忽略警报。
所以在这种情况下,策略是将主要和次要之间的转换视为正常,健康的模式,并在它们面前放置一个外观。
主要和次要HystrixCommand
实现是线程隔离的,因为它们正在执行网络拦截和业务逻辑,它们可能各自具有非常不同的性能特征(通常,辅助系统是静态缓存)所以每个单独命令的另一个好处是它们可以单独调整。
您不需要暴露这两个命令,而是将它们隐藏在另一个信号量隔离的HystrixCommand
后面,并实现关于是否调用主命令或辅助命令的条件逻辑,如果主要和次要失败,则控制切换到façade
命令本身的façade。
外观HystrixCommand
可以使用信号量隔离,因为它正在做的所有工作都是通过另外两个已经线程隔离的HystrixCommands
来完成。只要façade的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();
}
}
}
}
View Source
当您包装不执行网络访问的行为时,但是延迟是一个问题或者线程开销是不可接受的时,您可以将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;
}
}
View Source
如果您正在实现Get-Set-Get用例,其中Get接收到足以请求缓存的流量,但有时会在另一个命令上发生Set,该命令应该使同一请求中的缓存无效,你可以调用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;
}
}
}
View Source
确认行为的单元测试是:
@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();
}
}
在迁移现有客户端库以使用Hystri
x时,应使用HystrixCommand
替换每个“service methods”。
服务方法应该将调用转发到HystrixCommand
,并且不会在其中包含任何其他业务逻辑。
因此,在迁移之前,服务库可能如下所示:
迁移后,库的用户将能够通过委派给·HystrixCommands·的服务外观直接或间接访问HystrixCommands
。