1. 程式人生 > >09 Spring Cloud的集群保護框架Hystrix

09 Spring Cloud的集群保護框架Hystrix

sch isp col 方案 真的 new 創建 之前 其中

1.概述

  在很多系統架構中都需要考慮橫向擴展、單點故障等問題,對於一個龐大的應用集群,部分服務或機器出現問題不可避免。在出現問題時,如何減少故障的影響、保障集群的高可用,成為一個重要的課題。在微服務集群中,不管是服務器,還是客戶端,都支持集群部署,本節將介紹Spring Cloud中所用的集群保護框架:Hystrix.

 1.1實際問題

   假設有如下應用程序

技術分享圖片

  用戶範圍銷售模塊,服務通過Web接口或者其他方式訪問會員模塊,會員模塊訪問數據庫。如果數據庫因為某些原因變得不可用,會員模塊就會得到“數據庫無法訪問”的信息,並且會將此信息告知銷售模塊。在實際問題中,用戶會不斷地向銷售模塊發請求,而銷售模塊這繼續請求會員模塊,會員模塊會不斷地請求連接有問題的數據庫直到超時,但是還是會有大量的用戶請求(包括重試的)會發過來,導致整個應用不堪重負。可能情況會比這個更糟糕,用戶的請求不停的發送給銷售模塊,而由於數據庫的原因,會員模塊遲遲沒有響應,有可能導致整個機房的網絡阻塞,受害的不僅僅是這個應用程序,機房中的所有服務都有可能因為網絡的原因而癱瘓。

 1.2傳統的解決方式

   對於前面遇到的實際問題,可以選擇在連接數據庫的同時加上超時的配置,讓會員模塊快速響應。但這僅僅是解決了其中的一種情況,在實際情況中,會員模塊有可能出現問題,例如部分線程阻塞、進程假死等,在這些情況下,對外的服務銷售模塊面對大量的用戶與有故障的會員模塊,仍然無法獨善其身,前面所說的問題依舊會出現。

  在當今的互聯網時代,面對大量的用戶請求,傳統或者單一的解決方式在復雜的急群中顯得力不從心,我們需要跟優雅更完善的方案來解決這些問題。

2.集群容錯框架Hystrix

  在分布式環境中,總會有一些被依賴的服務會失效,例如像網絡短暫無法訪問、服務器宕機等情況。Hystrix是Netflix下的一個java庫,Spring Cloud將Hystrix整合到Netflix項目中,Hystrix通過添加延遲閾值以及容錯的邏輯,來幫助我們控制分布式系統間組件的交互。Hystrix通過隔離服務間的訪問點、停止他們之間的級聯故障、提供可回退操作來實現容錯。

  例如我們之前講到的問題,如果數據庫層面出現問題,銷售模塊在訪問會員模塊時必然會出現超時的情況,此時可以將會員模塊隔離開來,銷售模塊短時間內不再調用會員模塊,並且會快速響應用戶的請求,從而保證銷售模塊自身乃至整個集群的穩定性,這是Hystrix可以解決的問題。加入了容錯機制,當會員模塊或者數據庫不可用時,銷售模塊將對其進行“熔斷”,在一定時間內,銷售模塊不會再調用會員模塊,以維持自身的穩定,結構圖就變成下面的圖了

        技術分享圖片

  Hystrix主要實現以下的功能:

  > 當所依賴的網絡服務發生延遲或者失敗時,對訪問的客戶端程序進行保護,就像上面的例子對銷售模塊進行保護一樣;

  > 在分布式系統中停止級聯故障;

  > 網絡服務恢復正常後,可以快速恢復客戶端的訪問能力;

  > 調用失敗時執行服務回退;

  > 可支持實時監控、報警和其他操作。

3.第一個Hystrix程序

  本例將編寫一個簡單的Hello World程序,展示Hystrix的基本功能。

 3.1 創建服務提供者

    使用SpringBoot的spring-boot-starter-web項目,創建一個普通的web項目,發布兩個測試服務用於測試,項目目錄及代碼清單如下

技術分享圖片

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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.triheart</groupId>
    <artifactId>hystrixserver</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.5.4.RELEASE</version>
        </dependency>
    </dependencies>
</project>
View Code

ServerApp.java

技術分享圖片
package com.triheart.hystrixserver;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:16
 */
@SpringBootApplication
public class ServerApp {
    public static void main(String[] args) {
        new SpringApplicationBuilder(ServerApp.class).run(args);
    }
}
View Code

    MyController.java

技術分享圖片
package com.triheart.hystrixserver;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:18
 */
@RestController
public class MyController {

    @GetMapping("/normalHello")
    public String normalHello(HttpServletRequest request) {
        return "normal Hello!";
    }

    @GetMapping("/errorHello")
    public String errorHello(HttpServletRequest request) throws Exception{
        // 模擬處理讓線程睡眠10秒
        Thread.sleep(10000);
        return "error Hello";
    }
}
View Code

  在MyController控制器中,我們提供了一個正常的服務,提供了一個需要等待10秒才有返回的服務。

 3.2 創建客戶端並使用Hystrix

  使用Hystri來請求Web服務,與原來的方式不太一樣,新建項目hystrixclient,項目的目錄結構如下

  技術分享圖片

  添加相關的依賴,pom.xml的代碼清單如下

  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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.triheart</groupId>
    <artifactId>hystrixclient</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-core</artifactId>
            <version>1.5.12</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <version>1.7.25</version>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>
    </dependencies>
</project>
View Code

  客戶端除了要使用Hystrix外,還會使用HttpClient模塊來訪問Web服務,因此要加入httpclient的依賴。新建命令類

  HelloCommand.java

技術分享圖片
package com.triheart.hystrixclient;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:43
 */
public class HelloCommand extends HystrixCommand<String> {

    private String url;
    CloseableHttpClient httpclient;

    public HelloCommand(String url) {
        // 調用父類的構造器,設置命令組的key,默認用來作為線程池的key
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        // 創建HttpClient客戶端
        this.httpclient = HttpClients.createDefault();
        this.url = url;
    }

    protected String getFallback() {
        System.out.println("執行 HelloCommand 的回退方法");
        return "error";
    }

    protected String run() throws Exception {
        try {
            // 調用 GET 方法請求服務
            HttpGet httpget = new HttpGet(url);
            // 得到服務響應
            HttpResponse response = httpclient.execute(httpget);
            // 解析並返回命令執行結果
            return EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}
View Code

  新建運行類,執行HelloCommand命令

  HelloMain.java

技術分享圖片
package com.triheart.hystrixclient;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:49
 */
public class HelloMain {

    public static void main(String[] args) {
        // 請求正常的服務
        String normalUrl = "http://localhost:8080/normalHello";
        HelloCommand command = new HelloCommand(normalUrl);
        String result = command.execute();
        System.out.println("請求正常的服務,結果:" + result);
    }
}
View Code

  正常情況下,直接調用HttpClient的API來請求Web服務,而此處命令類和運行類則通過執行命令來執行調用的工作。在命令類HelloCommond中,實現了父類的run方法,使用HttpClient調用服務的過程,都放到了該方法中。運行HelloMain類,可以看到,結果與平常調用Web服務無異。

技術分享圖片

  假設我們所調用的Hello服務發生故障,導致無法正常訪問,那麽對於客戶端來說,該如何自保呢?下面將演示調用異常服務的情況

  在HelloCommand類中,加入回退方法。在運行類中,調用發生故障的服務

  HelloErrorMain.java

技術分享圖片
package com.triheart.hystrixclient;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:49
 */
public class HelloErrorMain {

    public static void main(String[] args) {
        // 請求異常的服務
        String normalUrl = "http://localhost:8080/errorHello";
        HelloCommand command = new HelloCommand(normalUrl);
        String result = command.execute();
        System.out.println("請求異常的服務,結果:" + result);
    }
}
View Code

  運行對應的類,可以看到控制臺輸入如下

技術分享圖片

  根據結果可知,回退方法被執行了。本例中調用的errorHello服務,會阻塞10秒才有返回。默認情況下,如果調用Web服務無法再一秒內完成,那麽將觸發回退。

  回退更像是一個備胎,當請求的服務無法正常返回時,就調用該備胎來實現。這樣就可以很好的保護客戶端,服務端所提供的服務受網絡等條件的制約,如果有服務真的需要10秒才能返回結果,而客戶端有沒有容錯的機制,後果就是客戶端將一直等待返回,直到網絡超時活著服務有響應,而外界會一直不停地發送請求給客戶端,最終導致的結果就是客戶端因為請求過多而癱瘓。

09 Spring Cloud的集群保護框架Hystrix