Netflix | 【翻譯】Hystrix文件-如何使用
文章目錄
“Hello World”
下面的列子展示了 HystrixCommand
的一個基本實現:
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
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() {
// 在真實世界,run() 方法可能會產生一些網路請求等
return "Hello " + name + "!";
}
}
HystrixObservableCommand
的等價版本
這個等價版本和 HystrixCommand
的不同包括過載構造器等,程式碼如下:
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixObservableCommand;
import rx.Observable;
import rx.Subscriber;
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()) {
// 在真實世界,run() 方法可能會產生一些網路請求等
observer.onNext("Hello");
observer.onNext(name + "!");
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
});
}
}
譯註:HystrixObservableCommand 依賴 RxJava。
同步執行
通過呼叫 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
來說,沒有一種簡單方法來實現 execute
,但如果你能確定 HystrixObservableCommand
內部的 Observable
永遠只返回單個值(譯註:即 onNext()
方法至多隻會被呼叫一次),那麼你可以通過對 HystrixObservableCommand
內部的 Observable
呼叫其 .toBlocking().toFuture().get()
來模擬 execute()
的行為。
非同步執行
通過呼叫 queue()
方法,可以以非同步的方式執行 HystrixCommand
,示例如下:
Future<String> fs = new CommandHelloWorld("World").queue();
通過對返回的 Future
呼叫 get
方法來獲取結果:
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
來說,沒有一種簡單方法來實現 execute
,但如果你能確定 HystrixObservableCommand
內部的 Observable
永遠只返回單個值,那麼你可以通過對 HystrixObservableCommand
內部的 Observable
呼叫其 RxJava
提供的 .toBlocking().toFuture()
方法來模擬 queue()
的行為。
Reactive模式執行
你也可以將 HystrixCommand
當作一個 Observable
來使用觀察者模式獲得結果,呼叫方式如下:
observe()
—— 返回一個 Hot Observable,這個命令將在呼叫observe()
方法時被立即執行。你不用擔心命令在返回Observable
時被執行而無法觀察/訂閱到結果,因為這個 Observable 內部在每次有新的 Subscriber 訂閱時會重放 Observable 的行為。toObservable()
—— 返回一個 Cold Observable,呼叫完toObservable()
方法之後命令不會立即被執行,直到有 Subscriber 訂閱了這個 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();
// 阻塞模式
assertEquals("Hello World!", fWorld.toBlockingObservable().single());
assertEquals("Hello Bob!", fBob.toBlockingObservable().single());
// 非阻塞模式
// - 匿名內部類形式,本測試不做任何斷言
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);
}
});
// 非阻塞模式
// - 同樣是匿名內部類形式,忽略“異常”和“完成”回撥
fBob.subscribe(new Action1<String>() {
@Override
public void call(String v) {
System.out.println("onNext: " + v);
}
});
}
如果你使用 Java 8 的 Lambda表示式/閉包,程式碼會更加緊湊,如下所示:
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
響應式(Reactive)命令
通過上述命令,可以將一個 HystrixCommand
轉換成 Observable
,但使用 HystrixObservableCommand
這個 HystrixCommand
的特殊版本是一個更好的選擇。HystrixObservableCommand
可以封裝一個能發射(譯註:意指 Subscriber 的 onNext() 能被呼叫多次)多次的 Observable
,而將普通的 HystrixCommand
轉成的 Observable
則最多隻能發射一次。
如果使用 HystrixObservableCommand
,你應該將你的命令邏輯放置在過載的 construct
方法中而不是 run
方法中(就像使用普通 HystrixCommand
一樣),這樣 Hystrix 才能將你的命令邏輯包裝到 Observable 中。
通過如下兩種方式,可以從 HystrixObservableCommand
得到 Observable
:
observe()
—— 返回一個“熱” Observable,這個方法內部會立即訂閱底層 Observable,但你不用擔心在這個方法返回 Observable 再去訂閱會不會丟資料,因為這個方法使用了ReplaySubject
去訂閱 Observable。即這個方法會讓命令邏輯立即執行。toObservable()
—— 返回一個“冷” Observable,這個方法內部不會訂閱底層的 Observable,即除非你去訂閱返回的 Observable,否則命令邏輯不會執行。
失敗回退(Fallback)
通過提供一個失敗回退的方法(fallback 方法),Hystrix 在主命令邏輯發生異常時能從這個方法中得到一個預設值或者一些資料作為命令的返回值,從而實現優雅的服務降級。當然,你可能會給所有可能失敗的命令增加失敗回退方法,但也有如下一些例外:
-
寫操作
如果一個 Hystrix 命令用於寫操作而不是返回一個值(例如一個返回void
的HystrixCommand
或者一個返回空 Observable 的 HystrixObservableCommand),失敗回退邏輯並沒有多大的意義。一般如果寫失敗,應該向上傳播至命令的呼叫者。 -
批處理/離線計算
如果你編寫的 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
方法以返回一個執行失敗回退邏輯的 Observable,這樣當失敗時,Hystrix 會通知所有 Subscriber 這個 Observable 的執行結果。因為 Observable 可能會發射多次,並且可能在發射多次之後發生失敗,因此,你的失敗回退邏輯並不能保證所有的 Subscriber 只接收到失敗回退 Observable 返回的資料。
在發生失敗時,Hystrix 在內部使用 RxJava 的 onErrorResumeNext
操作符在主 Observable 和失敗回退 Observable 之間進行無縫轉化。
錯誤傳播
run()
方法中丟擲的所有異常,除開 HystrixBadRequestException
之外,均會被算作命令執行失敗而觸發 getFallback()
方法的呼叫和熔斷器邏輯。
你可以通過將異常放進 HystrixBadRequestException
並通過 getCause()
獲取真實的異常,而不會觸發失敗回退。HystrixBadRequestException
專門用來處理這種不應該算作命令執行失敗,並且不應該觸發熔斷器的場景,例如向呼叫者報告引數不合法或者非系統異常等。
HystrixObservableCommand
的等價版本
若使用 HystrixObservableCommand
,不可恢復的錯誤將由主 Observable 通過 onError
來報告,而失敗回退邏輯則是由 Hystrix 從 resumeWithFallback
方法中得到的另一個 Observable 來報告。
命令名稱
預設情況下,命令的名稱由類名決定:
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 內部類以構造並 intern Key 物件:
HystrixCommandKey.Factory.asKey("HelloWorld")
命令組
Hystrix 提供命令組機制,通過使用相同命令組 Key,來統一一組命令的報表、告警、儀表盤或組/庫的所有權。
預設情況下,Hystrix 會讓一組命令使用統一的執行緒池,除非手工指定執行緒池的劃分。
HystrixCommandGroupKey
是一個介面,並能被實現為列舉型別或者普通類,但其仍然會保留 Factory 內部類以構造並 intern Key 物件:
HystrixCommandGroupKey.Factory.asKey("ExampleGroup")
命令執行緒池
Hystrix 使用執行緒池 Key 來表示一個 HystrixThreadPool
,以實現監控、監控指標上報、快取和其他用途中執行緒池的區分。一個 HystrixCommand
與一個通過 HystrixThreadPoolKey
獲得的執行緒池相關聯,該執行緒池會被注入到這個命令中。如果沒有指定執行緒池 Key,命令則預設會和一個通過 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 內部類以構造並 intern Key 物件:
HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")
儘量使用 HystrixThreadPoolKey
而不是通過指定另一個 HystrixCommandGroupKey
的原因是:多個命令可能從所有權或邏輯功能上來看屬於同一個組,但某些命令可能需要和其他命令隔離開來。
例如:
- 有兩個用來訪問視訊元資料的命令
- 組名為 “VideoMetadata”
- 命令A 訪問 資源#1
- 命令B 訪問 資源#2
如果 命令A 出現延遲並耗盡其關聯的執行緒池,其不應該影響 命令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);
}
}
因其依賴於請求上下文,因此在使用請求快取之前,需要初始化 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
中初始化上下文或者關閉上下文,以包裝所有的請求或其他 Servlet 帶生命週期的鉤子方法。
下面的例子演示了命令如何從快取中獲取結果(以及如何判斷結果是否從快取中獲取):
@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());
// 這是第二次執行命令,結果“2”命中快取
assertTrue(command2b.isResponseFromCache());
} finally {
context.shutdown();
}
// 建立一個新的上下文(快取為空)
context = HystrixRequestContext.initializeContext();
try {
CommandUsingRequestCache command3b = new CommandUsingRequestCache(