You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot应用中使用Resilience4j时Fallback方法未被调用的问题

Spring Boot应用中使用Resilience4j时Fallback方法未被调用的问题

我有一个基于微服务的简单应用,包含3个微服务:Movie-Catalog-Service、Ratings-Data-Service、Movie-Info-Service。其中Movie-Catalog-Service依赖Ratings-Data-Service和Movie-Info-Service。我在UserRatingInfo服务类的getUserRating()方法上添加了CircuitBreaker注解,但当我故意不启动Ratings-Data-Service并调用/catalog/10接口时,出现的是:

java.lang.IllegalStateException: No instances available for ratings-data-service

而不是执行fallback方法。

以下是我的相关代码:

1. Eureka Server启动类

package com.example.discoveryserver2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServer2Application {

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

}

2. Movie-Catalog-Service启动类

package com.example.moviecatalogservice2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class MovieCatalogService2Application {

    public static void main(String[] args) {
        SpringApplication.run(MovieCatalogService2Application.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

3. Movie-Info-Service启动类

package com.example.movieinfoservice2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class MovieInfoService2Application {

    public static void main(String[] args) {
        SpringApplication.run(MovieInfoService2Application.class, args);
    }
    
    @Bean
    public RestTemplate getRestTemplate() {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setConnectTimeout(3000);
        return new RestTemplate(clientHttpRequestFactory);
    }

}

4. Ratings-Data-Service启动类

package com.example.ratingsdataservice2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RatingsDataService2Application {

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

}

5. Movie-Catalog-Service的CatalogResource类

package com.example.moviecatalogservice2.resources;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.moviecatalogservice2.models.CatalogItem;
import com.example.moviecatalogservice2.models.UserRating;
import com.example.moviecatalogservice2.services.MovieInfo;
import com.example.moviecatalogservice2.services.UserRatingInfo;

@RestController
@RequestMapping("/catalog")
public class CatalogResource {
    
    @Autowired
    private MovieInfo movieInfo;
    
    @Autowired
    private UserRatingInfo userRatingInfo;

    @RequestMapping("/{userId}")
    public List<CatalogItem> getCatalog(@PathVariable("userId") String userId) {

        UserRating userRating = userRatingInfo.getUserRating(userId);

        return userRating.getRatings().stream()
                .map(rating -> {
                    return movieInfo.getCatalogItem(rating);
                })
                .collect(Collectors.toList());
    }
    
}

6. UserRatingInfo服务类(带CircuitBreaker注解)

package com.example.moviecatalogservice2.services;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.example.moviecatalogservice2.models.Rating;
import com.example.moviecatalogservice2.models.UserRating;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;


@Service
public class UserRatingInfo {
    
    @Autowired
    private RestTemplate restTemplate;

    @CircuitBreaker(name = "ratings-data-service-cb", fallbackMethod = "getFallbackUserRating")
    public UserRating getUserRating(String userId) {
        UserRating userRating = restTemplate.getForObject("http://ratings-data-service/ratingsdata/user/{userId}", UserRating.class, userId);
        return userRating;
    }
    
    public UserRating getFallbackUserRating(String userId, Exception exc) {
        UserRating userRating =  new UserRating();
        userRating.setUserId(userId);
        userRating.setRatings(Arrays.asList(new Rating("100", 0)));
        return userRating;
    }
    
}

7. Movie-Catalog-Service的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>movie-catalog-service-2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>movie-catalog-service-2</name>
    <description>Demo spring project for practicing Resilience4j</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>21</java.version>
        <spring-cloud.version>2024.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

问题分析及解决方案

核心原因

你遇到的问题是Resilience4j的断路器没有正确捕获负载均衡器抛出的IllegalStateException,或者因为默认配置的限制导致fallback未被触发。以下是针对性的解决方案:

1. 显式指定CircuitBreaker要捕获的异常类型

修改@CircuitBreaker注解,明确指定要包含的异常类型,确保负载均衡抛出的异常被正确捕获:

@CircuitBreaker(
    name = "ratings-data-service-cb", 
    fallbackMethod = "getFallbackUserRating",
    include = {IllegalStateException.class, Exception.class}
)
public UserRating getUserRating(String userId) {
    UserRating userRating = restTemplate.getForObject("http://ratings-data-service/ratingsdata/user/{userId}", UserRating.class, userId);
    return userRating;
}

2. 调整Resilience4j断路器的默认配置

Resilience4j默认的minimumNumberOfCalls为10,意味着需要10次失败才会触发断路器状态变化。你可以在application.yml中配置自定义的断路器参数,降低最小调用次数并明确记录异常:

resilience4j:
  circuitbreaker:
    instances:
      ratings-data-service-cb:
        minimum-number-of-calls: 1  # 第一次失败就统计
        failure-rate-threshold: 50   # 失败率超过50%打开断路器
        wait-duration-in-open-state: 10s  # 断路器打开后10秒进入半开状态
        record-exceptions:
          - java.lang.IllegalStateException
          - java.lang.Exception

3. 调整Fallback方法的异常参数类型

将fallback方法的异常参数从Exception改为Throwable,确保能捕获所有类型的异常:

public UserRating getFallbackUserRating(String userId, Throwable exc) {
    // 打印异常信息方便排查
    exc.printStackTrace();
    UserRating userRating =  new UserRating();
    userRating.setUserId(userId);
    userRating.setRatings(Arrays.asList(new Rating("100", 0)));
    return userRating;
}

4. 确认服务注册状态

访问Eureka Server的控制台(默认地址http://localhost:8761),确认Movie-Catalog-Service已成功注册。虽然Spring Cloud 2024版本会自动注册,但如果存在网络或配置问题,可能导致服务未注册,进而影响负载均衡和断路器的正常工作。


备注:内容来源于stack exchange,提问作者Shri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 20:24:28