知識庫 / REST RSS 訂閱

Spring Cloud Netflix – Hystrix 指南

REST,Spring Cloud
HongKong
5
03:55 AM · Dec 06 ,2025

1. 概述

在本教程中,我們將涵蓋 Spring Cloud Netflix Hystrix – 容錯庫。我們將使用該庫並實現斷路器企業級模式,該模式描述了在應用程序的不同層次防止級聯故障的策略。

其原理類似於電子學:Hystrix 會監視方法在調用相關服務時出現的失敗情況。如果發生此類失敗,它將打開斷路器並將調用轉發到備用方法。

該庫將容忍失敗,直到達到閾值。超過該閾值,它將保持斷路器打開。這意味着,它將將所有後續調用轉發到備用方法,以防止未來的故障。這為相關的服務從其故障狀態中恢復提供了一個時間緩衝。

2. REST Producer

為了創建一個演示 Circuit Breaker 模式的場景,我們首先需要一個服務。我們將它命名為“REST Producer”,因為它為啓用了 Hystrix 的“REST Consumer”提供數據,我們將在下一步中創建該消費者。

讓我們使用 spring-boot-starter-web 依賴項創建一個新的 Maven 項目:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

項目本身被有意保持簡單。它由一個控制器接口和一個帶有 @RequestMapping 註解的 GET 方法,該方法簡單地返回一個 String,一個實現該接口的 @RestController 以及一個 @SpringBootApplication 組成。

我們先從接口開始:

public interface GreetingController {
    @GetMapping("/greeting/{username}")
    String greeting(@PathVariable("username") String username);
}

以下是實現方式:

@RestController
public class GreetingControllerImpl implements GreetingController {
 
    @Override
    public String greeting(@PathVariable("username") String username) {
        return String.format("Hello %s!\n", username);
    }
}

接下來,我們將編寫主應用程序類:

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

為了完成本節,唯一需要做的就是配置一個應用程序端口,我們將在此端口上監聽。 我們不會使用默認端口 8080,因為該端口應保留供下一步驟中描述的應用程序使用。

此外,我們還定義了一個應用程序名稱,以便從稍後介紹的客户端應用程序中查找我們的生產者。

然後,我們將在 application.properties 文件中指定端口 9090 和名稱 rest-producer

server.port=9090
spring.application.name=rest-producer

現在我們可以使用 cURL 測試我們的生產者:

$> curl http://localhost:9090/greeting/Cid
Hello Cid!

3. 使用 Hystrix 的 REST 消費者

為了我們的演示場景,我們將實現一個 Web 應用程序,該應用程序將使用 RestTemplateHystrix 從前一步的 REST 服務中進行消費。為了簡化起見,我們將它稱為“REST 消費者”。

因此,我們創建一個新的 Maven 項目,其中包含 spring-cloud-starter-hystrixspring-boot-starter-webspring-boot-starter-thymeleaf 作為依賴項:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

為了使斷路器正常工作,Hystix 將掃描 @Component@Service 註解的類中帶有 @HystixCommand 註解的方法,並實現代理,監視其調用。

我們將首先創建一個 @Service 類,該類將被注入到 @Controller 中。由於我們正在使用 Thymeleaf 構建 Web 應用程序,因此還需要一個 HTML 模板,用作視圖。

這將是我們的可注入 @Service 類,它將實現一個 @HystrixCommand,並與關聯的備用方法一起使用。該備用方法必須具有與原始方法相同的簽名:

@Service
public class GreetingService {
    @HystrixCommand(fallbackMethod = "defaultGreeting")
    public String getGreeting(String username) {
        return new RestTemplate()
          .getForObject("http://localhost:9090/greeting/{username}", 
          String.class, username);
    }
 
    private String defaultGreeting(String username) {
        return "Hello User!";
    }
}

RestConsumerApplication 將作為我們的主應用程序類。@EnableCircuitBreaker 註解將掃描類路徑以查找任何兼容的 Circuit Breaker 實現。

為了顯式地使用 Hystrix,我們必須使用 @EnableHystrix 註解對該類進行標註:

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

我們將會使用我們的 GreetingService 設置控制器:

@Controller
public class GreetingController {
 
    @Autowired
    private GreetingService greetingService;
 
    @GetMapping("/get-greeting/{username}")
    public String getGreeting(Model model, @PathVariable("username") String username) {
        model.addAttribute("greeting", greetingService.getGreeting(username));
        return "greeting-view";
    }
}

以下是 HTML 模板:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Greetings from Hystrix</title>
    </head>
    <body>
        <h2 th:text="${greeting}"/>
    </body>
</html>

為了確保應用程序監聽指定的端口,我們在 application.properties文件中添加了以下內容:

server.port=8080

為了觀察 Hystix 斷路器的工作原理,我們首先啓動消費者,並將瀏覽器的目標地址指向 http://localhost:8080/get-greeting/Cid。 在正常情況下,將會顯示以下內容:

Hello Cid!

為了模擬生產者的故障,我們只需停止它。在刷新瀏覽器後,我們應該看到來自我們服務層面的備用方法返回的通用消息,該方法標記為@Service

Hello User!

4. 使用 Hystrix 和 Feign 的 REST 消費者

現在,我們將修改上一步驟的項目,使用 Spring Netflix Feign 作為聲明式的 REST 客户端,而不是 Spring RestTemplate

這樣做的好處是我們稍後可以輕鬆地重構我們的 Feign 客户端接口,以使用 Spring Netflix Eureka 進行服務發現。

為了啓動新項目,我們將複製我們的消費者,並添加我們的生產者和 spring-cloud-starter-feign 作為依賴項:

<dependency>
    <groupId>com.baeldung.spring.cloud</groupId>
    <artifactId>spring-cloud-hystrix-rest-producer</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.1.5.RELEASE</version>
</dependency>

現在,我們可以使用我們的 GreetingController 來擴展一個 Feign Client。我們將 Hystrix 回退作為靜態內部類實現,並使用 @Component 註解。

或者,我們可以定義一個 @Bean 註解的方法,返回該回退類的實例。

@FeignClient 的 name 屬性是必填的。它用於通過 Eureka Client 進行服務發現,或者當此屬性提供時,通過 URL 查找應用程序。

@FeignClient(
  name = "rest-producer"
  url = "http://localhost:9090", 
  fallback = GreetingClient.GreetingClientFallback.class
)
public interface GreetingClient extends GreetingController {
     
    @Component
    public static class GreetingClientFallback implements GreetingController {
 
        @Override
        public String greeting(@PathVariable("username") String username) {
            return "Hello User!";
        }
    }
}

要了解更多關於使用 Spring Netflix Eureka 進行服務發現的信息,請參考本文。

RestConsumerFeignApplication 中,我們將添加額外的註解以啓用 Feign 集成,實際上是 @EnableFeignClients,添加到主應用程序類中:

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

我們將會修改控制器,使用自動注入的 Feign 客户端,而不是之前注入的 @Service,以獲取我們的問候語:

@Controller
public class GreetingController {
    @Autowired
    private GreetingClient greetingClient;
 
    @GetMapping("/get-greeting/{username}")
    public String getGreeting(Model model, @PathVariable("username") String username) {
        model.addAttribute("greeting", greetingClient.greeting(username));
        return "greeting-view";
    }
}

為了將此示例與之前的示例區分開來,我們將更改應用程序監聽端口,在 application.properties 文件中:

server.port=8082

最後,我們將測試這個啓用了 Feign 的消費者,就像上一節中提到的那個消費者一樣。 預期結果應該相同。

5. 使用 Hystrix 進行緩存回退

現在,我們將向我們的 Spring Cloud 項目中添加 Hystrix。 在這個雲項目中,我們有一個評分服務,它與數據庫通信並獲取書籍的評分。

假設我們的數據庫是一個受併發請求影響的資源,其響應延遲可能會隨時間變化,或者在某些時間點可能不可用。 我們將使用 Hystrix 斷路器,通過緩存回退來處理這種情況。

5.1. 部署與配置

讓我們將 <a href="https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-hystrix">spring-cloud-starter-hystrix</a> 依賴項添加到我們的評分模塊中:

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

當評分插入/更新/刪除時,我們將通過Repository將其複製到 Redis 緩存。要了解更多關於 Redis 的信息,請查看這篇文章。

讓我們更新RatingService,使其將數據庫查詢方法包裝在 Hystrix 命令中使用@HystrixCommand,並配置它包含從 Redis 讀取的備用方案:

@HystrixCommand(
  commandKey = "ratingsByIdFromDB", 
  fallbackMethod = "findCachedRatingById", 
  ignoreExceptions = { RatingNotFoundException.class })
public Rating findRatingById(Long ratingId) {
    return Optional.ofNullable(ratingRepository.findOne(ratingId))
      .orElseThrow(() -> 
        new RatingNotFoundException("Rating not found. ID: " + ratingId));
}

public Rating findCachedRatingById(Long ratingId) {
    return cacheRepository.findCachedRatingById(ratingId);
}

請注意,回退方法應與被包裹的方法具有相同的簽名,並且必須位於同一類中。當 findRatingById 失敗或延遲超過給定閾值時,Hystrix 會回退到 findCachedRatingById.

由於 Hystrix 功能以 AOP 建議形式透明地注入,因此我們需要調整建議的堆疊順序,以防我們有其他建議,例如 Spring 的事務建議。在這裏,我們已將 Spring 的事務 AOP 建議設置為優先級低於 Hystrix AOP 建議。

@EnableHystrix
@EnableTransactionManagement(
  order=Ordered.LOWEST_PRECEDENCE, 
  mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication {
    @Bean
    @Primary
    @Order(value=Ordered.HIGHEST_PRECEDENCE)
    public HystrixCommandAspect hystrixAspect() {
        return new HystrixCommandAspect();
    }
 
    // other beans, configurations
}

在這裏,我們調整了 Spring 的事務 AOP 建議優先級,使其低於 Hystrix AOP 建議優先級。

5.2. 測試 Hystrix 回退

現在我們已經配置了斷路器,可以通過讓與我們的存儲庫交互的 H2 數據庫下線來對其進行測試。但是首先,讓我們以外部進程的形式運行 H2 實例,而不是將其作為嵌入式數據庫運行。

讓我們將 H2 庫(h2-1.4.193.jar)複製到已知目錄,並啓動 H2 服務器:

>java -cp h2-1.4.193.jar org.h2.tools.Server -tcp
TCP server running at tcp://192.168.99.1:9092 (only local connections)

現在,讓我們更新模塊的數據源 URL,在 rating-service.properties 文件中指向這個 H2 服務器:

spring.datasource.url = jdbc:h2:tcp://localhost/~/ratings

我們可以從我們上一篇 Spring Cloud 系列文章中提供的服務開始,通過將我們正在運行的外部 H2 實例降低,對每本書的評分進行測試。

當 H2 數據庫不可訪問時,Hystrix 會自動回退到 Redis 讀取每本書的評分。該用例的代碼演示可以在 這裏找到。

6. 使用 Scopes

通常,帶有 @HytrixCommand 註解的方法會在線程池上下文中執行。但是,有時需要在一個本地作用域中運行,例如使用 @SessionScope@RequestScope。可以通過向命令註解傳遞參數來實現:

@HystrixCommand(fallbackMethod = "getSomeDefault", commandProperties = {
  @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")
})

7. Hystrix 儀表盤

Hystrix 的一個不錯的可選功能是能夠通過儀表盤監控其狀態。

要啓用它,我們將把 spring-cloud-starter-hystrix-dashboardspring-boot-starter-actuator 添加到我們的消費者 pom.xml 中:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

前一種方式需要通過註解一個 @Configuration 並使用 @EnableHystrixDashboard 啓用。 後一種方式則會自動在我們的 Web 應用程序中啓用所需的指標。

在重啓應用程序後,請在瀏覽器中訪問 http://localhost:8080/hystrix,輸入 Hystrix 流的指標 URL,並開始監控。

最終,您應該看到類似下面的內容:

監控一個 Hystrix 流是很方便,但如果需要監控多個啓用了 Hystrix 的應用程序,將會變得不便。 為了解決這個問題,Spring Cloud 提供了名為 Turbine 的工具,它可以聚合流並呈現在一個 Hystrix 儀表盤中。

配置 Turbine 超出本文檔的範圍,但這裏值得一提。 因此,還可以通過使用 Turbine 流,通過消息傳遞收集這些流。

8. 結論

如前所述,我們現在能夠使用 Spring Netflix Hystrix 與 Spring RestTemplate 或 Spring Netflix Feign 結合,實現斷路器模式。

這意味着我們可以使用包含默認數據的降級方案來消費服務,並且可以監控這些數據的利用情況。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.