摘要:為每個依賴提供一個小的線程池或信號,如果線程池已滿調用將被立即拒絕,默認不采用排隊加速失敗判定時間。
最近小主看到很多公眾號都在發布Hystrix停更的文章,spring cloud體系的使用者和擁護者一片哀嚎,實際上,spring作為Java最大的家族,根本不需要擔心其中一兩個零件的廢棄,Hystrix的停更,只會催生更多或者更好的零件來替代它,因此,我們需要做的是:**知道Hystrix是干嘛,怎么用的,這樣要找替代者就易于反掌了。
1. 為什么需要Hystrix?文章提綱:
為什么需要Hystrix?
Hystrix如何解決依賴隔離
如何使用Hystrix
在大中型分布式系統中,通常系統很多依賴(HTTP,hession,Netty,Dubbo等),如下圖:
在高并發訪問下,這些依賴的穩定性與否對系統的影響非常大,但是依賴有很多不可控問題:如網絡連接緩慢,資源繁忙,暫時不可用,服務脫機等.
如下圖:QPS為50的依賴 I 出現不可用,但是其他依賴仍然可用.
當依賴I 阻塞時,大多數服務器的線程池就出現阻塞(BLOCK),影響整個線上服務的穩定性.如下圖:
在復雜的分布式架構的應用程序有很多的依賴,都會不可避免地在某些時候失敗。高并發的依賴失敗時如果沒有隔離措施,當前應用服務就有被拖垮的風險。
例如:一個依賴30個SOA服務的系統,每個服務99.99%可用。 99.99%的30次方 ≈ 99.7% 0.3% 意味著一億次請求 會有 3,000,00次失敗 換算成時間大約每月有2個小時服務不穩定. 隨著服務依賴數量的變多,服務不穩定的概率會成指數性提高.
解決問題方案:對依賴做隔離,Hystrix就是處理依賴隔離的框架,同時也是可以幫我們做依賴服務的治理和監控.
Netflix 公司開發并成功使用Hystrix,使用規模如下:
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)包裝依賴調用邏輯,每個命令在多帶帶線程中/信號授權下執行。
可配置依賴調用超時時間,超時時間一般設為比99.5%平均時間略高即可.當調用超時時,直接返回或執行fallback邏輯。
為每個依賴提供一個小的線程池(或信號),如果線程池已滿調用將被立即拒絕,默認不采用排隊.加速失敗判定時間。
依賴調用結果分:成功,失敗(拋出異常),超時,線程拒絕,短路。 請求失敗(異常,拒絕,超時,短路)時執行fallback(降級)邏輯。
提供熔斷器組件,可以自動運行或手動調用,停止當前依賴一段時間(10秒),熔斷器默認錯誤率閾值為50%,超過將自動運行。
提供近實時依賴的統計和監控
Hystrix依賴的隔離架構,如下圖:
使用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(); } //調用實例 public static void main(String[] args) throws Exception{ //每個Command對象只能調用一次,不可以重復調用, //重復調用對應異常信息:This instance can only be executed once. Please instantiate a new instance. HelloWorldCommand helloWorldCommand = new HelloWorldCommand("Synchronous-hystrix"); //使用execute()同步調用代碼,效果等同于:helloWorldCommand.queue().get(); String result = helloWorldCommand.execute(); System.out.println("result=" + result); helloWorldCommand = new HelloWorldCommand("Asynchronous-hystrix"); //異步調用,可自由控制獲取結果時機, 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()); } } //運行結果: run()方法在不同的線程下執行 // result=Hello Synchronous-hystrix thread:hystrix-HelloWorldGroup-1 // result=Hello Asynchronous-hystrix thread:hystrix-HelloWorldGroup-2 // mainThread=main
note:異步調用使用 command.queue()get(timeout, TimeUnit.MILLISECONDS);同步調用使用command.execute() 等同于 command.queue().get();
注冊異步事件回調執行
//注冊觀察者事件攔截 Observablefs = new HelloWorldCommand("World").observe(); //注冊結果回調事件 fs.subscribe(new Action1 () { @Override public void call(String result) { //執行結果處理,result 為HelloWorldCommand返回的結果 //用戶對結果做二次處理. } }); //注冊完整執行生命周期事件 fs.subscribe(new Observer () { @Override public void onCompleted() { // onNext/onError完成之后最后回調 System.out.println("execute onCompleted"); } @Override public void onError(Throwable e) { // 當產生異常時回調 System.out.println("onError " + e.getMessage()); e.printStackTrace(); } @Override public void onNext(String v) { // 獲取結果后回調 System.out.println("onNext: " + v); } }); /* 運行結果 call execute result=Hello observe-hystrix thread:hystrix-HelloWorldGroup-3 onNext: Hello observe-hystrix thread:hystrix-HelloWorldGroup-3 execute onCompleted */
使用Fallback() 提供降級策略
//重載HystrixCommand 的getFallback方法實現邏輯 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 秒,調用會超時 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(); } } /* 運行結果:getFallback() 調用運行 getFallback executed */
NOTE: 除了HystrixBadRequestException異常之外,所有從run()方法拋出的異常都算作失敗,并觸發降級getFallback()和斷路器邏輯。
HystrixBadRequestException用在非法參數或非系統故障異常等不應觸發回退邏輯的場景。
依賴命名: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
命令分組用于對依賴操作分組,便于統計,匯總等.
//使用HystrixCommandGroupKey工廠定義 public HelloWorldCommand(String name) { Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")) }
NOTE: CommandGroup是每個命令最少配置的必選參數,在不指定ThreadPoolKey的情況下,字面值用于對不同依賴的線程池/信號區分.
線程池/信號: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: 當對同一業務依賴做隔離時使用CommandGroup做區分,但是對同一依賴的不同遠程調用如(一個是redis 一個是http),可以使用HystrixThreadPoolKey做隔離區分.
最然在業務上都是相同的組,但是需要在資源上做隔離時,可以使用HystrixThreadPoolKey區分.
請求緩存 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方法,實現區分不同請求的邏輯 @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判定是否是在緩存中獲取結果 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)相同的情況下,直接共享結果,降低依賴調用次數,在高并發和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
隔離本地代碼或可快速返回遠程調用(如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()); } } /** 運行結果 HystrixThread:main MainThread:main */
fallback降級邏輯命令嵌套
用場景:用于fallback邏輯涉及網絡訪問的情況,如緩存訪問。
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:依賴調用和降級調用使用不同的線程池做隔離,防止上層線程池跑滿,影響二級降級邏輯調用.
顯示調用fallback邏輯,用于特殊業務處理
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:顯示調用降級適用于特殊需求的場景,fallback用于業務處理,fallback不再承擔降級職責,建議慎重使用,會造成監控統計換亂等問題.
命令調用合并:HystrixCollapser
命令調用合并允許多個請求合并到一個線程/信號下批量執行。
執行流程圖如下:
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) { //創建返回command對象 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 > { 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用于對多個相同業務的請求合并到一個線程甚至可以合并到一個連接中執行,降低線程交互次和IO數,但必須保證他們屬于同一依賴.
覺得本文對你有幫助?請分享給更多人
關注「編程無界」,提升裝逼技能
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72500.html
摘要:正好,最近又有幾位不同身份的初學者來咨詢,要我推薦幾本入門書籍,而我們薦書系列已經停更了兩個多月,所以,本期薦書就來推薦一些入門書籍吧。為了準備這期薦書,我專門搜集了本入門書籍,現在全部加入到了一份豆瓣豆列里,方便大家查看。 showImg(https://segmentfault.com/img/remote/1460000019299066?w=4790&h=3193); 本文原創...
摘要:中介者模式的應用中介者模式的優點就是減少類間的依賴,將一對多的依賴變成一對一的依賴,降低耦合,符合迪米特法則。中介者模式適用于多個對象之間出現緊密聯系,類圖成網狀結構,使用中介者模式可以梳理為星型結構,有助于理解其關系。 前言 由于最近瘋狂加班,博客都停更許久,難過~.~ 中介者模式定義 用一個中介對象封裝一系列的對象交互,中介者使各對象不需要顯示地相互作用,從而使其耦合松散,而且可以...
閱讀 3642·2021-11-15 11:37
閱讀 2311·2021-09-24 10:39
閱讀 2424·2021-07-25 21:37
閱讀 1406·2019-08-30 15:56
閱讀 2575·2019-08-30 15:55
閱讀 944·2019-08-30 15:54
閱讀 2117·2019-08-30 14:21
閱讀 848·2019-08-30 11:24