一文教你Spring Cloud微服務如何實現熔斷降級?

熔斷限流概述

在基於Spring Cloud的微服務架構體系下,按照系統功能邊界的不同劃分,原先大而全的系統會被拆分為多個不同的微服務,而相應的微服務會提供一組功能關聯的服務接口,並向系統中的其他微服務提供服務。在正常情況下,各個微服務之間功能上相互解耦,從軟件的設計上來講會呈現出一個比較合理的狀態,但是從調用鏈路上來看,這種拆分實際上也是拉長了外部服務請求的調用鏈路。

舉個例子,在創業公司的早期,考慮到研發維護成本,系統架構設計很簡單,從軟件結構上看,就是一個api服務面向app端,一個service端面向後臺功能。以用戶購物場景舉例,雖然這個過程邏輯上會經歷商品、下單、支付、物流、庫存等複雜邏輯的處理,但是因為這些邏輯都耦合在一個系統中,所以從用戶的app到後臺服務,服務的調用鏈路並不算太長。即便在這樣的情況下,還是會存在如果請求量突然劇增,服務端的業務處理線程池被塞滿,整個後臺系統的數據庫連接資源、緩存資源全部被耗盡,從而導致整個服務不可用的情況。

而隨著公司的逐步發展,業務請求量與日劇增,為了提高整個系統的吞吐量及可用性,我們採用了微服務架構的設計,將原先的系統拆分成了商品、訂單、支付、物流、庫存等多個微服務,而這些服務之間通過網絡進行通信(以Spring Cloud來說就是通過我們前面說到的FeignClient進行服務發現後,以HTTP的方式進行網絡調用),形成了一次購物請求,會經歷app端調用商品微服務進行瀏覽,選中商品後由商品中心調用訂單中心進行下單,然後訂單中心調用支付系統進行付款,付款成功後,訂單中心再調用物流中心進行發貨,與此同時,物流中心調用庫存系統進行庫存減少的漫長調用鏈路。

這個流程看起來就有點長了,為了方便大家理解,還是來張圖:

一文教你Spring Cloud微服務如何實現熔斷降級?


如上圖所示,在系統微服務化後,雖然此時每個微服務都擁有獨立的進程資源、業務線程池以及單獨的數據庫,整體的系統吞吐量比以前高了很多,並且每個微服務也都是集群部署。但是因為整個調用的網絡鏈路是非常長的,如果此時發生局部網絡或者部分微服服務故障的話,依然可能會導致整個微服務系統的癱瘓。

舉個例子,假設此時物流服務發生了宕機,但是前面的微服務都不知道,因為整個鏈路調用都是同步的,所以此時訂單服務調用物流微服務的時候會出現部分線程阻塞直至超時異常,同理調用物流的微服務的訂單服務的那個線程也會出現阻塞,假如此時業務請求併發量非常高,因為線程阻塞時間過長,那麼很快訂單微服務及物流微服務的業務線程數資源就會被耗盡,此時用戶App端就會出現不僅購物功能無法使用,就連商品瀏覽也不行了,而此時請求量繼續增加,情況就會更加惡化,如果業務線程池使用的是無界隊列(線程池請求隊列),最終還會導致系統內存溢出,此時系統要想自動恢復可能就比較困難了,糟糕的情況就是服務持續不可用,而最終可能只能採用重啟整個系統的高昂成本來臨時解決下,而這也還不能最終解決問題,因為重啟後情況依然可能會發生(如果並沒有排查及解決掉物流微服務故障原因的話)。

從上面的例子看,一個微服務的故障居然能導致整個系統的崩潰,而我們希望的情況是如果發現物流微服務持續故障的話,此時訂單微服務應該是可以感知到,並根據一定的機制進行容錯,即訂單微服務在知道物流微服務異常的情況下,就暫時先不要把請求發送到物流微服務了,給物流微服務先限流,而在自身本地邏輯中採取一個默認容錯邏輯進行熔斷後立刻返回App調用端,例如,可以先將需要發送的消息緩存,待物流微服務恢復後再重新發送。這樣的話,故障的物流微服務也就不會導致訂單服務因為同步調用鏈路超時過長而出現級聯故障了。

那麼在Spring Cloud微服務設計中如何才能實現這樣的機制呢?這裡涉及到幾個問題:

  • 微服務如何定義為故障,熔斷的條件是什麼?也就是說訂單微服務如何確定物流微服務不可用,從而可以實現熔斷操作;
  • 被定義為故障的微服務恢復後如何讓熔斷方感知?訂單微服務何時才可以繼續正常的調用物流微服務,實現故障恢復;
  • Spring Cloud的代碼實現機制是什麼樣的?

以上這些問題,就是本章要講述的如何在Spring Cloud微服務設計中實現服務熔斷限流的內容了!而這一點對於併發量非常高的情況下,實現微服務的可用性是很重要的一個方面。

Spring Cloud中集成Hystrix框架

在Spring Cloud微服務設計中需要通過集成Hystrix框架來實現微服務間的熔斷保護機制,Hystrix框架會通過監控微服務之間的調用情況,來決定是否啟動熔斷保護。那麼接下來,就讓我們一起來看下如何在Spring Cloud項目中通過集成Hystrix框架來實現熔斷機制吧!

引入依賴
要Spring Cloud中使用Hystrix框架,需要引入Hystrix框架的starter依賴包,如下:

 <dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-netflix-hystrix/<artifactid>
/<dependency>

通過引入此stater依賴包,我們就可以基於Spring Boot框架的特性,實現對Hystrix框架的開箱即用了。

註解開啟熔斷器
在Spring Cloud微服務中啟用熔斷器,需要在微服務的Application主程序上添加org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker註解,如:

@EnableDiscoveryClient
@EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients(basePackageClasses = {PaymentClient.class})
@EnableScheduling
public class Goods {

public static void main(String[] args) {
SpringApplication.run(Goods.class, args);
}
}

通過這樣一個簡單的註解配置,此時微服務就開啟了基於Hystrix的斷路器功能。需要說明的是,在某個微服務中開啟斷路器,實現的是該微服務對其下游微服務的熔斷功能,而不是該微服務對其上游調用的熔斷,這一點大家不要混淆了,因為在Spring Cloud的微服務體系下,熔斷的實現是基於Hystrix本地庫來實現的,本質上是客戶端熔斷,而不是服務端的熔斷。相比較於最近談論比較多的基於Service Mesh的限流熔斷功能而言,基於客戶端的熔斷從應用的形態上看,是與微服務本身融合在一起的,而不是獨立的服務。

FeignClient開啟Hystrix
在微服務中開啟斷路器後,並不表示就可以立刻使用了,在前面的章節中我們講過,在Spring Cloud微服務體系中,微服務之間的通信交互需要通過使用FeignClient來進行,而默認情況下FeignClient中默認情況下是禁用Hystrix的,所以如果需要在微服務中啟用Hystrix的熔斷功能,則需要通過配置手動開啟Hystrix功能,這樣FeignClient客戶端在微服務之間進行通訊調用時,才能在感知到微服務異常的情況下,將錯誤指標信息反饋給Hystrix框架,從而Hystrix才能根據自身邏輯對熔斷器的狀態進行啟停(關於Hystrix的具體運行原理,我們在後面的章節中進行介紹)。

以下是我們在項目的bootstrap.yml文件中,開啟FeignClient對Hystrix支持的配置:

feign:
hystrix:
enabled: true

實現FeignClient服務降級代碼

Spring Cloud中微服務之間的服務調用是基於FeignClient的,在實際的工程實踐中,我們一般會單獨將微服務的FeignClient調用端代碼進行抽離,並以SDK jar包依賴的形式進行發佈。一般情況下,可以每個微服務都抽離一個FeignClient工程代碼,這樣更加清晰;如果覺得太過於麻煩,也可以把多個不同微服務的FeignClient客戶端代碼耦合在一起,所有的微服務依賴這一個SDK也可以,只是後期如果微服務的數量比較多,並且維護團隊比較分散的話,這樣也會導致一個很臃腫的項目出現,維護升級更加麻煩而已,大家可以根據團隊的實際情況進行規劃。

我們在前面講述過基於Spring Cloud的微服務的熔斷機制,實際上是基於Hystrix框架的客戶端熔斷機制,也就是說上游微服務在通過FeignClient調用下游微服務的時候,如果感知到下游微服務調用異常需要向上向Hystrix框架反饋異常,如果Hystrix框架計算異常指標達到了閥值就會開啟熔斷器。而之後FeignClient客戶端針對該下游微服務的調用,就需要被Hystrix熔斷後回調一個相應的本地降級處理方法,從而實現服務降級。

而FeignClient從代碼的角度已經支持了這樣的設計,我們在通過@FeignClient註解編寫微服務的客戶端調用代碼時,就可以通過指定相應的Fallback類來處理服務被熔斷後的降級邏輯。下面我們就以本文舉例的項目示例,來編寫訂單微服務的FeignClient客戶端SDK代碼:order-client。

代碼示例:

@FeignClient(value = "order", configuration = OrderClientConfiguration.class, fallback = OrderClientFallback.class)
public interface OrderClient {

// 查詢購物訂單扣費狀態(內)
@RequestMapping(value = "/order/queryOrderCost", method = RequestMethod.GET)
QeuryOrderCostResVo queryOrderCost(@RequestParam(value = "orderId") String orderId) throws InternalApiException;
}

根據訂單微服務中的服務接口定義,我們通過@FeignClient註解定義了一個OrderClient.class類,該類聲明瞭微服務的接口定義,假設這裡訂單微服務提供了一個訂單查詢接口(一個微服務一般情況下會有多個服務接口,這裡舉一個接口只是為了好舉例)。

我們可以看到在@FeignClient註解的屬性中,有一個fallback屬性,這個屬性指定了一個服務降級的配置類OrderClientFallback.class。這樣,就可以在該類中實現微服務對應方法的降級邏輯了:

public class OrderClientFallback implements OrderClient {

@Override
public OrderCostDetailVo orderCost(String orderId, long userId, String busiId, String orderType, int duration,
int bikeType, String bikeNo, String countryName, int cityId, int orderCost, String currency, int strategyId,

String tradeTime) {
return new OrderCostDetailVo();
}
}

可以看到降級處理類實際上是OrderClient的一個實現類,所以在這裡每個微服務的接口都會被強制要求實現相應的熔斷降級代碼。而具體的降級邏輯,則可以根據服務的具體情況進行編寫,如這裡是返回一個空的消息對象。

以上模式就是在Spring Cloud中通過FeignClient調用時,在開啟Hystrix熔斷功能後的基本處理套路了。接下來,我們通過具體的測試效果,來看下熔斷器功能的生效情況:

1、在微服務goods中引入order微服務的FeignClient客戶端SDK

<dependency>
<groupid>com.wudimanong.client/<groupid>
<artifactid>order-client/<artifactid>
<version>1.0.0/<version>
/<dependency>

2、為了觀測,我們需要開啟HystrixDashboard

引入HystrixDashboard依賴:

<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-hystrix-dashboard/<artifactid>
/<dependency>

在應用程序主類中開啟HystrixDashboard註解:

@EnableHystrixDashboard
@EnableDiscoveryClient
@EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients
public class GoodsApplication {

public static void main(String[] args) {
SpringApplication.run(GoodsApplication.class, args);
}
}

3、此時通過訪問HystrixDashboard控制檯就可以看到監控指標信息了

我們假設goods調用order服務正常情況下Ciruit是close狀態的,如果此時斷掉order服務,然後多刷幾次goods調用請求,此時,我們就發現關於order服務的熔斷開關被打開了。

一文教你Spring Cloud微服務如何實現熔斷降級?

然後我們恢復order服務,然後再多刷幾次調用接口,就會發現Ciruit就會被關閉了。

一文教你Spring Cloud微服務如何實現熔斷降級?

通過上面的配置,我們就基本完成了Spring Cloud項目中關於Hystrix熔斷器的配置了。


分享到:


相關文章: