1. 程式人生 > 實用技巧 >SpringCloud08:Hystrix服務熔斷與降級

SpringCloud08:Hystrix服務熔斷與降級

1、分散式系統面臨的問題

複雜分散式體系結構中的應用程式有數十個依賴關係,每個依賴關係在某些時候將不可避免失敗!

2、服務雪崩

多個微服務之間呼叫的時候,假設微服務A呼叫微服務B和微服務C,微服務B和微服務C又呼叫其他的微服務,這就是所謂的“扇出”,如果扇出的鏈路上某個微服務的呼叫響應時間過長,或者不可用,對微服務A的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”。

對於高流量的應用來說,單一的後端依賴可能會導致所有伺服器上的所有資源都在幾十秒內飽和。比失敗更糟糕的是,這些應用程式還可能導致服務之間的延遲增加,備份佇列,執行緒和其他系統資源緊張,導致整個系統發生更多的級聯故障,這些都表示需要對故障和延遲進行隔離和管理,以達到單個依賴關係的失敗而不影響整個應用程式或系統執行

我們需要,棄車保帥

3、什麼是Hystrix?

Hystrix是一個應用於處理分散式系統的延遲和容錯的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時,異常等,Hystrix 能夠保證在一個依賴出問題的情況下,不會導致整個體系服務失敗,避免級聯故障,以提高分散式系統的彈性。

斷路器”本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控 (類似熔斷保險絲) ,向呼叫方返回一個服務預期的,可處理的備選響應 (FallBack) ,而不是長時間的等待或者丟擲呼叫方法無法處理的異常,這樣就可以保證了服務呼叫方的執行緒不會被長時間,不必要的佔用,從而避免了故障在分散式系統中的蔓延,乃至雪崩。

4、Hystrix能幹嘛?

  • 服務降級
  • 服務熔斷
  • 服務限流
  • 接近實時的監控

當一切正常時,請求流可以如下所示:

當許多後端系統中有一個潛在阻塞服務時,它可以阻止整個使用者請求:

隨著大容量通訊量的增加,單個後端依賴項的潛在性會導致所有伺服器上的所有資源在幾秒鐘內飽和。

應用程式中通過網路或客戶端庫可能導致網路請求的每個點都是潛在故障的來源。比失敗更糟糕的是,這些應用程式還可能導致服務之間的延遲增加,從而備份佇列、執行緒和其他系統資源,從而導致更多跨系統的級聯故障。

當使用Hystrix包裝每個基礎依賴項時,上面的圖表中所示的體系結構會發生類似於以下關係圖的變化。每個依賴項是相互隔離的

,限制在延遲發生時它可以填充的資源中,幷包含在回退邏輯中,該邏輯決定在依賴項中發生任何型別的故障時要做出什麼樣的響應

官網資料https://github.com/Netflix/Hystrix/wiki

5、服務熔斷

什麼是服務熔斷?

熔斷機制是賭贏雪崩效應的一種微服務鏈路保護機制

當扇出鏈路的某個微服務不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的呼叫,快速返回錯誤的響應資訊。檢測到該節點微服務呼叫響應正常後恢復呼叫鏈路。在SpringCloud框架裡熔斷機制通過Hystrix實現。Hystrix會監控微服務間呼叫的狀況,當失敗的呼叫到一定閥值預設是5秒內20次呼叫失敗,就會啟動熔斷機制。熔斷機制的註解是:@HystrixCommand

服務熔斷解決如下問題:

  • 當所依賴的物件不穩定時,能夠起到快速失敗的目的;
  • 快速失敗後,能夠根據一定的演算法動態試探所依賴物件是否恢復。

入門案例

新建springcloud-provider-dept-hystrix-8001模組並拷貝springcloud-provider-dept–8001內的pom.xml、resource和Java程式碼進行初始化並調整。

匯入hystrix依賴

<!--Hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

修改描述配置資訊:再熔斷的時候區別於沒有熔斷機制的8001服務提供者提供的服務

instance:
  instance-id: springcloud-provider-dept-hystrix-8001 #修改Eureka上的預設描述資訊

修改原來的controller,比如這裡我們用按照id查詢部門資訊的方法舉例子,我們讓這個方法出錯的時候就去呼叫我們備用的方法給前端返回資訊(這就是使用hystrix實現的熔斷機制,當原方法崩潰/報錯不能正常提供服務的時候,直接切換備用的方法繼續為客戶端提供服務,而不是向客戶端返回報錯資訊)

直接在方法queryDeptById()上加上註解@HystrixCommand,這個註解需要傳遞一個引數fallbackMethod,即失敗的時候呼叫的方法的名稱

@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept queryById(@PathVariable("id") Long id) {
    Dept dept = deptService.queryById(id);
    if (dept == null) {
        throw new RuntimeException("id=>"+id+",不存在該使用者,或者資訊無法找到~");
    }
    return dept;
}

//備選方法
public Dept hystrixGet(@PathVariable("id") Long id){
    return new Dept()
            .setDeptno(id)
            .setDname("id=>"+id+"沒有對應的資訊,null--@Hystrix")
            .setDb_source("no this database in MySQL");
}

注意:我們定義的hystrix用於在原來服務者提供的按照id查詢部門資訊的方法崩潰/報錯的時候替換它繼續提供服務的方法的定義除了方法名稱,其他的應該和原方法保持一致,這是為了返回的資料仍是原來的資料型別;上面熔斷處理方法返回的是一個我們臨時new出來的Dept物件,這個物件的屬性都是我們設定的提示資訊,用於正常的結束本次消費者對於服務的請求並返回給消費者提示資訊

注意:上面我們直接返回一個消費者期望的Dept物件,或者說是原服務相同的資料型別的資料,比我們直接丟擲異常然後再捕獲異常再返回給消費者來的好

注意:異常丟擲必須有,否則hystrix不能發現這個方法執行的時候出現了異常

去這個model的入口程式/主啟動類上加上註解@EnableXXX(@EnableHystrix繼承了@EnableCircuitBreaker)

@SpringBootApplication
@EnableEurekaClient //EnableEurekaClient 客戶端的啟動類,在服務啟動後自動向註冊中心註冊服務
@EnableDiscoveryClient //服務發現
@EnableCircuitBreaker  //開啟熔斷器,即新增對熔斷的支援
public class HystrixDeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDeptProvider_8001.class, args);
    }
}

測試:

可以發現我們的服務以及註冊進去

查詢一個存在我資料:

查詢一個不存在的資料:

查詢沒熔斷的8001:

使用Hystrix實現熔斷機制雖然效果很好,但是加大了增高開發的程式碼量,因為服務者每新增一個向外體提供服務的方法,我們就需要對應的為這個方法新增一個hystrix的熔斷機制的備份方法,或許可以出現多個方法使用一個hystrix的熔斷機制的備份方法,但是大多數情況下我們還是要針對不同的情境返回不同的提示資訊,所以程式碼量還是會增高

顯式提供服務的伺服器的IP

修改配置檔案:

instance:
  instance-id: springcloud-provider-dept-hystrix-8001 #修改Eureka上的預設描述資訊
  prefer-ip-address: true #顯式顯示ip地址,預設false

小結

  • Hystrix使用步驟
    • 編寫hystrix熔斷之後的備用方法
    • 使用註解@HystrixCommand指定熔斷之後呼叫的方法的名稱
    • 使用註解@EnableCircuitBreaker開啟model的熔斷器
  • Hystrix對於使用者體驗更好,但是程式碼量增加
  • 可以修改監控頁面上對應的服務的Status欄位下面超連結顯示的地址為IP,而不是localhostXXX

6、服務降級

  • 服務降級是指當伺服器壓力劇增的情況下,根據實際業務情況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放伺服器資源以保證核心業務正常運作或高效運作。說白了,就是儘可能的把系統資源讓給優先順序高的服務
  • 資源有限,而請求是無限的。如果在併發高峰期,不做服務降級處理,一方面肯定會影響整體服務的效能,嚴重的話可能會導致宕機某些重要的服務不可用。所以,一般在高峰期,為了保證核心功能服務的可用性,都要對某些服務降級處理。比如當雙11活動時,把交易無關的服務統統降級,如檢視螞蟻深林,檢視歷史訂單等等
  • 服務降級主要用於什麼場景呢?當整個微服務架構整體的負載超出了預設的上限閾值或即將到來的流量預計將會超過預設的閾值時,為了保證重要或基本的服務能正常執行,可以將一些 不重要 或 不緊急 的服務或任務進行服務的 延遲使用 或 暫停使用
  • 降級的方式可以根據業務來,可以延遲服務,比如延遲給使用者增加積分,只是放到一個快取中,等服務平穩之後再執行 ;或者在粒度範圍內關閉服務,比如關閉相關文章的推薦

由上圖可得,當某一時間內服務A的訪問量暴增,而B和C的訪問量較少,為了緩解A服務的壓力,這時候需要B和C暫時關閉一些服務功能,去承擔A的部分服務,從而為A分擔壓力,叫做服務降級

服務降級需要考慮的問題

  • 1)那些服務是核心服務,哪些服務是非核心服務
  • 2)那些服務可以支援降級,那些服務不能支援降級,降級策略是什麼
  • 3)除服務降級之外是否存在更復雜的業務放通場景,策略是什麼?

自動降級分類

1)超時降級:主要配置好超時時間和超時重試次數和機制,並使用非同步機制探測回覆情況

2)失敗次數降級:主要是一些不穩定的api,當失敗呼叫次數達到一定閥值自動降級,同樣要使用非同步機制探測回覆情況

3)故障降級:比如要呼叫的遠端服務掛掉了(網路故障、DNS故障、http服務返回錯誤的狀態碼、rpc服務丟擲異常),則可以直接降級。降級後的處理方案有:預設值(比如庫存服務掛了,返回預設現貨)、兜底資料(比如廣告掛了,返回提前準備好的一些靜態頁面)、快取(之前暫存的一些快取資料)

4)限流降級:秒殺或者搶購一些限購商品時,此時可能會因為訪問量太大而導致系統崩潰,此時會使用限流來進行限制訪問量,當達到限流閥值,後續請求會被降級;降級後的處理方案可以是:排隊頁面(將使用者導流到排隊頁面等一會重試)、無貨(直接告知使用者沒貨了)、錯誤頁(如活動太火爆了,稍後重試)。

入門案例

在springcloud-api模組下的service包中建立一個實現FallbackFactory介面的實現類DeptClientServiceFallbackFactory

@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public boolean addDept(Dept dept) {
                return false;
            }

            @Override
            public Dept queryById(Long id) {
                return new Dept()
                        .setDeptno(id)
                        .setDname("id=>" + id + "沒有對應的資訊,客戶端提供了降級的資訊,這個服務現在已經被關閉")
                        .setDb_source("沒有資料~");
            }

            @Override
            public List<Dept> queryAll() {
                return null;
            }
        };
    }
}

在DeptClientService中指定降級配置類DeptClientServiceFallBackFactory

@Component
//@FeignClient(value = "SPRINGCOULD-PROVIDER-DEPT")
@FeignClient(value = "SPRINGCOULD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {

    @PostMapping("/dept/add")
    boolean addDept(Dept dept);

    @GetMapping("/dept/get/{id}")
    Dept queryById(@PathVariable("id") Long id);

    @GetMapping("/dept/list")
    List<Dept> queryAll();
}

去使用Feign作為負載均衡的消費者的model中的配置檔案中開啟Hystrix的降級服務

server:
  port: 80

# Eureka配置
eureka:
  client:
    register-with-eureka: false # 不向 Eureka註冊自己
    service-url: # 從三個註冊中心中隨機取一個去訪問
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

feign:
  hystrix:
    enabled: true #只需要設定該屬性就可以開啟該消費者model的hystrix服務降級

小結

  • 通過上面的案例我們大致可以明白服務降級的作用,以及它的實現
  • 上面的例子在實現服務降級的時候只是在公用的資料model springcould-api中實現了介面FallbackFactory,實現的方法create()返回的資料型別為DeptClientService,即前面我們為了實現Feign定義的一個介面物件,但是介面不能直接new,所以我們就在方法create()中實現了介面DeptClientService,為每一個方法定義了服務降級之後再被訪問的時候返回的資料
  • 然後我們就需要去需要去對應的這個介面的註解@FeignClient中傳入引數fallbackFactory,值就是上面我們實現介面FallbackFactory的類的class物件
  • 最後就是去使用了Feign作為負載均衡的消費者模組中的配置檔案開啟hystrix的服務降級功能
  • 在測試的流程中,在正常的情況下(開啟了服務降級的消費者模組對應消費的服務提供者模組正常執行的情況下),消費者可以正常的通過Feign的介面方式對於服務進行消費,但是一旦提供該服務的伺服器不再對外提供服務的時候,即我們在IDEA中關閉了這個微服務的時候,再次請求該微服務中提供的任何服務功能,返回的都是一開始在介面FallbackFactory中定義好的提示資訊
  • 注意:整個實現流程中,我們並沒有修改服務提供者的任何程式碼,只是在公共資料模組springcould-api中添加了一個實現介面FallbackFactory的類,在Feign介面的註解@FeignClient上添加了一個傳入的引數,在使用Feign的消費者模組中開啟了Hystrix的服務降級功能,然後就開啟的服務進行了測試

服務熔斷和降級的對比

  • 服務熔斷—>服務端:某個服務超時或異常,引起熔斷~,類似於保險絲(自我熔斷)
  • 服務降級—>客戶端:從整體網站請求負載考慮,當某個服務熔斷或者關閉之後,服務將不再被呼叫,此時在客戶端,我們可以準備一個 FallBackFactory ,返回一個預設的值(預設值)。會導致整體的服務下降,但是好歹能用,比直接掛掉強。
  • 觸發原因不太一樣,服務熔斷一般是某個服務(下游服務)故障引起,而服務降級一般是從整體負荷考慮;管理目標的層次不太一樣,熔斷其實是一個框架級的處理,每個微服務都需要(無層級之分),而降級一般需要對業務有層級之分(比如降級一般是從最外圍服務開始)
  • 實現方式不太一樣,服務降級具有程式碼侵入性(由控制器完成/或自動降級),熔斷一般稱為自我熔斷

熔斷,降級,限流

限流:限制併發的請求訪問量,超過閾值則拒絕;

降級:服務分優先順序,犧牲非核心服務(不可用),保證核心服務穩定;從整體負荷考慮;

熔斷:依賴的下游服務故障觸發熔斷,避免引發本系統崩潰;系統自動執行和恢復

7、Dashboard 流監控

新建springcloud-consumer-hystrix-dashboard模組

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-study</artifactId>
        <groupId>com.godfrey</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-hystrix-dashboard</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.godfrey</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--erueka-服務註冊與發現-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--actuator完善監控資訊-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--Dashboard-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
    </dependencies>

</project>

主啟動類

@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumerDashboard_9001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumerDashboard_9001.class, args);
    }
}

給springcloud-provider-dept-hystrix-8001模組下的confing包下新增如下程式碼,新增監控

@Configuration
public class ConfigBean {

    @Bean
    public ServletRegistrationBean<HystrixMetricsStreamServlet> hystrixMetricsStreamServlet(){
        ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(new HystrixMetricsStreamServlet());
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        return registrationBean;
    }
}

訪問:http://localhost:9001/hystrix

填寫資訊

效果