這是SpringCloud實戰系列中第4篇文章,瞭解前面第兩篇文章更有助於更好理解本文內容:
①SpringCloud 實戰:引入Eureka組件,完善服務治理
②SpringCloud 實戰:引入Feign組件,發起服務間調用
③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡java
Hystrix 是一個延遲和容錯庫,旨在隔離對遠程系統、服務和第三方庫的訪問點,中止級聯故障,並在故障不可避免的複雜分佈式系統中實現恢復能力。git
在分佈式微服務的架構體系下,通常都會存在多層級服務服務的調用鏈,當鏈路中的某個服務發生異常,最後致使整個系統不可用,這種現象稱爲服務雪崩效應。github
如上圖所示,從最開始的整個系統正常狀態,到單個服務出現異常,再到多個服務出現異常,到最後整個系統可不用,整個過程就是服務雪崩效應。若是在單個服務出現異常的時候,咱們能及時發現、預防、處理,也就不會出現級聯效果致使整個系統不可用。Hystrix 就是來保證上面的狀況發生時能中止級聯故障,保證系統穩定運行的。web
Hystrix 支持線程池和信號量兩種隔離方式,默認使用的線程池隔離。spring
線程池隔離是當用戶請求到 A 服務後,A 服務須要調用其餘服務,這個時候能夠爲不一樣的服務建立獨立的線程池,假如 A 須要調用 B 和 C,那麼能夠建立 2 個獨立的線程池,將調用 B 服務的線程丟入到一個線程池,將調用 C 服務的線程丟入到另外一個線程池,這樣就起到隔離效果,就算其中某個線程池請求滿了,沒法處理請求了,對另外一個線程池也沒有影響。緩存
信號量隔離就比較簡單了,信號量就是一個計數器,好比初始化值是 100,那麼每次請求過來的時候就會減 1,當信號量計數爲 0 的時候,請求就會被拒絕,等以前的請求處理完成後,信號量會加 1,同時也起到了限流的做用,這就是信號量隔離,信號量隔離是在請求主線程中執行的。架構
線程池隔離的特色是 Command 運行在獨立的線程池中,能夠支持超時,是單獨的線程,支持異步。信號量隔離運行在調用的主線程中,不支持超時,只能同步調用。併發
引入相關依賴app
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
在ribbon-client
服務的啓動類上添加註解@EnableHystrix
這時候服務已經能夠啓動成功了負載均衡
HystrixCommand 註解做用於方法上,哪一個方法想要使用 Hystrix 來進行保護,就在這個方法上增長 HystrixCommand 註解。
好比在咱們的queryPort方法上添加@HystrixCommand註解:
@HystrixCommand(commandKey = "queryPort") @GetMapping("queryPort") public String queryPort(){ return providerFeign.queryPort(); }
其中commandKey不指定的話,會默認使用方法名,這裏也是queryPort;
@HystrixCommand 有不少默認的配置,好比超時時間,隔離方式等;咱們能夠手動指定配置信息有好比 commandKey、groupKey、fallbackMethod 等。
配置回退方法fallbackMethod
使用@HystrixCommand 註解方式配置回退方法,須要將回退方法定義在HystrixCommand所在的類中,且回退方法的簽名與調用的方法簽名(入參,返回值)應該保持一致,好比:
private String queryPortFallBack(){ return "sorry queryPort,jinglingwang.cn no back!"; } //調用方法改造 @HystrixCommand(commandKey = "queryPort",fallbackMethod = "queryPortFallBack")
而後咱們把eureka-provider服務停掉或者故意超時,訪問接口會出現以下圖所示的結果:
咱們也能夠結合@HystrixProperty
註解來豐富咱們的配置
@HystrixCommand(commandKey = "queryPort",commandProperties ={ @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"),//超時時間,默認1000,即1秒 @HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),//信號量隔離級別 @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "50") //信號量模式下,最大請求併發數,默認10 },fallbackMethod = "queryPortFallBack") @GetMapping("queryPort") public String queryPort(){ return providerFeign.queryPort(); }
上面的一些配置信息咱們還能夠配置到配置文件中,效果是同樣的:
# queryPort 是@HystrixCommand註解裏面的commandKey # 隔離方式,SEMAPHORE:信號量隔離,THREAD:線程隔離(默認值) hystrix.command.queryPort.execution.isolation.strategy = SEMAPHORE # 信號量模式下,最大請求併發數,默認10 hystrix.command.queryPort.execution.isolation.semaphore.maxConcurrentRequests = 50 # 超時時間,默認值是1000,也就是1秒;在HystrixCommandProperties類能夠看到 hystrix.command.queryPort.execution.isolation.thread.timeoutInMilliseconds = 3000
下面的代碼展現了線程隔離級別下的配置示例:
@HystrixCommand(commandKey = "queryTempPort", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "30"), @HystrixProperty(name = "maxQueueSize", value = "101"), @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"), @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440") } ,fallbackMethod = "queryTempPortFallBack") @GetMapping("queryTempPort") public String queryTempPort(){ return providerTempFeign.queryPort(); }
咱們也可使用@DefaultProperties
註解來配置默認屬性;
@DefaultProperties是做用在類上面的,能夠配置一些好比groupKey、threadPoolKey、commandProperties、threadPoolProperties、ignoreExceptions和raiseHystrixExceptions等屬性。方法級別的@HystrixCommand命令中單獨指定了的屬性會覆蓋默認的屬性,好比:
@RestController @DefaultProperties(groupKey = "DefaultGroupKey") public class RibbonController{ ... @HystrixCommand(commandKey = "queryTempPort",groupKey="eureka-provider-temp", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "30"), @HystrixProperty(name = "maxQueueSize", value = "101"), @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"), @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440") } ,fallbackMethod = "queryTempPortFallBack") @GetMapping("queryTempPort") public String queryTempPort(){ return providerTempFeign.queryPort(); } }
開啓Feign對Hystrix的支持
在配置文件添加以下配置
# 若是爲true,則將使用Hystrix斷路器包裝OpenFeign客戶端,默認是false feign.hystrix.enabled=true
配置fallback
爲Feign配置回退方法,將fallback屬性設置成回退的類名,例如:
@Component public class ProviderTempFeignFallback implements ProviderTempFeign{ @Override public String queryPort(){ return "sorry ProviderTempFeign, jinglingwang.cn no back!"; } } @FeignClient(value = "eureka-provider-temp",fallback = ProviderTempFeignFallback.class) public interface ProviderTempFeign{ @RequestMapping("/queryPort") String queryPort(); }
咱們保留上面的@HystrixCommand註解,而後啓動項目,把eureka-provider項目的接口加一個斷點,保證接口會超時。同時配置有兩個fallback時,發現最後生效的是@HystrixCommand註解配置的fallback,說明@HystrixCommand註解的優先級要高一些,返回結果如圖:
而後咱們把@HystrixCommand註解註釋掉,再重啓,成功執行了Feign配置的fallback,效果如圖:
fallback返回失敗的緣由
若是須要訪問致使失敗回退的緣由,可使用@FeignClient內的fallbackFactory屬性。
@Component public class ProviderFeignFallbackFactory implements FallbackFactory<ProviderFeign>{ @Override public ProviderFeign create(Throwable cause){ return new ProviderFeign(){ @Override public String queryPort(){ return "sorry ProviderFeignFallbackFactory, jinglingwang.cn no back! why? ==>" + cause.getCause(); } }; } } @FeignClient(value = "eureka-provider",fallbackFactory = ProviderFeignFallbackFactory.class) public interface ProviderFeign{ /** * 調用服務提供方,其中會返回服務提供者的端口信息 * @return jinglingwang.cn */ @RequestMapping("/queryPort") String queryPort(); }
網關中使用Hystrix等到了整合網關的時候再細講。
默認配置是全局有效的
# 配置 Hystrix 默認的配置 # To set thread isolation to SEMAPHORE hystrix.command.default.execution.isolation.strategy: SEMAPHORE hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000 hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests: 40
單獨爲Feign Client 來指定超時時間
# 單獨爲 ProviderFeign 配置 hystrix.command.ProviderFeign.execution.isolation.strategy = SEMAPHORE # 超時時間 hystrix.command.ProviderFeign.execution.isolation.thread.timeoutInMilliseconds = 5000 # 最大請求併發數,默認10 hystrix.command.ProviderFeign.execution.isolation.semaphore.maxConcurrentRequests: 200
單獨爲ProviderTempFeign類的queryPort()方法進行配置
# 單獨爲ProviderTempFeign類的queryPort()方法配置 hystrix.command.ProviderTempFeign#queryPort().execution.isolation.strategy = THREAD # 超時時間 hystrix.command.ProviderTempFeign#queryPort().execution.isolation.thread.timeoutInMilliseconds = 5000
使用 @HystrixCommand 註解配置
具體作法能夠參考上面的示例代碼
Hystrix的配置項有不少,其餘屬性的配置key能夠參考HystrixCommandProperties
類。
Hystrix 的超時時間是和Ribbon有關聯的,若是配置的不對,可能會出現莫名其妙的問題。
在Hystrix源碼裏面是建議hystrixTimeout
應該大於等於ribbonTimeout
的時間的,不然會輸出一句警告:
LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey + " is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms.");
而在取ribbonTimeout
配置值的時候,是有一個計算公式的:
ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);
假如咱們Ribbon的超時時間配置以下:
#讀超時 ribbon.ReadTimeout=3000 #鏈接超時 ribbon.ConnectTimeout=3000 #同一臺實例最大重試次數,不包括首次調用 ribbon.MaxAutoRetries=0 #重試負載均衡其餘的實例最大重試次數,不包括首次調用 ribbon.MaxAutoRetriesNextServer=1
將上面的值代入到公式計算,獲得結果:ribbonTimeout=(3000+3000)(0+1)(1+1),結果爲12000,也就是說Hystrix 的超時時間建議配置值要大於等於12000,也就是12秒。
引入dashboard依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
在啓動類上加入註解@EnableHystrixDashboard
而後啓動項目,訪問http://localhost:7071/hystrix,咱們能夠看到以下頁面:
可是這時候並不能直接使用,須要對項目進行監控,首先要有對應的 Stream 地址,Stream 產生數據源,而後將儀表板指向Hystrix客戶端應用程序中的單個實例/hystrix.stream端點。
咱們在被ribbon-client項目中加入 spring-boot-starter-actuator
依賴,只有加入了 actuator 才能暴露出 hystrix.stream 端點。
而後再配置文件添加以下配置:
management.endpoints.web.exposure.include = hystrix.stream
啓動項目,訪問http://localhost:7071/actuator/hystrix.stream接口,你會發現頁面一直在顯示ping;
而後把該地址配置到上面的頁面中,點擊monitor,OK,等待loading。
而後咱們隨便訪問一些接口,就能夠看到監控內容了。
代碼示例:Github ribbon client