隔離術之使用Hystrix實現隔離
1. Hystrix簡介
Hystrix是Netflix開源的一款針對分布式系統的延遲和容錯庫,目的是用來隔離分布式服務故障。它提供線程和信號量隔離,以減少不同服務之間資源競爭帶來的相互影響;提供優雅降級機制;提供熔斷機制使得服務可以快速失敗,而不是一直阻塞等待服務響應,并能從中快速恢復。Hystrix通過這些機制來阻止級聯失敗并保證系統彈性、可用。下圖是一個典型的分布式服務實現。
首先,當大多數人在使用Tomcat時,多個HTTP服務會共享一個線程池,假設其中一個HTTP服務訪問的數據庫響應非常慢,這將造成服務響應時間延遲增加,大多數線程阻塞等待數據響應返回,導致整個Tomcat線程池都被該服務占用,甚至拖垮整個Tomcat。因此,如果我們能把不同HTTP服務隔離到不同的線程池,則某個HTTP服務的線程池滿了也不會對其他服務造成災難性故障。這就需要線程隔離或者信號量隔離來實現了。
使用線程隔離或信號隔離的目的是為不同的服務分配一定的資源,當自己的資源用完,直接返回失敗而不是占用別人的資源。
同理,如“HTTP服務1”和“HTTP服務2”要分別訪問遠程的“分布式服務A”和“分布式服務B”,假設它們共享線程池,那么其中一個服務在出現問題時也會影響到另一個服務,因此,我們需要進行訪問隔離,可以通過Hystrix的線程池隔離或信號量隔離來實現。
其次,“分布式服務B”依賴了“分布式服務D”和“分布式服務E”,其中“分布式服務D”是一個可降級的服務,意思是出現故障時(如超時、網絡故障)可以暫時屏蔽掉或者返回緩存臟數據,如訪問商品詳情頁時,可以暫時屏蔽掉上邊的商家信息,不會影響用戶下單流程。
當我們依賴的服務訪問超時時,要提供降級策略。比如,返回托底數據阻止級聯故障。當因為一些故障(如網絡故障)使得服務可用率下降時,要能及時熔斷,一是快速失敗,二是可以保護遠程分布式服務。
到此我們大體了解了Hystrix是用來解決什么問題的。
- 限制調用分布式服務的資源使用,某一個調用的服務出現問題不會影響其他服務調用,通過線程池隔離和信號量隔離實現。
- Hystrix提供了優雅降級機制:超時降級、資源不足時(線程或信號量)降級,降級后可以配合降級接口返回托底數據。
- Hystrix也提供了熔斷器實現,當失敗率達到閥值自動觸發降級(如因網絡故障/超時造成的失敗率高),熔斷器觸發的快速失敗會進行快速恢復。
- 還提供了請求緩存、請求合并實現。
接下來,我們來看下如何使用Hystrix,本書使用的版本是Hystrix- 1.5.6。
2. 隔離示例
以線程池隔離為示例,會為不同的服務設置不同的線程池,從而實現相互隔離。
為不同的HTTP服務設置不同的線程池,為不同的分布式服務調用設置不同的線程池。
假設我們現在要調用一個獲取庫存服務,通過封裝一個命令GetStockServiceCommand來實現。
- public class GetStockServiceCommand extends HystrixCommand<String> {
- private StockService stockService;
- public GetStockServiceCommand() {
- super(setter());
- }
- private static Setter setter() {
- //服務分組
- HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory. asKey("stock");
- //服務標識
- HystrixCommandKey commandKey =HystrixCommandKey.Factory. asKey("getStock");
- //線程池名稱
- HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory. asKey("stock-pool");
- //線程池配置
- HystrixThreadPoolProperties.Setter threadPoolProperties =HystrixThreadPoolProperties.Setter threadPoolProperties =HystrixThreadPoolProperties.Setter()
- .withCoreSize(10)
- .withKeepAliveTimeMinutes(5)
- .withMaxQueueSize(Integer.MAX_VALUE)
- .withQueueSizeRejectionThreshold(10000);
- //命令屬性配置
- HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
- .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD);
- return HystrixCommand.Setter
- .withGroupKey(groupKey)
- .andCommandKey(commandKey)
- .andThreadPoolKey(threadPoolKey)
- .andThreadPoolPropertiesDefaults(threadPoolProperties)
- .andCommandPropertiesDefaults(commandProperties);
- }
- @Override
- protectedString run() throws Exception {
- return stockService.getStock();
- }
- }
幾個重要組件如下。
- HystrixCommandGroupKey:配置全局唯一標識服務分組的名稱,比如,庫存系統就是一個服務分組。當我們監控時,相同分組的服務會聚合在一起,必填選項。
- HystrixCommandKey:配置全局唯一標識服務的名稱,比如,庫存系統有一個獲取庫存服務,那么就可以為這個服務起一個名字來唯一識別該服務,如果不配置,則默認是簡單類名。
- HystrixThreadPoolKey:配置全局唯一標識線程池的名稱,相同線程池名稱的線程池是同一個,如果不配置,則默認是分組名,此名字也是線程池中線程名字的前綴。
- HystrixThreadPoolProperties:配置線程池參數,coreSize配置核心線程池大小和線程池***大小,keepAliveTimeMinutes是線程池中空閑線程生存時間(如果不進行動態配置,那么是沒有任何作用的),maxQueueSize配置線程池隊列***大小,queueSizeRejectionThreshold限定當前隊列大小,即實際隊列大小由這個參數決定,通過改變queueSizeRejectionThreshold可以實現動態隊列大小調整。
- HystrixCommandProperties:配置該命令的一些參數,如executionIsolationStrategy配置執行隔離策略,默認是使用線程隔離,此處我們配置為THREAD,即線程池隔離。
此處可以粗粒度實現隔離,也可以細粒度實現隔離,如下所示。
- 服務分組+線程池:粗粒度實現,一個服務分組/系統配置一個隔離線程池即可,不配置線程池名稱或者相同分組的線程池名稱配置為一樣。
- 服務分組+服務+線程池:細粒度實現,一個服務分組中的每一個服務配置一個隔離線程池,為不同的命令實現配置不同的線程池名稱即可。
- 混合實現:一個服務分組配置一個隔離線程池,然后對重要服務單獨設置隔離線程池。
如上配置是在應用啟動時就配置好了,在實際運行過程中,我們可能隨時調整其中一些參數,如線程池大小、隊列大小,此時,可以使用如下方式進行動態配置。
- String dynamicQueueSizeRejectionThreshold = "hystrix.threadpool."+ "stock-pool" + ".queueSizeRejectionThreshold";
- Configuration configuration = ConfigurationManager.getConfigInstance();
- configuration.setProperty(dynamicQueueSizeRejectionThreshold,100);
如果是改變線程池配置,則是"hystrix.threadpool."+ threadPoolKey + propertyName;如果是改變命令屬性配置,則是"hystrix.command." + commandKey + propertyName。
接下來就可以通過如下方式創建命令。
- GetStockServiceCommand command = new GetStockServiceCommand(newStockService());
然后通過如下方式同步調用。
- String result = command.execute();
或者返回Future從而實現異步調用。
- Future<String> future = command.queue();
或者配合RxJava實現響應式編程。
- Observable<String> observe =command.observe();
- observe.asObservable().subscribe((result) -> {
- System.out.println(result);
- });
在應用Hystrix時,首先需要把服務封裝成HystrixCommand,即命令模式實現,然后就可以通過同步/異步/響應式模式來調用服務。
信號量隔離通過如下配置即可。
- HystrixCommandProperties.Setter commandProperties= HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
- .withExecutionIsolationSemaphoreMaxConcurrentRequests(50);
信號量隔離只是限制了總的并發數,服務使用主線程進行同步調用,即沒有線程池。因此,如果只是想限制某個服務的總并發調用量或者調用的服務不涉及遠程調用的話,可以使用輕量級的信號量來實現。
GetStockServiceCommand不是單例,不能重用,必須每次使用創建一個。如果覺得Hystrix太麻煩或者太重,則可以參考Hystrix思路設計自己的組件。
【本文是51CTO專欄作者張開濤的原創文章,作者微信公眾號:開濤的博客( kaitao-1234567)】