Hystrix是一個簡單易用的熔斷中間件弹渔,本篇文章會介紹下常規(guī)的使用方式。
目錄
- helloWorld初窺Hystrix
- HystrixCommand基本配置端壳、同步和異步執(zhí)行
- request cache的使用
- fallback
- default fallback
- 單級fallback
- 多級fallback
- 主次多HystrixCommand fallback
- 接入現(xiàn)有業(yè)務(wù)
- 總結(jié)
helloWorld初窺Hystrix
先貼代碼
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() {
return "Hello " + name + "!";
}
}
代碼很簡單,聲明一個類CommandHelloWorld
竟纳,集成HystrixCommand
, HystrixCommand攜帶泛型,泛型的類型就是我們的執(zhí)行方法run()
返回的結(jié)果的類型疚鲤。邏輯執(zhí)行體就是run方法的實現(xiàn)。
構(gòu)造方法至少要傳遞一個分組相關(guān)的配置給父類才能實現(xiàn)實例化缘挑,具體用來干什么的后面會描述集歇。
下面測試一下
public class CommandHelloWorldTest {
@Test
public void test_01(){
String result = new CommandHelloWorld("world").execute();
Assert.assertEquals("Hello world!",result);
}
}
就這樣第一個hellworld就跑起來,so easy
HystrixCommand基本配置语淘、同步和異步執(zhí)行
1.HystrixCommand诲宇、Group、ThreadPool 配置
Hystrix把執(zhí)行都包裝成一個HystrixCommand惶翻,并啟用線程池實現(xiàn)多個依賴執(zhí)行的隔離姑蓝。
上面的代碼集成了HystrixCommand并實現(xiàn)了類似分組key的構(gòu)造方法,那么分組是用來做什么呢吕粗?還有沒有其他類似的東西纺荧?怎么沒有看到線程配置呢?
Hystrix每個command都有對應(yīng)的commandKey可以認(rèn)為是command的名字颅筋,默認(rèn)是當(dāng)前類的名字getClass().getSimpleName()
,每個command也都一個歸屬的分組宙暇,這兩個東西主要方便Hystrix進行監(jiān)控、報警等议泵。
HystrixCommand使用的線程池也有線程池key占贫,以及對應(yīng)線程相關(guān)的配置
下面是代碼的實現(xiàn)方式
自定義command key
HystrixCommandKey.Factory.asKey("HelloWorld")
自定義command group
HystrixCommandGroupKey.Factory.asKey("ExampleGroup")
那么線程池呢?
HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")
Hystrix command配置有熔斷閥值先口,熔斷百分比等配置型奥,ThreadPoll有線程池大小瞳收,隊列大小等配置,如何設(shè)置厢汹?
Hystrix的配置可以通過Setter進行構(gòu)造
public CommandHelloWorld(){
super(Setter
//分組key
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("helloWorldGroup"))
//commandKey
.andCommandKey(HystrixCommandKey.Factory.asKey("commandHelloWorld"))
//command屬性配置
.andCommandPropertiesDefaults(HystrixPropertiesCommandDefault.Setter().withCircuitBreakerEnabled(true).withCircuitBreakerForceOpen(true))
//線程池key
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("helloWorld_Poll"))
//線程池屬性配置
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(20).withMaxQueueSize(25))
);
}
其它的詳細配置可參考https://github.com/Netflix/Hystrix/wiki/Configuration 后續(xù)也會整理對應(yīng)的配置介紹文章螟深。
2.HystrixCommand和分組、線程池三者的關(guān)系
commandKey分組內(nèi)唯一坑匠,HystrixCommand和分組血崭、線程池是多對1的關(guān)系。分組和線程池沒關(guān)系厘灼。
3.HystrixCommand如何執(zhí)行夹纫?同步?異步设凹?
同步
從helloWorld的例子可以看到舰讹,我們實例化了我們的HelloWorldCommand,調(diào)用了execute方法,從而執(zhí)行了command的Run()闪朱。這種是同步的執(zhí)行方式月匣。
異步執(zhí)行
在實際業(yè)務(wù)中,有時候我們會同時觸發(fā)多個業(yè)務(wù)依賴的調(diào)用奋姿,而這些業(yè)務(wù)又相互不依賴這時候很適合并行執(zhí)行锄开,我們可以使用Future方式,調(diào)用command的queue()方法称诗。
我們可以再寫一個helloWorld2
public class CommandHelloWorld2 extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld2(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
return "Hello " + name + "!";
}
}
具體異步調(diào)用
@Test
public void test_02() throws ExecutionException, InterruptedException {
Future<String> future1 = new CommandHelloWorld("world").queue();
Future<String> future2 = new CommandHelloWorld2("world").queue();
Assert.assertEquals("Hello world!",future1.get());
Assert.assertEquals("Hello world!",future2.get());
}
request cache的使用
先貼代碼
public class CachedCommand extends HystrixCommand<String> {
private String key;
private static final HystrixCommandKey COMMANDKEY = HystrixCommandKey.Factory.asKey("CachedCommand_cmd");
protected CachedCommand(String key){
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CachedCommand"))
.andCommandKey(COMMANDKEY));
this.key = key;
}
@Override
protected String getCacheKey() {
return this.key;
}
public static void flushCache(String key) {
HystrixRequestCache.getInstance(COMMANDKEY,
HystrixConcurrencyStrategyDefault.getInstance()).clear(key);
}
@Override
protected String run() throws Exception {
return "hello "+ key +" !";
}
}
Hystrix的cache萍悴,個人的理解就是在上下文中,多次請求同一個command寓免,返回值不會發(fā)生改變的時候可以使用癣诱。cache如果要生效,必須聲明上下文
HystrixRequestContext context = HystrixRequestContext.initializeContext();
....... command 的調(diào)用 ........
context.shutdown();
清緩存袜香,就是先獲得到command然后把對應(yīng)的key刪除
HystrixRequestCache.getInstance(COMMANDKEY,
HystrixConcurrencyStrategyDefault.getInstance()).clear(key);
接下來看下完整的調(diào)用
@Test
public void test_no_cache(){
HystrixRequestContext context = HystrixRequestContext.initializeContext();
String hahahah = "hahahah";
CachedCommand cachedCommand = new CachedCommand(hahahah);
Assert.assertEquals("hello hahahah !", cachedCommand.execute());
Assert.assertFalse(cachedCommand.isResponseFromCache());
CachedCommand cachedCommand2 = new CachedCommand(hahahah);
Assert.assertEquals("hello hahahah !", cachedCommand2.execute());
Assert.assertTrue(cachedCommand2.isResponseFromCache());
//清除緩存
CachedCommand.flushCache(hahahah);
CachedCommand cachedCommand3 = new CachedCommand(hahahah);
Assert.assertEquals("hello hahahah !", cachedCommand3.execute());
Assert.assertFalse(cachedCommand3.isResponseFromCache());
context.shutdown();
}
fallback
1.單個fallback
fallback就是當(dāng)HystrixCommand 執(zhí)行失敗的時候走的后備邏輯撕予,只要實現(xiàn)HystrixCommand 的fallback方法即可
public class CommandWithFallBack extends HystrixCommand<String> {
private final boolean throwException;
public CommandWithFallBack(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 "I'm fallback";
}
}
測試結(jié)果
@Test
public void testSuccess() {
assertEquals("success", new CommandWithFallBack(false).execute());
}
@Test
public void testFailure() {
try {
assertEquals("I'm fallback", new CommandWithFallBack(true).execute());
} catch (HystrixRuntimeException e) {
Assert.fail();
}
}
2.多級fallback
當(dāng)我們執(zhí)行業(yè)務(wù)的時候,有時候會有備用方案1蜈首、備用方案2实抡,當(dāng)備用方案1失敗的時候啟用備用方案2,所以可以使用多級fallback疾就。
多級fallback沒有名字那么神秘澜术,說到底其實就是HystrixCommand1執(zhí)行fallback1, fallback1的執(zhí)行嵌入HystrixCommand2,當(dāng)HystrixCommand2執(zhí)行失敗的時候猬腰,觸發(fā)HystrixCommand2的fallback2鸟废,以此循環(huán)下去實現(xiàn)多級fallback,暫未上限姑荷,只要你的方法棧撐的起盒延。
代碼實現(xiàn)
command1
public class CommandWithMultiFallBack1 extends HystrixCommand<String> {
private final boolean throwException;
public CommandWithMultiFallBack1(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 new CommandWithMultiFallBack2(true).execute();
}
}
command2
public class CommandWithMultiFallBack2 extends HystrixCommand<String> {
private final boolean throwException;
public CommandWithMultiFallBack2(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "I'm fallback1";
}
}
@Override
protected String getFallback() {
return "I'm fallback2";
}
調(diào)用測試
@Test
public void testMultiFailure(){
try {
assertEquals("I'm fallback2", new CommandWithMultiFallBack1(true).execute());
} catch (HystrixRuntimeException e) {
Assert.fail();
}
}
3.主次多HystrixCommand fallback
這里探討的是 主Command里串行執(zhí)行 多個Command時的fallback執(zhí)行邏輯
這里就不貼代碼了缩擂,fallback的跳轉(zhuǎn)也比較好理解,次command添寺,不管任何一個執(zhí)行失敗都認(rèn)為主command的run執(zhí)行失敗胯盯,進而進入主command的fallback
接入現(xiàn)有業(yè)務(wù)
上面的章節(jié)主要在解釋如何使用HystrixCommand,但是我們在開發(fā)中都已經(jīng)分好了各種業(yè)務(wù)的servie,如何套入這個Hystrix?
1.模擬業(yè)務(wù)場景
假設(shè)我們要加載商品詳情頁计露,需要加載商品信息博脑、用戶信息、店鋪信息
接入Hystrix前的代碼(代碼有點天真票罐,只是為了表述下意思)
//主功能類
public class GoodsService {
private UserService userService = new UserService();
private ShopService shopService = new ShopService();
/**
* 獲取商品詳情
* @return
*/
public GoodsDetailFrontModel getGoodsFrontDetail(){
GoodsDetailFrontModel goodsDetailFrontModel = new GoodsDetailFrontModel();
goodsDetailFrontModel.setTitle("這是一個測試商品");
goodsDetailFrontModel.setPrice(10000L);
UserModel userInfo = userService.getUserInfo(1000001L);
ShopModel shopInfo = shopService.getShopInfo(2001L);
goodsDetailFrontModel.setShopModel(shopInfo);
goodsDetailFrontModel.setUserModel(userInfo);
return goodsDetailFrontModel;
}
}
//依賴的用戶類
public class UserService {
/**
* 獲取用戶信息
* @param userId
* @return
*/
public UserModel getUserInfo(Long userId){
return new UserModel();
}
}
下面我們對用戶服務(wù)套入Hystrix叉趣,為了不侵入我們依賴的服務(wù),我們新建一個門戶類该押,包裝Hystrix相關(guān)的代碼
public class UserServiceFacade extends HystrixCommand<UserModel> {
//原業(yè)務(wù)service
private UserService userService = new UserService();
private Long userId;
protected UserServiceFacade() {
super(HystrixCommandGroupKey.Factory.asKey("UserServiceFacade"));
}
@Override
protected UserModel run() throws Exception {
return userService.getUserInfo(userId);
}
/**
*如果執(zhí)行失敗則返回游客身份
**/
@Override
protected UserModel getFallback() {
UserModel userModel = new UserModel();
userModel.setName("游客");
return userModel;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
然后我們再看下主執(zhí)行類
GoodsService
/**
* 獲取商品詳情
* @return
*/
public GoodsDetailFrontModel getGoodsFrontDetail(){
GoodsDetailFrontModel goodsDetailFrontModel = new GoodsDetailFrontModel();
goodsDetailFrontModel.setTitle("這是一個測試商品");
goodsDetailFrontModel.setPrice(10000L);
//原寫法
//UserModel userInfo = userService.getUserInfo(1000001L);
//這里替換成了調(diào)用用戶門面類
UserServiceFacade userServiceFacade = new UserServiceFacade();
userServiceFacade.setUserId(1000001L);
UserModel userInfo = userServiceFacade.execute();
ShopModel shopInfo = shopService.getShopInfo(2001L);
goodsDetailFrontModel.setShopModel(shopInfo);
goodsDetailFrontModel.setUserModel(userInfo);
return goodsDetailFrontModel;
}
上面的代碼提供一個套入的思路疗杉,官方原生的Hystrix就是這樣接入的,這里注意一點蚕礼,HystrixCommand每次執(zhí)行都需要new一個烟具,不能使用單例,一個command實例只能執(zhí)行一次
,上面的代碼也就是我們的userServiceFacade奠蹬,每次執(zhí)行都需要new一個新的對象朝聋。
總結(jié)
上面介紹了Hystrix的常規(guī)用法,也是我們公司目前的使用方式囤躁,官網(wǎng)還有HystrixObservableCommand的使用方式介紹玖翅,主要是rxjava的使用方式,獲取到observable可以進行更加靈活的處理割以,這里就不介紹了。
回顧下应媚,Hystrix能給我們帶來什么好處
1.多業(yè)務(wù)依賴隔離严沥,不會相互影響,并可以根據(jù)需要給不同的依賴分不同的線程資源
2.業(yè)務(wù)依賴fail-fast
3.依賴服務(wù)恢復(fù)中姜,能合理感知并恢復(fù)對服務(wù)的依賴
4.對依賴服務(wù)限流消玄,Hystrix對每個業(yè)務(wù)的依賴都包裝成了一個command,并分配線程池,線程池的容量也就是能下發(fā)請求的能力丢胚,防止雪崩
使用的介紹先到這里了翩瓜,后續(xù)大家有什么建議或者想法可以一起交流、碰撞携龟。
系列文章推薦
Hystrix熔斷框架介紹
Hystrix常用功能介紹
Hystrix執(zhí)行原理
Hystrix熔斷器執(zhí)行機制
Hystrix超時實現(xiàn)機制