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。