Spring-Cloud 學習筆記-(5)熔斷器Hystrix
[TOC]java
一、前言
-
上個章節咱們作了什麼?git
上個章節咱們使用了
Ribbon
實現了服務之間調用的負載均衡,具體能夠分爲三個步驟github- 引ribbon依賴
- 在啓動類中的RestTemplate 加註解@LoadBalanced
- 把serviceId直接寫在RestTemplate 請求的url中調用
而且咱們針對ribbon底層實現原理,走了一遍源碼。spring
-
這個章節咱們會作什麼?api
熔斷器Hystrix服務器
二、Hystrix介紹
2.一、簡介
Hystrix,英文翻譯是豪豬,是一種保護機制,Netflix公司的一款組件。app
主頁:https://github.com/Netflix/Hystrix/ 負載均衡
Hystix是Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程服務、第三方庫,防止出現級聯失敗。 ide
2.二、雪崩問題
上一章咱們服務的調用方(order-service)調用了服務的提供方(user-serivce)查詢用戶的方法,咱們能夠稱order-service依賴於user-service,一旦咱們user-service不可用,也會致使了order-service也不可用,相似這種級聯的失敗,咱們能夠稱做雪崩。微服務
2.2.一、雪崩效應產生緣由
- **服務的級聯失敗:**就是剛剛說的A服務依賴B服務,B服務失敗了,卻是A服務也掛了,若是還有服務依賴A服務,這樣它也會掛了,就這樣一直延伸下去致使整個項目的不可用。
- **服務鏈接數被耗盡:**失敗的服務佔用了鏈接數,卻是正常的服務依舊訪問不了。
描述的詳細一點能夠這麼理解(圖片來自於:https://github.com/Netflix/Hystrix/wiki)
2.2.二、Hystrix如何解決雪崩問題
-
服務的熔斷和降級:
熔斷:當用戶的請求調用一個服務,這個服務掛了,阻塞了,咱們設置一個超時時常,若是超過這個時間,咱們會快速的返回一個失敗的友好提示給客戶端。
降級:之前訪問一個功能,咱們能夠提供全部的服務,可是如今咱們有個地方有點問題,咱們只能給你提供核心服務,不重要的暫時就訪問不了了。
-
線程的隔離:
好比咱們Tomcat線程有500個,一個用戶的請求來了調用5個服務,咱們分一個線程給他,讓這個線程去調用服務,調用成功返回結果,也就是說之前全部的服務均可以用這500個線程,這樣500個線程用完了,這個項目就掛了,如今的作法是什麼呢,咱們有針對性的給這些服務分配線程,好比一個服務分配100個線程,這樣就算有一個服務掛了, 就算服務I不可用,那隻會阻塞這個100個線程,其他的400個線程仍是正常,依舊能夠調用其餘正常的服務,咱們把這種把不一樣的服務請求,用不一樣的線程池去隔離,就算你資源耗盡,僅僅會消耗當前線程池的鏈接數叫作線程的隔離。
官網對線程的隔離圖解
當服務繁忙時,若是服務出現異常,不是粗暴的直接報錯,而是返回一個友好的提示,雖然拒絕了用戶的訪問,可是會返回一個結果。
這就比如去買魚,日常超市買魚會額外贈送殺魚的服務。等到逢年過節,超時繁忙時,可能就不提供殺魚服務了,這就是服務的降級。
系統特別繁忙時,一些次要服務暫時中斷,優先保證主要服務的暢通,一切資源優先讓給主要服務來使用,在雙11、618時,京東天貓都會採用這樣的策略。
三、服務的降級和線程隔離
3.一、代碼:
在服務的調用方(order-service)
3.1.一、引依賴:
<!-- hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
3.1.二、加註解
@SpringBootApplication @EnableCircuitBreaker//開啓服務的熔斷 public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class); } /** * 把RestTemplate注入到Spring容器中 */ @Bean @LoadBalanced //讓RestTemplate內置一個負載均衡器 public RestTemplate restTemplate(){ return new RestTemplate(); } }
其實咱們做爲Eureka的服務端須要加註解@EnableEurekaServer
,一樣咱們做爲eureka的客戶端也須要加一個註解
@EnableDiscoveryClient
,只是咱們Eureka比較智能,若是你有spring-cloud-starter-netflix-eureka-client
這個依賴,eureka就會默認你是一個eureka客戶端,因此@EnableDiscoveryClient
能夠不用加。因此一個正常的springcloud微服務,基本上都會有三個註解,@SpringBootApplication
、@EnableDiscoveryClient
、@EnableCircuitBreaker
,因此springcloud很人性化的把這三個註解合成一個註解SpringCloudApplication
,因此你們嫌麻煩能夠直接加一個SpringCloudApplication
註解就能夠了。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootApplication//啓動類 @EnableDiscoveryClient//eureka客戶端 @EnableCircuitBreaker//熔斷 public @interface SpringCloudApplication { }
3.1.二、修改代碼:
上面說過,若是服務失敗,咱們快速返回一個失敗信息,因此如今咱們要作的是寫一個快速失敗的處理。
在OrderController中
//OrderController類 @RequestMapping("{user_id}") //開啓服務的線程合理和降級處理,並指定失敗後調用的方法↓ @HystrixCommand(fallbackMethod = "findUserByIdFallbace") public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); } /** findUserById失敗後調用的方法 * 方法參數和返回值要和上面的徹底同樣 */ public BaseData findUserByIdFallbace(int id){ return new BaseData("服務器擁擠,請稍後再試!",null); }
3.1.四、模擬服務調用異常
UserServiceImpl中
//UserServiceImpl類 /** * 根據id查詢用戶基本信息 * @param id 用戶id * @return 用戶對象 */ @Override public User findById(int id) { //模擬服務器延遲 try { //休眠兩秒 TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); } User user = userMap.get(id); user.setPort(port); return user; }
3.1.五、測試
啓動服務,訪問 http://localhost:8781/api/v1/order/2
3.二、升級版
剛剛咱們是針對某一個類寫了一個降級方法,可是若是Controller中有不少方法咱們就要寫不少的降級方法。因此咱們能夠針對一個類全部方法降級
3.一、代碼
3.1.一、註解修改
@RestController @RequestMapping("api/v1/order") @DefaultProperties(defaultFallback = "defaultFallback") //爲整個類開啓服務的熔斷 public class OrderController { //.... @RequestMapping("{user_id}") //開啓服務的線程合理和降級處理 @HystrixCommand public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); }
3.1.二、降級方法修改:
/** * 方法參數爲空 */ public BaseData defaultFallback(){ return new BaseData("服務器擁擠,請稍後再試!",null); }
3.1.三、測試
3.三、配置修改
3.3.一、單一方法Hystrix配置
打開控制檯(F12),這裏咱們看的出來,雖然user-service咱們設置了睡眠時間是2秒,可是每次一秒就返回結果了,說明Hytrix默認的超時時長是1秒,可是因爲業務邏輯須要,好比發送郵件、銀行轉帳、等咱們超時時長均可以稍微設置長一點,一些簡單查詢能夠超時時長能夠設置稍微短一點。
3.3.1.一、註解修改
//OrderController類 @RequestMapping("{user_id}") /** *commandProperties,中咱們能夠配置一些屬性,能夠配置多個。 *可是這些配置的nama 和value 是什麼呢...咱們看源碼,這些配置都在HystrixCommandProperties類中 */ @HystrixCommand(commandProperties = { @HystrixProperty(name = "",value = "") }) public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); }
//HystrixCommandProperties類 //執行 超時時長 單位毫秒 咱們ctrl+f 搜索一下default_executionTimeoutInMilliseconds,看看這個配置的key是什麼... private static final Integer default_executionTimeoutInMilliseconds = 1000;
//找到配置 key:execution.isolation.thread.timeoutInMilliseconds this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
因此咱們要修改某一個方法熔斷配置,能夠在@HystrixProperty中配置對於的name和value,好比咱們配置超時時長爲3秒,咱們就能夠
//OrderController類 @RequestMapping("{user_id}") //開啓服務的線程合理和降級處理,並指定失敗後調用的方法 @HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") }) public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); }
3.3.1.二、測試
返回成功
3.3.二、全局配置
修改配置文件application.yml文件
#hystrix超時時長配置 hystrix: command: default: execution.isolation.thread.timeoutInMilliseconds: 3000
咱們刪除以前的方法上的超時配置,重啓一下order-service測試一下
依舊沒有問題。
四、服務的熔斷
4.一、熔斷的原理
-
一個請求過來,若是是關閉狀態,那請求繼續。
-
若是請求失敗超過必定的
閾值
(默認最近的20次請求有50%以上的請求失敗)則熔斷器打開。 -
後續請求發現熔斷器是開啓狀態,將直接返回錯誤信息,不會等待。
-
從熔斷器打開時候開始計時,熔斷器會通過一個
休眠時間窗
(默認5秒),超過5秒後熔斷器會進入半開狀態。 -
半開狀態的熔斷器會放必定量的請求經過進行嘗試,若是依舊超時,熔斷器繼續進入關閉狀態,而後在經歷休眠,如此反覆,直到半開狀態的熔斷器放過去的請求成功了,熔斷器會繼續進入關閉狀態。
這些默認值也是在
//HystrixCommandProperties類 //打開熔斷器的最小請求次數 private static final Integer default_circuitBreakerRequestVolumeThreshold = 20; //休眠時間窗 private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000; //設置打開熔斷並啓動回退邏輯的錯誤比率 private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;
4.二、測試
修改代碼便於測試
- 把user-service中的睡眠時間刪除
- 修改OrderController代碼
//OrderController類 @RequestMapping("{user_id}") //開啓服務的線程合理和降級處理 @HystrixCommand( commandProperties = { @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60") } ) public BaseData findUserById(@PathVariable("user_id")int id){ //手動控制請求成功失敗 技術 if(id%2==0){ throw new RuntimeException(""); } Order order = orderService.findById(id); return new BaseData(order); }
-
測試
訪問:http://localhost:8781/api/v1/order/1 成功
訪問:http://localhost:8781/api/v1/order/2 失敗
若是咱們一直訪問http://localhost:8781/api/v1/order/2 一直失敗
根據上面咱們的配置在最近的10次請求中,若是失敗超過60%,這個時候熔斷器就會開啓,就算咱們訪問http://localhost:8781/api/v1/order/1 成功的請求也會當即返回失敗信息,這個時候會經歷休眠時間窗5秒,超過5秒熔斷器進入半開狀態,咱們訪問http://localhost:8781/api/v1/order/1,成功。