javascript
深入了解SpringCloud Hystrix
雪崩效應即在多個服務節點當中,如果有一個服務不可用而這個不可用的服務導致整個應用資源都耗在這里,進而影響整個系統的崩潰。在分布式環境中,不可避免地會出現雪崩效應。Hystrix是一個netflix實現了 circuit breaker pattern模式的庫,它通過處理并發量,降低延遲故障和優雅的容錯處理來幫助您控制這些分布式服務之間的交互。Hystrix通過隔離服務之間的訪問點,停止跨服務的級聯故障和優雅的降級來提高系統的整體彈性。
5.2.1 為什么需要Hystrix
hystrix的作用:
5.2.2 防止雪崩效應
Hystrix采用了如下方式來防止雪崩效應:
5.2.3 工作流程
一、 構建HystrixCommand或者HystrixObservableCommand對象
第一步是構造一個HystrixCommand或HystrixObservableCommand對象,表示您正在向依賴項發出的請求。HystrixCommand用于對一個依賴項產生獨立的響應,而HystrixObservableCommand拿到的是基于rxjava實現的響應式結果observerable
二、 執行Command命令
通過使用Hystrix命令對象的以下四種方法之一(前兩種方法僅適用于簡單的HystrixCommand對象,不適用于HystrixObservableCommand),有四種方法可以執行命令:
- execute:阻塞的,可以從依賴項返回單個結果(或者在出現錯誤時拋出異常)
- queue: 從依賴項返回Future對象
- observer:訂閱依賴項返回的結果,并返回復制該源頭Observable做為返回值對象
- toObservable: 返回Observable對象,只有訂閱它的時候才會執行hystrix command命令
三、 查看緩存中是否有響應結果
如果開啟了緩存,并且緩存中有針對于本次請求結果的緩存,那么將會讀取緩存中的值
四、 斷路器是否打開
當執行命令時,Hystrix會檢查斷路器是否打開。如果斷路器的狀態是open或者tripped,那么Hystrix不會執行相關命令,它會路由至第8步。如果斷路器沒有打開則會執行第5步
五、 信號量/隊列/線程池是否填滿
如果與命令關聯的線程池和隊列或信號量已經滿了,那么Hystrix將不會執行命令,它會立即路由到(8)進行回退的操作。
六、 HystrixObservableCommand.construct() or HystrixCommand.run()
Hystrix通過調用如下方法進行對依賴項的請求:
- HystrixCommand.run()— 返回單獨的結果響應
- HystrixObservableCommand.construct()— 返回一個Observable對象
如果run()或construct()方法超過命令的超時值,線程將拋出TimeoutException(如果命令本身不在自己的線程中運行,則單獨的計時器線程將拋出該異常)。在這種情況下,Hystrix將響應傳遞到步驟8來獲取回退,如果最終返回值run()或construct()方法沒有取消/中斷,它將丟棄。
七、 計算斷路的健康值
Hystrix向斷路器報告成功、故障、拒絕和超時等信息,斷路器維護一組動態計數器,用于計算統計數據。它使用這些統計數據來確定電路什么時候應該“跳閘”,如果已經跳閘,它會短路任何后續請求,直到恢復周期結束,在此期間,它會在第一次檢查某些健康檢查后再次關閉電路
八、 調用fallback方法
當Command命令執行失敗時,Hystrix會嘗試進行回滾的操作,常見的失敗可原因如下:
- construct() or run() 拋出異常時
- 當斷路器被打開時
- 線程、隊列或者信號量充滿時
- commnad執行超時
九、 成功的進行響應
5.2.4 跳閘原理
電路開閉的方式如下:
一、 當斷路器滿足某個閥值
HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
二、 當錯誤百分比超過某個閥值
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
三、 而后斷路器有關—>開
四、當斷路器打開時,所有的請求都會進行短路操作,最常見的方式就是執行fallback方法
在一定時間以后,我們可以通過HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()來設置此值。下一個請求會被放行(此時斷路器狀態是half-open),如果這次請求失敗了,仍會打開斷路器。如果成功了,則斷路器進行關閉
5.2.5 隔離策略
5.2.5.1 線程組隔離
客戶機在單獨的線程上執行。這將它們與調用線程(Tomcat線程池)隔離開來,以便調用者可以“避開”耗時太長的依賴項調用。官方推薦使用這種方式進行服務與依賴之間的隔離,官方解釋的好處如下:
- The application is fully protected from runaway client libraries. The pool for a given dependency library can fill up without impacting the rest of the application.
- The application can accept new client libraries with far lower risk. If an issue occurs, it is isolated to the library and doesn’t affect everything else.
- When a failed client becomes healthy again, the thread pool will clear up and the application immediately resumes healthy performance, as opposed to a long recovery when the entire Tomcat container is overwhelmed.
- If a client library is misconfigured, the health of a thread pool will quickly demonstrate this (via increased errors, latency, timeouts, rejections, etc.) and you can handle it (typically in real-time via dynamic properties) without affecting application functionality.
- If a client service changes performance characteristics (which happens often enough to be an issue) which in turn cause a need to tune properties (increasing/decreasing timeouts, changing retries, etc.) this again becomes visible through thread pool metrics (errors, latency, timeouts, rejections) and can be handled without impacting other clients, requests, or users.
- Beyond the isolation benefits, having dedicated thread pools provides built-in concurrency which can be leveraged to build asynchronous facades on top of synchronous client libraries (similar to how the Netflix API built a reactive, fully-asynchronous Java API on top of Hystrix commands).
我認為其最主要的優點就是各個服務模塊的“獨立性”,不依賴與容器中(tomcat)的線程組,出了問題可以把風險降到最低同時也可以快速恢復。當然其主要缺點是增加了計算開銷。每個命令執行都涉及到在單獨的線程上運行命令所涉及的排隊、調度和上下文切換。不過官方決定接受這種開銷的成本以換取它所提供的好處,他們認為這種成本和性能影響不大。線程組隔離依賴的示例圖如下:
5.2.5.2 信號量隔離
信號量隔離,通常通過設置一個值來限制針對一項依賴的并發請求數目,這種方式可以允許不適用線程池的方式下降低負載量,如果您信任客戶端并且只希望減少負載,那么可以使用這種方法。一旦達到限制閥值,信號量拒絕其他線程的請求,但是填充信號量的線程不能離開。
如果使用ThreadLocal綁定變量或傳遞時,一定要使用信號量的隔離方式
5.2.6 單獨使用Hystrix示例
package com.iteng.springcloud.hystrix;import com.netflix.hystrix.*;public class FirstHystrixExample extends HystrixCommand<String> {public FirstHystrixExample() {super(Setter. withGroupKey(HystrixCommandGroupKey.Factory.asKey(FirstHystrixExample.class.getSimpleName())).andCommandKey(HystrixCommandKey.Factory.asKey("test")). andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(30)). andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(1000)).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(1)).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withFallbackIsolationSemaphoreMaxConcurrentRequests(200)).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerEnabled(true)));}@Overrideprotected String run() throws Exception {// Thread.sleep(30000);return "hello";//return "hello";}@Overrideprotected String getFallback() {return "error msg...";}}關于Hystrix的相關配置請參考官網:地址 ,這里需要指定hystrix的組和commandkey,我們可以通過以下方式來做:
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(FirstHystrixExample.class.getSimpleName())).andCommandKey(HystrixCommandKey.Factory.asKey("test"))在這里我們可以指定類名為組,方法名為key。利用動態代理來實現HystrixCommand
調用示例:
static void execute() {FirstHystrixExample firstHystrixExample = new FirstHystrixExample();System.out.println(firstHystrixExample.execute());}static void future() {FirstHystrixExample firstHystrixExample = new FirstHystrixExample();Future<String> future = firstHystrixExample.queue();try {String s = future.get(2, TimeUnit.SECONDS);System.out.println(s);} catch (Exception e) {e.printStackTrace();}}static void observer() {FirstHystrixExample firstHystrixExample = new FirstHystrixExample();firstHystrixExample.observe().subscribeOn(Schedulers.newThread()).subscribe(s -> System.out.println(Thread.currentThread().getName() + ":" + s));}5.2.7 SpringCloud集成Hystrix
在SpringCloud中添加Hystrix的支持,我們需要在maven或者gradle里添加groupId為org.springframework.cloud,AffactId為spring-cloud-starter-netflix-hystrix的依賴。代碼示例如下:
@SpringBootApplication@EnableCircuitBreakerpublic class Application {public static void main(String[] args) {new SpringApplicationBuilder(Application.class).web(true).run(args);}}@Componentpublic class StoreIntegration {@HystrixCommand(fallbackMethod = "defaultStores")public Object getStores(Map<String, Object> parameters) {//do stuff that might fail}public Object defaultStores(Map<String, Object> parameters) {return /* something useful */;}}其中SpringCloud在這里會把@HystrixCommand標注的方法包裝成代理對象連接至Hystrix circuit breaker,Hystrix斷路保護器會計算什么時候開閉。
5.2.7.1 源碼分析
我們可以來看一下它的源碼:
在spring-cloud-netflix-core-2.0.1.RELEASE.jar中的META-INF/spring.factories里有如下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfigurationorg.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration那么在HystrixCircuitBreakerConfiguration當中可以看到如下實現:
/** Copyright 2013-2017 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.cloud.netflix.hystrix;import org.apache.catalina.core.ApplicationContext;import org.springframework.beans.factory.DisposableBean;import org.springframework.cloud.client.actuator.HasFeatures;import org.springframework.cloud.client.actuator.NamedFeature;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import com.netflix.hystrix.Hystrix;import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;/*** @author Spencer Gibb* @author Christian Dupuis* @author Venil Noronha*/@Configurationpublic class HystrixCircuitBreakerConfiguration {@Beanpublic HystrixCommandAspect hystrixCommandAspect() {return new HystrixCommandAspect();}@Beanpublic HystrixShutdownHook hystrixShutdownHook() {return new HystrixShutdownHook();}@Beanpublic HasFeatures hystrixFeature() {return HasFeatures.namedFeatures(new NamedFeature("Hystrix", HystrixCommandAspect.class));}// ...省略部分代碼/*** {@link DisposableBean} that makes sure that Hystrix internal state is cleared when* {@link ApplicationContext} shuts down.*/private class HystrixShutdownHook implements DisposableBean {@Overridepublic void destroy() throws Exception {// Just call Hystrix to reset thread pool etc.Hystrix.reset();}}}在這里我們可以看到創建了一個HystrixCommandAspect的切面。在切面里有幾行關鍵的代碼:
//定義尋找@HystrixCommand的切點@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")public void hystrixCommandAnnotationPointcut() {}@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")public void hystrixCollapserAnnotationPointcut() {}//環繞通知@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {//根據切點找到對應的執行方法Method method = getMethodFromTarget(joinPoint);Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);//如果方法上@HystrixCommand與@HystrixCollapser則扔出異常if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +"annotations at the same time");}/*根據Joinpoint切點拿到MetaHolder,該類封裝了與Hystrix相關的要素,如配置等根據metaHolder拿到HystrixInvokable對象,該對象定義了Hystrix的執行規范*/MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));MetaHolder metaHolder = metaHolderFactory.create(joinPoint);HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();Object result;try {if (!metaHolder.isObservable()) {//執行hystrix的Commandresult = CommandExecutor.execute(invokable, executionType, metaHolder);} else {//通過Observable的方式執行result = executeObservable(invokable, executionType, metaHolder);}} catch (HystrixBadRequestException e) {throw e.getCause() != null ? e.getCause() : e;} catch (HystrixRuntimeException e) {throw hystrixRuntimeExceptionToThrowable(metaHolder, e);}return result;}5.2.7.2 Hystrix傳播ThreadLocal
如果我們想傳播ThreadLocal至@HystrixCommand中,只有設置默認策略為semaphore才可以,因為在默認情況下,Hystrix隔離策略是線程級別的,因此調用run方法時已經是Hystrix單獨維護的線程了,示例:
package com.iteng.springcloud.hystrix.service;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HystrixService {private ThreadLocal<String> threadLocal = new ThreadLocal<>();@Autowiredprivate HystrixService hystrixService;@HystrixCommand(fallbackMethod = "fallback")@GetMapping("/sleep/{value}")public String index(@PathVariable Integer value) {String r = threadLocal.get();return Thread.currentThread().getName() + ":" + r;}@GetMappingpublic String test() {threadLocal.set("test");return hystrixService.index(1);}public String fallback(Integer value) {return "error msg...";}}另外我們可以通過擴展HystrixConcurrencyStrategy的方式來處理ThreadLocal的問題,在這里官方明確的告訴我們這個辦法來解決:
/*** Provides an opportunity to wrap/decorate a {@code Callable<T>} before execution.* <p>* This can be used to inject additional behavior such as copying of thread state (such as {@link ThreadLocal}).* <p>* <b>Default Implementation</b>* <p>* Pass-thru that does no wrapping.* * @param callable* {@code Callable<T>} to be executed via a {@link ThreadPoolExecutor}* @return {@code Callable<T>} either as a pass-thru or wrapping the one given*/public <T> Callable<T> wrapCallable(Callable<T> callable) {return callable;}使用示例:
package com.iteng.springcloud.hystrix.concurrencystrategy;import com.iteng.springcloud.hystrix.FirstHystrixExample;import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;import java.util.concurrent.Callable;public class TestConcurrencyStrategy extends HystrixConcurrencyStrategy {@Overridepublic <T> Callable<T> wrapCallable(Callable<T> callable) {return new Callable<T>() {@Overridepublic T call() throws Exception {FirstHystrixExample.test.set("333");return callable.call();}};}}由于默認情況下Hystrix加載HystrixProperties默認是用ServiceLoader,具體可見(HystrixPlugins)因此需創建META-INF/services/com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy文件里面做如下配置:
com.iteng.springcloud.hystrix.concurrencystrategy.TestConcurrencyStrategy那么改造5.2.6的示例:
package com.iteng.springcloud.hystrix;import com.netflix.hystrix.*;import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;import java.util.concurrent.Callable;public class FirstHystrixExample extends HystrixCommand<String> {//....省略部分代碼...public static ThreadLocal<String> test =new ThreadLocal<>();// .....@Overrideprotected String run() throws Exception {System.out.println(Thread.currentThread().getName()+":"+test.get());return "hello";}}那么在SpringCloud中我們可以將TestConcurrencyStrategy配置為一個bean就可以了
5.2.7.3 健康信息
hystrix提供了對健康信息的檢查,我們可以通過/health端點進行有效的監控,例如:
{"hystrix": {"openCircuitBreakers": ["StoreIntegration::getStoresByLocationLink"],"status": "CIRCUIT_OPEN"},"status": "UP"}總結
以上是生活随笔為你收集整理的深入了解SpringCloud Hystrix的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于CLGeocoder - 地理编码
- 下一篇: gradle idea java ssm