摘要:為每個依賴提供一個小的線程池或信號,如果線程池已滿調(diào)用將被立即拒絕,默認不采用排隊加速失敗判定時間。
最近小主看到很多公眾號都在發(fā)布Hystrix停更的文章,spring cloud體系的使用者和擁護者一片哀嚎,實際上,spring作為Java最大的家族,根本不需要擔心其中一兩個零件的廢棄,Hystrix的停更,只會催生更多或者更好的零件來替代它,因此,我們需要做的是:**知道Hystrix是干嘛,怎么用的,這樣要找替代者就易于反掌了。
1. 為什么需要Hystrix?文章提綱:
為什么需要Hystrix?
Hystrix如何解決依賴隔離
如何使用Hystrix
在大中型分布式系統(tǒng)中,通常系統(tǒng)很多依賴(HTTP,hession,Netty,Dubbo等),如下圖:
在高并發(fā)訪問下,這些依賴的穩(wěn)定性與否對系統(tǒng)的影響非常大,但是依賴有很多不可控問題:如網(wǎng)絡(luò)連接緩慢,資源繁忙,暫時不可用,服務(wù)脫機等.
如下圖:QPS為50的依賴 I 出現(xiàn)不可用,但是其他依賴仍然可用.
當依賴I 阻塞時,大多數(shù)服務(wù)器的線程池就出現(xiàn)阻塞(BLOCK),影響整個線上服務(wù)的穩(wěn)定性.如下圖:
在復(fù)雜的分布式架構(gòu)的應(yīng)用程序有很多的依賴,都會不可避免地在某些時候失敗。高并發(fā)的依賴失敗時如果沒有隔離措施,當前應(yīng)用服務(wù)就有被拖垮的風(fēng)險。
例如:一個依賴30個SOA服務(wù)的系統(tǒng),每個服務(wù)99.99%可用。 99.99%的30次方 ≈ 99.7% 0.3% 意味著一億次請求 會有 3,000,00次失敗 換算成時間大約每月有2個小時服務(wù)不穩(wěn)定. 隨著服務(wù)依賴數(shù)量的變多,服務(wù)不穩(wěn)定的概率會成指數(shù)性提高.
解決問題方案:對依賴做隔離,Hystrix就是處理依賴隔離的框架,同時也是可以幫我們做依賴服務(wù)的治理和監(jiān)控.
Netflix 公司開發(fā)并成功使用Hystrix,使用規(guī)模如下:
he Netflix API processes 10+ billion HystrixCommand executions per day using thread isolation. Each API instance has 40+ thread-pools with 5-20 threads in each (most are set to 10).2. Hystrix如何解決依賴隔離
Hystrix使用命令模式HystrixCommand(Command)包裝依賴調(diào)用邏輯,每個命令在多帶帶線程中/信號授權(quán)下執(zhí)行。
可配置依賴調(diào)用超時時間,超時時間一般設(shè)為比99.5%平均時間略高即可.當調(diào)用超時時,直接返回或執(zhí)行fallback邏輯。
為每個依賴提供一個小的線程池(或信號),如果線程池已滿調(diào)用將被立即拒絕,默認不采用排隊.加速失敗判定時間。
依賴調(diào)用結(jié)果分:成功,失敗(拋出異常),超時,線程拒絕,短路。 請求失敗(異常,拒絕,超時,短路)時執(zhí)行fallback(降級)邏輯。
提供熔斷器組件,可以自動運行或手動調(diào)用,停止當前依賴一段時間(10秒),熔斷器默認錯誤率閾值為50%,超過將自動運行。
提供近實時依賴的統(tǒng)計和監(jiān)控
Hystrix依賴的隔離架構(gòu),如下圖:
使用maven引入Hystrix依賴
1.3.16 1.1.2 com.netflix.hystrix hystrix-core ${hystrix.version} com.netflix.hystrix hystrix-metrics-event-stream ${hystrix-metrics-event-stream.version} nexus local private nexus http://maven.oschina.net/content/groups/public/ true false
使用命令模式封裝依賴邏輯
public class HelloWorldCommand extends HystrixCommand{ private final String name; public HelloWorldCommand(String name) { //最少配置:指定命令組名(CommandGroup) super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); this.name = name; } @Override protected String run() { // 依賴邏輯封裝在run()方法中 return "Hello " + name +" thread:" + Thread.currentThread().getName(); } //調(diào)用實例 public static void main(String[] args) throws Exception{ //每個Command對象只能調(diào)用一次,不可以重復(fù)調(diào)用, //重復(fù)調(diào)用對應(yīng)異常信息:This instance can only be executed once. Please instantiate a new instance. HelloWorldCommand helloWorldCommand = new HelloWorldCommand("Synchronous-hystrix"); //使用execute()同步調(diào)用代碼,效果等同于:helloWorldCommand.queue().get(); String result = helloWorldCommand.execute(); System.out.println("result=" + result); helloWorldCommand = new HelloWorldCommand("Asynchronous-hystrix"); //異步調(diào)用,可自由控制獲取結(jié)果時機, Future future = helloWorldCommand.queue(); //get操作不能超過command定義的超時時間,默認:1秒 result = future.get(100, TimeUnit.MILLISECONDS); System.out.println("result=" + result); System.out.println("mainThread=" + Thread.currentThread().getName()); } } //運行結(jié)果: run()方法在不同的線程下執(zhí)行 // result=Hello Synchronous-hystrix thread:hystrix-HelloWorldGroup-1 // result=Hello Asynchronous-hystrix thread:hystrix-HelloWorldGroup-2 // mainThread=main
note:異步調(diào)用使用 command.queue()get(timeout, TimeUnit.MILLISECONDS);同步調(diào)用使用command.execute() 等同于 command.queue().get();
注冊異步事件回調(diào)執(zhí)行
//注冊觀察者事件攔截 Observablefs = new HelloWorldCommand("World").observe(); //注冊結(jié)果回調(diào)事件 fs.subscribe(new Action1 () { @Override public void call(String result) { //執(zhí)行結(jié)果處理,result 為HelloWorldCommand返回的結(jié)果 //用戶對結(jié)果做二次處理. } }); //注冊完整執(zhí)行生命周期事件 fs.subscribe(new Observer () { @Override public void onCompleted() { // onNext/onError完成之后最后回調(diào) System.out.println("execute onCompleted"); } @Override public void onError(Throwable e) { // 當產(chǎn)生異常時回調(diào) System.out.println("onError " + e.getMessage()); e.printStackTrace(); } @Override public void onNext(String v) { // 獲取結(jié)果后回調(diào) System.out.println("onNext: " + v); } }); /* 運行結(jié)果 call execute result=Hello observe-hystrix thread:hystrix-HelloWorldGroup-3 onNext: Hello observe-hystrix thread:hystrix-HelloWorldGroup-3 execute onCompleted */
使用Fallback() 提供降級策略
//重載HystrixCommand 的getFallback方法實現(xiàn)邏輯 public class HelloWorldCommand extends HystrixCommand{ private final String name; public HelloWorldCommand(String name) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")) /* 配置依賴超時時間,500毫秒*/ .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationThreadTimeoutInMilliseconds(500))); this.name = name; } @Override protected String getFallback() { return "exeucute Falled"; } @Override protected String run() throws Exception { //sleep 1 秒,調(diào)用會超時 TimeUnit.MILLISECONDS.sleep(1000); return "Hello " + name +" thread:" + Thread.currentThread().getName(); } public static void main(String[] args) throws Exception{ HelloWorldCommand command = new HelloWorldCommand("test-Fallback"); String result = command.execute(); } } /* 運行結(jié)果:getFallback() 調(diào)用運行 getFallback executed */
NOTE: 除了HystrixBadRequestException異常之外,所有從run()方法拋出的異常都算作失敗,并觸發(fā)降級getFallback()和斷路器邏輯。
HystrixBadRequestException用在非法參數(shù)或非系統(tǒng)故障異常等不應(yīng)觸發(fā)回退邏輯的場景。
依賴命名:CommandKey
public HelloWorldCommand(String name) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) /* HystrixCommandKey工廠定義依賴名稱 */ .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))); this.name = name; }
NOTE: 每個CommandKey代表一個依賴抽象,相同的依賴要使用相同的CommandKey名稱。依賴隔離的根本就是對相同CommandKey的依賴做隔離.
依賴分組:CommandGroup
命令分組用于對依賴操作分組,便于統(tǒng)計,匯總等.
//使用HystrixCommandGroupKey工廠定義 public HelloWorldCommand(String name) { Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")) }
NOTE: CommandGroup是每個命令最少配置的必選參數(shù),在不指定ThreadPoolKey的情況下,字面值用于對不同依賴的線程池/信號區(qū)分.
線程池/信號:ThreadPoolKey
public HelloWorldCommand(String name) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")) /* 使用HystrixThreadPoolKey工廠定義線程池名稱*/ .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"))); this.name = name; }
NOTE: 當對同一業(yè)務(wù)依賴做隔離時使用CommandGroup做區(qū)分,但是對同一依賴的不同遠程調(diào)用如(一個是redis 一個是http),可以使用HystrixThreadPoolKey做隔離區(qū)分.
最然在業(yè)務(wù)上都是相同的組,但是需要在資源上做隔離時,可以使用HystrixThreadPoolKey區(qū)分.
請求緩存 Request-Cache
public class RequestCacheCommand extends HystrixCommand{ private final int id; public RequestCacheCommand( int id) { super(HystrixCommandGroupKey.Factory.asKey("RequestCacheCommand")); this.id = id; } @Override protected String run() throws Exception { System.out.println(Thread.currentThread().getName() + " execute id=" + id); return "executed=" + id; } //重寫getCacheKey方法,實現(xiàn)區(qū)分不同請求的邏輯 @Override protected String getCacheKey() { return String.valueOf(id); } public static void main(String[] args){ HystrixRequestContext context = HystrixRequestContext.initializeContext(); try { RequestCacheCommand command2a = new RequestCacheCommand(2); RequestCacheCommand command2b = new RequestCacheCommand(2); Assert.assertTrue(command2a.execute()); //isResponseFromCache判定是否是在緩存中獲取結(jié)果 Assert.assertFalse(command2a.isResponseFromCache()); Assert.assertTrue(command2b.execute()); Assert.assertTrue(command2b.isResponseFromCache()); } finally { context.shutdown(); } context = HystrixRequestContext.initializeContext(); try { RequestCacheCommand command3b = new RequestCacheCommand(2); Assert.assertTrue(command3b.execute()); Assert.assertFalse(command3b.isResponseFromCache()); } finally { context.shutdown(); } } }
NOTE:請求緩存可以讓(CommandKey/CommandGroup)相同的情況下,直接共享結(jié)果,降低依賴調(diào)用次數(shù),在高并發(fā)和CacheKey碰撞率高場景下可以提升性能.
Servlet容器中,可以直接實用Filter機制Hystrix請求上下文
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(); } } }HystrixRequestContextServletFilter HystrixRequestContextServletFilter com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter HystrixRequestContextServletFilter /*
信號量隔離:SEMAPHORE
隔離本地代碼或可快速返回遠程調(diào)用(如memcached,redis)可以直接使用信號量隔離,降低線程隔離開銷.
public class HelloWorldCommand extends HystrixCommand{ private final String name; public HelloWorldCommand(String name) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")) /* 配置信號量隔離方式,默認采用線程池隔離 */ .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))); this.name = name; } @Override protected String run() throws Exception { return "HystrixThread:" + Thread.currentThread().getName(); } public static void main(String[] args) throws Exception{ HelloWorldCommand command = new HelloWorldCommand("semaphore"); String result = command.execute(); System.out.println(result); System.out.println("MainThread:" + Thread.currentThread().getName()); } } /** 運行結(jié)果 HystrixThread:main MainThread:main */
fallback降級邏輯命令嵌套
用場景:用于fallback邏輯涉及網(wǎng)絡(luò)訪問的情況,如緩存訪問。
public class CommandWithFallbackViaNetwork extends HystrixCommand{ 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() { // RemoteService.getValue(id); throw new RuntimeException("force failure for example"); } @Override protected String getFallback() { return new FallbackViaNetwork(id).execute(); } private static class FallbackViaNetwork extends HystrixCommand { private final int id; public FallbackViaNetwork(int id) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX")) .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueFallbackCommand")) // 使用不同的線程池做隔離,防止上層線程池跑滿,影響降級邏輯. .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback"))); this.id = id; } @Override protected String run() { MemCacheClient.getValue(id); } @Override protected String getFallback() { return null; } } }
NOTE:依賴調(diào)用和降級調(diào)用使用不同的線程池做隔離,防止上層線程池跑滿,影響二級降級邏輯調(diào)用.
顯示調(diào)用fallback邏輯,用于特殊業(yè)務(wù)處理
public class CommandFacadeWithPrimarySecondary extends HystrixCommand{ 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( 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 { 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 { 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(); } } } }
NOTE:顯示調(diào)用降級適用于特殊需求的場景,fallback用于業(yè)務(wù)處理,fallback不再承擔降級職責,建議慎重使用,會造成監(jiān)控統(tǒng)計換亂等問題.
命令調(diào)用合并:HystrixCollapser
命令調(diào)用合并允許多個請求合并到一個線程/信號下批量執(zhí)行。
執(zhí)行流程圖如下:
public class CommandCollapserGetValueForKey extends HystrixCollapser, 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) { //創(chuàng)建返回command對象 return new BatchCommand(requests); } @Override protected void mapResponseToRequests(List batchResponse, Collection > requests) { int count = 0; for (CollapsedRequest request : requests) { //手動匹配請求和響應(yīng) request.setResponse(batchResponse.get(count++)); } } private static final class BatchCommand extends HystrixCommand > { 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; } } public static class UnitTest { 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()); assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); HystrixCommand> command = HystrixRequestLog.getCurrentRequest().getExecutedCommands().toArray(new HystrixCommand>[1])[0]; assertEquals("GetValueForKey", command.getCommandKey().name()); assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } finally { context.shutdown(); } } }
NOTE:使用場景:HystrixCollapser用于對多個相同業(yè)務(wù)的請求合并到一個線程甚至可以合并到一個連接中執(zhí)行,降低線程交互次和IO數(shù),但必須保證他們屬于同一依賴.
覺得本文對你有幫助?請分享給更多人
關(guān)注「編程無界」,提升裝逼技能
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72500.html
摘要:正好,最近又有幾位不同身份的初學(xué)者來咨詢,要我推薦幾本入門書籍,而我們薦書系列已經(jīng)停更了兩個多月,所以,本期薦書就來推薦一些入門書籍吧。為了準備這期薦書,我專門搜集了本入門書籍,現(xiàn)在全部加入到了一份豆瓣豆列里,方便大家查看。 showImg(https://segmentfault.com/img/remote/1460000019299066?w=4790&h=3193); 本文原創(chuàng)...
摘要:中介者模式的應(yīng)用中介者模式的優(yōu)點就是減少類間的依賴,將一對多的依賴變成一對一的依賴,降低耦合,符合迪米特法則。中介者模式適用于多個對象之間出現(xiàn)緊密聯(lián)系,類圖成網(wǎng)狀結(jié)構(gòu),使用中介者模式可以梳理為星型結(jié)構(gòu),有助于理解其關(guān)系。 前言 由于最近瘋狂加班,博客都停更許久,難過~.~ 中介者模式定義 用一個中介對象封裝一系列的對象交互,中介者使各對象不需要顯示地相互作用,從而使其耦合松散,而且可以...
閱讀 3674·2021-11-15 11:37
閱讀 2322·2021-09-24 10:39
閱讀 2462·2021-07-25 21:37
閱讀 1451·2019-08-30 15:56
閱讀 2589·2019-08-30 15:55
閱讀 958·2019-08-30 15:54
閱讀 2129·2019-08-30 14:21
閱讀 859·2019-08-30 11:24