本篇文章繼續(xù)學(xué)習(xí)springcloud組件中斷路器hystrix的基本使用
hystrix是什么检吆?
hystrix是一個(gè)服務(wù)保障型的框架,提供了包括熔斷斋扰、降級(jí)和限流等功能陵刹。
引入hystrix
spring官網(wǎng)介紹如下:
To include Hystrix in your project, use the starter with a group ID of org.springframework.cloud
and a artifact ID of spring-cloud-starter-netflix-hystrix
. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
簡(jiǎn)單說(shuō)就是告訴我們了maven坐標(biāo)迂求,根據(jù)maven坐標(biāo)就可以引入hystrix碾盐,當(dāng)然你需要注意對(duì)應(yīng)的springcloud版本,新舊版本的maven坐標(biāo)是不同的揩局,版本不同會(huì)導(dǎo)致部分注解丟失
<!--hystrix依賴,官方推薦-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--hystrix依賴毫玖,本文中使用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
使用hystrix
- 在啟動(dòng)類添加注解@EnableCircuitBreaker(官方推薦)或者@EnableHystrix
@Slf4j
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
//@EnableHystrix
@EnableCircuitBreaker
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
log.info("consumer啟動(dòng)成功");
}
}
-
@HystrixCommand注解
使用hystrix的核心注解,通過(guò)該注解標(biāo)明熔斷參數(shù)凌盯,降級(jí)調(diào)用方式等
@RequestMapping(value = "/hello")
@HystrixCommand(fallbackMethod = "defaultOut",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "20000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "90")
}
)
public String hello(@RequestParam String name) throws UnknownHostException {
System.err.println("嘗試調(diào)用");
return demoService.hello(name);
}
private String defaultOut(@RequestParam String name){
return "服務(wù)出現(xiàn)異常,降級(jí)了";
}
-
熔斷
熔斷是指付枫,指定的請(qǐng)求個(gè)數(shù)在指定時(shí)間內(nèi),失敗率達(dá)到某一閾值后驰怎,出發(fā)的保護(hù)機(jī)制阐滩。這里需要介紹幾個(gè)參數(shù):
- hystrix.command.default.circuitBreaker.enabled 用來(lái)跟蹤circuit的健康性,如果未達(dá)標(biāo)則讓request短路县忌。默認(rèn)true
- hystrix.command.default.circuitBreaker.requestVolumeThreshold 一個(gè)rolling window內(nèi)最小的請(qǐng)求數(shù)掂榔。如果設(shè)為20,那么當(dāng)一個(gè)rolling window的時(shí)間內(nèi)(比如說(shuō)1個(gè)rolling window是10秒)收到19個(gè)請(qǐng)求症杏,即使19個(gè)請(qǐng)求都失敗装获,也不會(huì)觸發(fā)circuit break。默認(rèn)20
- hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 觸發(fā)短路的時(shí)間值厉颤,當(dāng)該值設(shè)為5000時(shí)穴豫,則當(dāng)觸發(fā)circuit break后的5000毫秒內(nèi)都會(huì)拒絕request,也就是5000毫秒后才會(huì)關(guān)閉circuit逼友。默認(rèn)5000
- hystrix.command.default.circuitBreaker.errorThresholdPercentage錯(cuò)誤比率閥值精肃,如果錯(cuò)誤率>=該值,circuit會(huì)被打開(kāi)帜乞,并短路所有請(qǐng)求觸發(fā)fallback司抱。默認(rèn)50
? 配置形式:多個(gè)參數(shù)用,號(hào)隔開(kāi)
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "",value = "")挖函,...)
? 示例如下:
@RequestMapping(value = "/hello")
@HystrixCommand(
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "20000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "90")
}
)
public String hello(@RequestParam String name) throws UnknownHostException {
System.err.println("嘗試調(diào)用");
return demoService.hello(name);
}
上述參數(shù)配置理解為下:
1.我可以接收3個(gè)請(qǐng)求状植,這3個(gè)請(qǐng)求我都會(huì)去調(diào)微服務(wù)接口,即使錯(cuò)誤也不會(huì)熔斷怨喘;(這一條很重要)
2.計(jì)算3個(gè)請(qǐng)求的失敗率津畸,失敗率閾值90%;
3.當(dāng)失敗率超過(guò)90%時(shí)必怜,觸發(fā)熔斷機(jī)制肉拓,否則不觸發(fā);
4.這期間不會(huì)觸發(fā)微服務(wù)請(qǐng)求梳庆,直到20秒后暖途,hystrix才會(huì)嘗試觸發(fā)微服務(wù)請(qǐng)求卑惜;
5.如果成功,則可以正常調(diào)用驻售;如果失敗露久,繼續(xù)熔斷
為了方便把配置改為0次有效,如下:
@RequestMapping(value = "/hello")
@HystrixCommand(
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "0"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "20000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50")
}
)
public String hello(@RequestParam String name) throws UnknownHostException {
log.info("嘗試調(diào)用微服務(wù)接口");
return demoService.hello(name);
}
配置完成后欺栗,啟動(dòng)項(xiàng)目毫痕,可以正常訪問(wèn),這時(shí)候模擬服務(wù)提供者掛掉迟几,我們把服務(wù)提供者全部關(guān)閉消请,連續(xù)兩次去調(diào)用服務(wù),在發(fā)起微服務(wù)請(qǐng)求前类腮,進(jìn)行打印臊泰,結(jié)果如下:
2019-11-05 15:33:01.774 INFO 24668 --- [emoController-1] c.s.m.demo.controller.DemoController : 嘗試調(diào)用微服務(wù)接口
java.util.concurrent.TimeoutException: null
at com.netflix.hystrix.AbstractCommand.handleTimeoutViaFallback(AbstractCommand.java:997) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand.access$500(AbstractCommand.java:60) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand$12.call(AbstractCommand.java:609) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand$12.call(AbstractCommand.java:601) ~[hystrix-core-1.5.18.jar:1.5.18]
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) ~[rxjava-1.3.8.jar:1.3.8]
at com.netflix.hystrix.AbstractCommand$HystrixObservableTimeoutOperator$1.run(AbstractCommand.java:1142) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable$1.call(HystrixContextRunnable.java:41) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable$1.call(HystrixContextRunnable.java:37) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable.run(HystrixContextRunnable.java:57) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand$HystrixObservableTimeoutOperator$2.tick(AbstractCommand.java:1159) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.util.HystrixTimer$1.run(HystrixTimer.java:99) ~[hystrix-core-1.5.18.jar:1.5.18]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_181]
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) ~[na:1.8.0_181]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) ~[na:1.8.0_181]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) ~[na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_181]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
2019-11-05 15:33:07.222 ERROR 24668 --- [nio-8781-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: hello short-circuited and fallback failed.] with root cause
java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
at com.netflix.hystrix.AbstractCommand.handleShortCircuitViaFallback(AbstractCommand.java:979) ~[hystrix-core-1.5.18.jar:1.5.18]
- ?第一次調(diào)用服務(wù),因?yàn)榉?wù)提供者已經(jīng)掛掉蚜枢,這時(shí)候連接不到服務(wù)缸逃,報(bào)連接超時(shí)錯(cuò)誤
嘗試調(diào)用
2019-11-05 11:56:21.712 ERROR 8248 --- [io-8781-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: hello timed-out and fallback failed.] with root cause
java.util.concurrent.TimeoutException: null
- ?第二次調(diào)用服務(wù),因?yàn)閰?shù)配置circuitBreaker.requestVolumeThreshold為0,第二次請(qǐng)求就已經(jīng)滿足熔斷機(jī)制了祟偷,所以斷路器打開(kāi)了并且不會(huì)嘗試去調(diào)用微服務(wù)了
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
2019-11-05 11:56:24.273 ERROR 8248 --- [nio-8781-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: hello short-circuited and fallback failed.] with root cause
java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
這里就模擬實(shí)現(xiàn)了熔斷機(jī)制察滑,但熔斷后,我們沒(méi)有對(duì)服務(wù)做任何處理修肠,在實(shí)際開(kāi)發(fā)中必然是要處理的贺辰;熔斷是因?yàn)橄掠畏?wù)異常,服務(wù)宕機(jī)嵌施,上游調(diào)用一直失敗而觸發(fā)的保護(hù)機(jī)制
與同事討論時(shí)饲化,同事提出了一個(gè)問(wèn)題,這里的demo是對(duì)單個(gè)服務(wù)配置熔斷策略吗伤,那么集群環(huán)境下如何配置吃靠?我大概了解了一下在springcloud-config里可能會(huì)有答案
- 降級(jí)
我在網(wǎng)上看到這么一段話:服務(wù)器當(dāng)壓力劇增的時(shí)候,根據(jù)當(dāng)前業(yè)務(wù)情況及流量足淆,對(duì)一些服務(wù)和頁(yè)面進(jìn)行有策略的降級(jí)巢块。以此緩解服務(wù)器資源的的壓力,以保證核心業(yè)務(wù)的正常運(yùn)行巧号,同時(shí)也保持了客戶和大部分客戶的得到正確的相應(yīng)族奢。
我個(gè)人理解,降級(jí)就是當(dāng)微服務(wù)請(qǐng)求失敗時(shí)丹鸿,請(qǐng)求一個(gè)臨時(shí)的越走,甚至可以是假的數(shù)據(jù)返回,避免服務(wù)器線程一直處于被占用的情況。不過(guò)hystrix官網(wǎng)的文檔更容易理解廊敌,文檔地址如下,感興趣的額可以看看:
https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki
?使用方式
?與熔斷一樣铜跑,在@HystrixCommand注解中指定fallbackMethod的值,這個(gè)值就是你指定降級(jí)后,服務(wù)調(diào)用的方法骡澈。與熔斷的區(qū)別是锅纺,降級(jí)后不會(huì)請(qǐng)求微服務(wù)接口,而是直接去請(qǐng)求fallbackMethod了
@RequestMapping(value = "/hello")
@HystrixCommand(fallbackMethod = "defaultOut")
public String hello(@RequestParam String name) throws UnknownHostException {
log.info("嘗試調(diào)用微服務(wù)接口");
return demoService.hello(name);
}
private String defaultOut(@RequestParam String name){
return "服務(wù)出現(xiàn)異常,降級(jí)了";
}
?服務(wù)宕機(jī)后秧廉,調(diào)用:
2019-11-05 15:45:18.096 INFO 19540 --- [emoController-1] c.s.m.demo.controller.DemoController : 嘗試調(diào)用微服務(wù)接口
2019-11-05 15:45:18.219 INFO 19540 --- [emoController-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-SERVICE-HI
2019-11-05 15:45:18.220 INFO 19540 --- [emoController-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: SERVICE-HI instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SERVICE-HI,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-11-05 15:45:18.224 INFO 19540 --- [emoController-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2019-11-05 15:45:18.240 INFO 19540 --- [emoController-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client SERVICE-HI initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SERVICE-HI,current list of Servers=[DELL-PC:8772],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:DELL-PC:8772; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@3b1b4ea3
2019-11-05 15:45:19.091 WARN 19540 --- [ HystrixTimer-1] c.s.m.demo.controller.DemoController : 警告伞广,服務(wù)異常,已經(jīng)降級(jí)處理
這里就模擬了降級(jí)的情況疼电,簡(jiǎn)單來(lái)說(shuō)就是原本的服務(wù)調(diào)用失敗,進(jìn)而去調(diào)用一個(gè)新的服務(wù)减拭,返回一些內(nèi)容蔽豺,至于返回什么樣的內(nèi)容就需要根據(jù)業(yè)務(wù)實(shí)際需求來(lái)指定了。
hystrix超時(shí)問(wèn)題
上篇文章有提到并測(cè)試拧粪,feign調(diào)用服務(wù)時(shí)超時(shí)問(wèn)題修陡,實(shí)際上就是hystrix導(dǎo)致的,因?yàn)閔ystrix默認(rèn)超時(shí)時(shí)間1秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds //命令執(zhí)行超時(shí)時(shí)間可霎,默認(rèn)1000ms
feign整合hystrix
上篇文章介紹了feign的使用魄鸦,也推薦大家使用feign,feign本身集成了ribbon做負(fù)載均衡癣朗,也有對(duì)hystrix的支持拾因,更加方便,可讀性更強(qiáng)
上篇文章鏈接:http://www.reibang.com/p/f8c80fe7806c
feign整合hystrix官方文檔:https://cloud.spring.io/spring-cloud-static/spring-cloud-openfeign/2.2.0.RC1/reference/html/#spring-cloud-feign-hystrix
- 核心注解
@EnableFeignClients
@EnableCircuitBreaker
- 官方描述
1.4. Feign Hystrix Support
If Hystrix is on the classpath and feign.hystrix.enabled=true
使用hystrix要開(kāi)啟feign對(duì)hystrix的支持
1.5. Feign Hystrix Fallbacks
Hystrix supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given @FeignClient
set the fallback
attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
這句話的意思大概是旷余,要是要有一個(gè)打了@FeignClient的接口绢记,并且在fallback屬性中指明一個(gè)實(shí)現(xiàn)了該接口的類,并且這個(gè)類必須是一個(gè)spring管理的bean
-
代碼實(shí)現(xiàn)
接口:
/**
* @FeignClient(value = "SERVICE-HI",fallback = DemoServiceFeignImpl.class)
* 使用value 也是可以的正卧,可自行測(cè)試
*/
@FeignClient(name = "SERVICE-HI",fallback = DemoServiceFeignImpl.class)
public interface DemoServiceFeign {
/**
* 注意:
* @RequestParam 中 value要與服務(wù)提供方接口中聲明的參數(shù)一致
* @PostMapping 中 url要與服務(wù)提供方url一致
*/
@PostMapping("/hi")
String hello(@RequestParam(value = "name") String name);
}
實(shí)現(xiàn)類:
@Slf4j
@Component
public class DemoServiceFeignImpl implements DemoServiceFeign {
@Override
public String hello(String name) {
log.warn("feign-fallback調(diào)用成功");
return null;
}
}
調(diào)用結(jié)果:
2019-11-05 16:50:15.225 INFO 28476 --- [nio-8781-exec-2] c.s.m.demo.controller.DemoController : 嘗試調(diào)用微服務(wù)接口
2019-11-05 16:50:15.426 INFO 28476 --- [nio-8781-exec-2] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-SERVICE-HI
java.net.ConnectException: Connection refused: connect
很顯然失敗了蠢熄,但是接口和實(shí)現(xiàn)以及對(duì)應(yīng)的注解都是正確的,仔細(xì)排查炉旷,問(wèn)題在于沒(méi)有開(kāi)啟feign允許使用hystrix签孔,配置如下:
feign:
hystrix:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 11000
調(diào)用結(jié)果:
2019-11-05 16:53:51.273 INFO 29356 --- [nio-8781-exec-1] c.s.m.demo.controller.DemoController : 嘗試調(diào)用微服務(wù)接口
2019-11-05 16:53:52.387 WARN 29356 --- [ HystrixTimer-1] c.s.m.d.f.f.DemoServiceFeignImpl : feign-fallback調(diào)用成功
2019-11-05 16:53:52.389 INFO 29356 --- [nio-8781-exec-1] c.s.m.demo.service.impl.DemoServiceImpl : 假數(shù)據(jù)啦! 第 0 次調(diào)用
feign整合hystrix就演示成功了窘行,注意核心一定要開(kāi)啟 feign.hystrix.enabled=true饥追,這個(gè)和版本也有關(guān)系,新版本默認(rèn)關(guān)閉抽高,舊版本默認(rèn)開(kāi)啟
至此判耕,springcloud組件hystrix的基礎(chǔ)學(xué)習(xí)已經(jīng)介紹完畢了,建議大家結(jié)合feign一起來(lái)使用翘骂,實(shí)現(xiàn)對(duì)微服務(wù)的調(diào)用以及實(shí)現(xiàn)熔斷壁熄,降級(jí)等保護(hù)機(jī)制帚豪。事實(shí)上,本篇在降級(jí)之后草丧,可以適當(dāng)添加一些邏輯來(lái)保護(hù)系統(tǒng)狸臣,比如添加報(bào)警機(jī)智,調(diào)用第三方服務(wù)昌执,定時(shí)發(fā)送短信烛亦,或打電話給開(kāi)發(fā)人員,提醒他服務(wù)已經(jīng)降級(jí)需要查看原因等等懂拾,有感興趣的同學(xué)可以自行實(shí)現(xiàn)煤禽,歡迎大家一起來(lái)討論學(xué)習(xí)