1. 程式人生 > 程式設計 >一文教你Spring Cloud微服務如何實現熔斷降級?

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

熔斷限流概述

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

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

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

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

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

舉個例子,假設此時物流服務發生了宕機,但是前面的微服務都不知道,因為整個鏈路呼叫都是同步的,所以此時訂單服務呼叫物流微服務的時候會出現部分執行緒阻塞直至超時異常,同理呼叫物流的微服務的訂單服務的那個執行緒也會出現阻塞,假如此時業務請求併發量非常高,因為執行緒阻塞時間過長,那麼很快訂單微服務及物流微服務的業務執行緒數資源就會被耗盡,此時使用者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服務的熔斷開關被開啟了。

然後我們恢復order服務,然後再多刷幾次呼叫介面,就會發現Ciruit就會被關閉了。

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