javascript
Spring Cloud Netflix Hystrix介绍和使用
前面我們搭建了具有服務(wù)降級(jí)功能的Hystrix客戶端,現(xiàn)在我們來詳細(xì)了解下Hystrix的一些功能。
Hystrix的意思是豪豬,大家都知道,就是長滿刺的豬。。。實(shí)際上,它表明了該框架的主要功能:自我保護(hù)功能。Hystrix具有服務(wù)降級(jí),熔斷,線程池隔離,信號(hào)量隔離,緩存等功能,基本上能覆蓋到微服務(wù)中調(diào)用依賴服務(wù)會(huì)遇到的問題。下面我們介紹下,如何理解和使用這些功能。
1、最常用的的服務(wù)降級(jí)功能
當(dāng)執(zhí)行調(diào)用服務(wù)方法時(shí),若調(diào)用方法出現(xiàn)問題,如:請(qǐng)求超時(shí),拋出異常,線程池拒絕,熔斷這些情況下,為該方法定義降級(jí)方法,以便在出現(xiàn)問題時(shí)執(zhí)行,實(shí)現(xiàn)備用返回。之前我們已經(jīng)實(shí)現(xiàn)了服務(wù)降級(jí)功能,主要就是通過@HystrixCommand(fallbackMethod = "defaultMethod")注釋到需要在出現(xiàn)問題時(shí)降級(jí)的方法。fallbackMethod指定降級(jí)后執(zhí)行的方法。方法定義在該類中,public,private,protected都可以。在注釋的方法出問題后,如超時(shí)未返回(execution.isolation.thread.timeoutinMilliseconds來配置),就會(huì)執(zhí)行備用方法,返回備用方法的返回值。當(dāng)然,降級(jí)的方法也可以定義再下一級(jí)的降級(jí)方法,實(shí)現(xiàn)和上面一樣。
上面說到方法拋出異常也會(huì)觸發(fā)服務(wù)降級(jí),但是如果我們自定義了異常,并需要將異常拋出給上層做操作,不希望Hystrix捕捉到自定義異常執(zhí)行服務(wù)降級(jí)時(shí),可以使用@HystrixCommand(ignoreExceptions = {MyException.class})來定義忽略捕捉的異常。多個(gè)異常用逗號(hào)隔開。也可以將拋出的異常通過入?yún)鞯浇导?jí)的方法,來實(shí)現(xiàn)不同類型異常的不同處理,需要將降級(jí)方法定義如下。
@HystrixCommand(fallbackMethod = "back")public String getHello(String id){return template.getForObject("http://helloclient/hello", String.class);}public String back(String id , Throwable e){if (e instanceof NullPointerException){return "client 2 has some error! NullPointerException";}else{return "client 2 has some error! Exception";}}?2、熔斷器
熔斷器,和物理概念中的斷路器類似,斷路器在高壓高溫的過載情況下,會(huì)自動(dòng)斷開,實(shí)現(xiàn)對(duì)電路的保護(hù)。熔斷器也是一樣,下面我們看下主要的接口類:HystrixCircuitBreaker.java,它定義了以下幾個(gè)方法,并有兩個(gè)內(nèi)部實(shí)現(xiàn)類HystrixCircuitBreakerImpl,NoOpCircuitBreaker,斷路器主要用到HystrixCircuitBreakerImpl。NoOpCircuitBreaker這個(gè)類表明不做任何操作,默認(rèn)熔斷器不打開,表明不起用熔斷功能。以下的實(shí)現(xiàn)方法,都是指HystrixCircuitBreakerImpl的實(shí)現(xiàn)。熔斷器有三個(gè)狀態(tài),OPEN,HALF_OPEN,CLOSED,如果要自定義參數(shù)配置,下面代碼注釋中可以找到。
/*** Every {@link HystrixCommand} requests asks this if it is allowed to proceed or not. It is idempotent and does* not modify any internal state, and takes into account the half-open logic which allows some requests through* after the circuit has been opened* * @return boolean whether a request should be permitted*/boolean allowRequest();/*** Whether the circuit is currently open (tripped).* * @return boolean state of circuit breaker*/boolean isOpen();/*** Invoked on successful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state.*/void markSuccess();/*** Invoked on unsuccessful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state.*/void markNonSuccess();/*** Invoked at start of command execution to attempt an execution. This is non-idempotent - it may modify internal* state.*/boolean attemptExecution();(1) isOpen()方法用于判斷熔斷器是否打開。實(shí)現(xiàn)方法如下:
@Overridepublic boolean isOpen() {//判斷熔斷器是否被強(qiáng)制打開,如果強(qiáng)制打開,返回true,表示熔斷器已打開。circuitBreaker.forceOpen這個(gè)配置指定if (properties.circuitBreakerForceOpen().get()) {return true;}//判斷熔斷器是否被強(qiáng)制關(guān)閉。circuitBreaker.forceClosedif (properties.circuitBreakerForceClosed().get()) {return false;}//判斷上一次斷路器打開的時(shí)間是否大于零,訪問成功,該值為-1,訪問失敗,該值為訪問失敗時(shí)的系統(tǒng)時(shí)間。根據(jù)是否大于零,判斷熔斷器是否打開。return circuitOpened.get() >= 0;}?
?
(2) attemptExecution(),該方法會(huì)在熔斷器開啟的時(shí)候,有訪問時(shí),熔斷器第一個(gè)執(zhí)行的方法。如果返回false,則直接執(zhí)行fallback降級(jí)方法。
@Overridepublic boolean attemptExecution() {//判斷熔斷器是否被強(qiáng)制打開,如果強(qiáng)制打開,返回false后,直接執(zhí)行fallbackif (properties.circuitBreakerForceOpen().get()) {return false;}//判斷熔斷器是否被強(qiáng)制關(guān)閉if (properties.circuitBreakerForceClosed().get()) {return true;}//如果circuitOpened為-1,返回true,正常執(zhí)行if (circuitOpened.get() == -1) {return true;} else {//如果circuitOpened不為-1,則表示斷路器打開了,此時(shí),服務(wù)會(huì)從circuitOpened起,休眠5秒(circuitBreaker.sleepWindowInMilliseconds配置,//默認(rèn)5000),直接返回false,執(zhí)行fallback。若休眠時(shí)間超過5秒,并且當(dāng)前熔斷狀態(tài)為打開狀態(tài),則會(huì)將熔斷狀態(tài)置為半開狀態(tài)。如它的注釋,只有第一個(gè)
//請(qǐng)求滿足第一次為打開,之后的請(qǐng)求都為半開狀態(tài),返回false。if (isAfterSleepWindow()) {if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {//only the first request after sleep window should executereturn true;} else {return false;}} else {return false;}}}
?
(3)markSuccess(),在執(zhí)行完attemptExecution()返回true正常執(zhí)行成功后(未fallback),才會(huì)執(zhí)行該方法,標(biāo)注成功,若之前斷路器為關(guān)閉狀態(tài),則不做處理,若為半開狀態(tài),則重置熔斷器。
@Overridepublic void markSuccess() {//如果當(dāng)前狀態(tài)為半開,則將state設(shè)置成closed,關(guān)閉熔斷器。如果之前由于斷路器打開時(shí),之后的請(qǐng)求,Hystrix會(huì)放開一個(gè)請(qǐng)求去嘗試是否服務(wù)正常,并將斷路器置為半開,//如果正常,則將斷路器關(guān)閉,并重置斷路器。if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {//This thread wins the race to close the circuit - it resets the stream to start it over from 0 metrics.resetStream();Subscription previousSubscription = activeSubscription.get();if (previousSubscription != null) {previousSubscription.unsubscribe();}Subscription newSubscription = subscribeToStream();activeSubscription.set(newSubscription);circuitOpened.set(-1L);}}
(4) markNonSuccess(),用來在正常請(qǐng)求下,請(qǐng)求失敗后調(diào)用。
@Overridepublic void markNonSuccess() {//如果當(dāng)前為半開狀態(tài),且請(qǐng)求失敗,則重新打開斷路器,將最近一次訪問失敗的時(shí)間置為當(dāng)前時(shí)間。if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {//This thread wins the race to re-open the circuit - it resets the start time for the sleep window circuitOpened.set(System.currentTimeMillis());}}(5) 熔斷器的打開。上面的方法都不會(huì)去打開熔斷器,熔斷器打開是由另一個(gè)方法去判斷的。這個(gè)觀察者的方法應(yīng)該是周期執(zhí)行的。
private Subscription subscribeToStream() {/** This stream will recalculate the OPEN/CLOSED status on every onNext from the health stream*/return metrics.getHealthCountsStream().observe().subscribe(new Subscriber<HealthCounts>() {@Overridepublic void onCompleted() {}@Overridepublic void onError(Throwable e) {}@Overridepublic void onNext(HealthCounts hc) {// check if we are past the statisticalWindowVolumeThreshold//檢查時(shí)間窗內(nèi)的請(qǐng)求總數(shù)小于配置文件中的數(shù)量(采用的是buckets,感興趣的自己研究下)。默認(rèn)時(shí)間窗為10S(metrics.rollingStats.timeInMilliseconds,metrics.rollingStats.numBuckets),默認(rèn)請(qǐng)求總數(shù)為20(circuitBreaker.requestVolumeThreshold)。if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {// we are not past the minimum volume threshold for the stat window,// so no change to circuit status.// if it was CLOSED, it stays CLOSED// if it was half-open, we need to wait for a successful command execution// if it was open, we need to wait for sleep window to elapse} else {//時(shí)間窗內(nèi),統(tǒng)計(jì)的錯(cuò)誤(失敗)請(qǐng)求比例是否小于配置比例,默認(rèn)配置是50%,通過circuitBreaker.errorThresholdPercentage=50指定。if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {//we are not past the minimum error threshold for the stat window,// so no change to circuit status.// if it was CLOSED, it stays CLOSED// if it was half-open, we need to wait for a successful command execution// if it was open, we need to wait for sleep window to elapse} else {// our failure rate is too high, we need to set the state to OPEN//如果時(shí)間窗內(nèi)請(qǐng)求數(shù)大于定義數(shù),且失敗比例大于定義比例,并且當(dāng)前熔斷器關(guān)閉的情況下,將熔斷器置為打開,并將circuitOpened置為當(dāng)前時(shí)間。if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {circuitOpened.set(System.currentTimeMillis());}}}}});}(6) 過程:先文字敲吧,沒畫圖工具。
正常情況:請(qǐng)求——>subscribeToStream未打開熔斷器——>attemptExecution——>markSuccess
異常情況:請(qǐng)求——>subscribeToStream打開熔斷器——>attemptExecution最后一個(gè)return返回false——>markNonSuccess,這個(gè)時(shí)候斷路器打開狀態(tài),且在休眠時(shí)間窗內(nèi)。
請(qǐng)求——>subscribeToStream未處理——>attemptExecution在超過休眠時(shí)間窗后,放開一個(gè)請(qǐng)求,并把熔斷器設(shè)置成半開——>請(qǐng)求成功,執(zhí)行markSuccess,將熔斷器從半開置為關(guān)閉,并重置熔斷器;請(qǐng)求失敗,則將半開狀態(tài)置為打開狀態(tài),失敗時(shí)間起點(diǎn)重置成當(dāng)前時(shí)間,再次循環(huán)。
3、緩存
之前我以為是每次相同請(qǐng)求,會(huì)使用緩存直接返回。其實(shí)理解錯(cuò)了,Hystrix的緩存是在當(dāng)次請(qǐng)求的緩存,當(dāng)次請(qǐng)求中,多次使用同一方法時(shí),會(huì)使用緩存。其他請(qǐng)求不能用到。而且還需初始化HystrixRequestContext,不然直接使用會(huì)報(bào)錯(cuò),我們采用定義filter來初始化。不多說了,貼代碼大家看下,代碼中注釋很清楚,啟動(dòng)注冊(cè)中心和服務(wù)實(shí)例后(環(huán)境搭建見之前章節(jié)),就可以測試。
(1)pom.xml,application.yml配置,大家參見之前的章節(jié)。
(2)啟動(dòng)類,注意注解上@ServletComponentScan。
package com.example.demo;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;@EnableCircuitBreaker @SpringBootApplication @EnableEurekaClient @ServletComponentScan public class ConsumerApplication {@Bean@LoadBalancedRestTemplate template(){return new RestTemplate();}public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);} }(3)Filter類,用于初始化HystrixRequestContext。
package com.example.demo;import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;@WebFilter(filterName = "HystrixRequestContextServletFilter",urlPatterns = "/*",asyncSupported = true) public class HystrixRequestContextServletFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HystrixRequestContext context = HystrixRequestContext.initializeContext();try{chain.doFilter(request,response);}finally {context.shutdown();}}@Overridepublic void destroy() {} }(4)controller類。
package com.example.demo;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class ConsumerContorller {@AutowiredHystrixServer server;//注意,在這個(gè)controller中調(diào)用具有緩存功能的方法才會(huì)具備緩存效果。@RequestMapping("/hello")public String sayHello(){System.out.println("請(qǐng)求了一次hello2");server.getHello2("1","ibethfy");System.out.println("請(qǐng)求了二次hello2,不會(huì)打印hello2 initinized");server.getHello2("1","ibethfy");System.out.println("請(qǐng)求了三次hello2,清空緩存,會(huì)打印hello2 initinized");server.updateHello2("1","ibethfy");server.getHello2("1","ibethfy");System.out.println("請(qǐng)求了四次hello2,入?yún)⒉煌?#xff0c;會(huì)打印hello2 initinized");server.getHello2("1","ibethfy1");return server.getHello2("1","ibethfy1");} }(5)server類。
package com.example.demo;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;@Service public class HystrixServer {@AutowiredRestTemplate template;//通過指定生成緩存key的方法生成key,commandKey指定一個(gè)HystrixCommand的key,表示注解@HystrixCommand的方法的key。groupKey表示一個(gè)類型分組的key。threadPoolKey指定線程池的key。//fallbackMethod指定降級(jí)方法,commandProperties指定該HystrixCommand方法的參數(shù),是個(gè)數(shù)組類型,里面的值是@HystrixProperty,多個(gè)用逗號(hào)隔開。@CacheResult(cacheKeyMethod = "generateCacheKey")@HystrixCommand(commandKey = "getHello1",groupKey = "getHello",threadPoolKey = "getHelloThreadPool",fallbackMethod = "back",commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutinMilliseconds", value = "5000")})public String getHello1(){System.out.println("hello1 initinized");return template.getForObject("http://helloclient/hello", String.class);}private String generateCacheKey(){return "myHelloKey";}//若不指定cache的key,默認(rèn)使用方法的所有參數(shù)作為key @CacheResult@HystrixCommand(commandKey = "getHello2",groupKey = "getHello",threadPoolKey = "getHelloThreadPool")public String getHello2(String id,String name){System.out.println("hello2 initinized");return template.getForObject("http://helloclient/hello", String.class);}//使用@CacheRemove在數(shù)據(jù)更新時(shí),移除對(duì)應(yīng)key的緩存,需要指定commandKey,@HystrixCommand里面的參數(shù)可以指定亦可以不用@CacheRemove(commandKey = "getHello2")@HystrixCommand(commandKey = "getHello2",groupKey = "getHello",threadPoolKey = "getHelloThreadPool")public void updateHello2(String id,String name){System.out.println("hello2 id = "+ id + ", name = "+ name + " removed");}//使用@CacheKey指定參數(shù)作為key @CacheResult@HystrixCommand(commandKey = "getHello3",groupKey = "getHello",threadPoolKey = "getHelloThreadPool")public String getHello3(@CacheKey("id") String id, String name){System.out.println("請(qǐng)求了一次hello3");return "hello3 " + id + name;}public String back(Throwable e){if (e instanceof NullPointerException){return "client 2 has some error! NullPointerException";}else{return "client 2 has some error! Exception";}}}
4、線程隔離和信號(hào)量隔離。
Hystrix為了避免多個(gè)不同服務(wù)間的調(diào)用影響,使用了線程隔離模式,它為每個(gè)依賴服務(wù)單獨(dú)創(chuàng)建自己的線程池,就算某個(gè)服務(wù)延遲或問題阻塞,其余的服務(wù)也能正常執(zhí)行。總之,使得我們的服務(wù)更加健壯。當(dāng)然,創(chuàng)建這么多線程池,可能會(huì)對(duì)性能造成影響,但Hystrix測試后,獨(dú)立線程池帶來的好處,遠(yuǎn)大于性能損耗的壞處。所以,大家可以放心使用。
ExecutionIsolationStrategy枚舉中定義了兩個(gè)THREAD, SEMAPHORE,一個(gè)是線程池,一個(gè)是信號(hào)量,Hystix默認(rèn)使用線程池。通過execution.isolation.strategy可以切換。
Hystrix默認(rèn)情況下,會(huì)讓配置了同組名的groupKey的command使用同一線程池,但也支持使用threadPoolKey配置線程池key。
對(duì)于那些本來延遲就比較小的請(qǐng)求(例如訪問本地緩存成功率很高的請(qǐng)求)來說,線程池帶來的開銷是非常高的,這時(shí),可以考慮采用非阻塞信號(hào)量(不支持超時(shí)),來實(shí)現(xiàn)依賴服務(wù)的隔離,使用信號(hào)量的開銷很小。但絕大多數(shù)情況下,Netflix 更偏向于使用線程池來隔離依賴服務(wù),因?yàn)槠鋷淼念~外開銷可以接受,并且能支持包括超時(shí)在內(nèi)的所有功能。
?
好了,Hystrix的主要功能基本介紹完了,碼字不容易呀,,,,
轉(zhuǎn)載于:https://www.cnblogs.com/ibethfy/p/9669281.html
總結(jié)
以上是生活随笔為你收集整理的Spring Cloud Netflix Hystrix介绍和使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷 P3128 [USACO15DEC
- 下一篇: bzoj 2727