【问题标题】:Spring-retry - @Circuitbreaker is not retryingSpring-retry - @Circuitbreaker 没有重试
【发布时间】:2019-03-27 20:45:01
【问题描述】:

我遇到了@CircuitBreaker 没有重试的问题。

我有一个服务类(例如 UserService 类和方法名称 getUser),该方法调用另一个 Spring bean(例如 AppClient 和 execute),后者又调用远程服务(REST 调用)。 execute 方法用 Spring-Retry 的 @CircuitBreaker 注释。

我已经在 rest 控制器中公开了对服务方法(类 UserService 和方法名称 getUser)的调用,并使用 Postman 对其进行了测试。这是发生的事情 - 如果发生超时错误,它会调用 @Recover 方法。但它不会重试调用远程服务三次(默认值)。

如果我通过 Postman 手动运行 3 次,断路器状态变为 OPEN 并将调用重定向到 @Recover 方法,并且在重置超时后,它会继续调用远程服务。

另外,我用@Retryable 替换了@CircuitBreaker,这使得调用3 次(默认值)。我正在使用 spring-retry 版本 1.2.1.RELEASE 和 aspectjtools 版本 1.6.2。

为什么不使用@CircuitBreaker 重试?我希望同时具有断路器和重试功能。根据文档@CircuitBreaker 应该两者都做。任何帮助将不胜感激。

@Configuration
@EnableRetry
public class cfgUserApp
{

}

@RestController
public class UserController
{
@Autowired
UserService userService;

@RequestMapping(value = "/user/{userId}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<User> getUser(@PathVariable String userId) {
    return ok(userService.getUser(userId));
}
}

/* Spring Bean -- userService */

public class UserServiceImpl
implements userService
{

@Override
public User getUser( final String userId )
{
checkArgument( User != null && !User.isEmpty(), "User Id can not be null or empty." );
try
{
    final HttpGet request = buildGetUserRequest( userId );
    final User userResult = appClient.execute( request,
        response -> createGetReservationResult( response ) );
    return userResult;
}
catch ( final IOException e )
{
    LOG.error( "getUser failed.", e );
    throw new AppException(e.getMessage(), e);
}
}
}

public Class Appclient {

@Recover
public <T> T recover(AppException appException, final HttpUriRequest request,
                            final ResponseHandler<T> responseFunction )
{
    System.out.println("In Recovery");
    return <T>new User();
}

@CircuitBreaker( include = AppException.class, openTimeout = 5000l, resetTimeout = 10000l )
    public <T> T execute( final HttpUriRequest request,
                          final ResponseHandler<T> responseFunction )
                          {
                          // HTTP call
                          }
}

【问题讨论】:

    标签: spring-retry circuit-breaker


    【解决方案1】:

    这可能与 this 类似的问题 - 使用 JDK 代理时找不到注释,因为您有一个 interface

    把注解移到界面上,或者使用

    @EnableRetry(proxyTargetClass = true)
    

    我向该 PR 添加了另一个提交以解决此问题。

    编辑

    你好像误解了@CircuitBreaker;它不会在内部重试;相反,它是一个有状态的重试拦截器,在超过断路器属性后故障转移。

    我更改了您的应用来执行此操作...

    @GetMapping("/getnumber")
    public int getNumber(){
        return this.userService.getNumber() + this.userService.getNumber() +
                this.userService.getNumber() + this.userService.getNumber();
    }
    

    然后我看到了

    getNumber
    fallback
    getNumber
    fallback
    getNumber
    fallback
    fallback
    

    要实现(我认为)你想要的,你需要用重试服务包装服务,并将恢复放在那里:

    @SpringBootApplication
    @EnableRetry(proxyTargetClass = true)
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    
    @RestController
    class UserRestController {
    
        private final RetryingUserService userService;
    
        @Autowired
        public UserRestController(RetryingUserService userService) {
            this.userService = userService;
        }
    
        @GetMapping("/getnumber")
        public int getNumber() {
            return this.userService.getNumber();
        }
    
    }
    
    @Service
    class RetryingUserService {
    
        private final UserService userService;
    
        public RetryingUserService(UserService userService) {
            this.userService = userService;
        }
    
        @Retryable
        public int getNumber() {
            return this.userService.getNumber();
        }
    
        @Recover
        public int fallback(RuntimeException re) {
            System.out.println("fallback");
            return 2;
        }
    
    }
    
    @Service
    class UserService {
    
        @CircuitBreaker(include = RuntimeException.class)
        public int getNumber() {
            System.out.println("getNumber");
            throw new RuntimeException();
        }
    
    }
    

    getNumber
    getNumber
    getNumber
    fallback
    

    或者,您可能希望将重试放在断路器中,具体取决于您想要的行为。

    【讨论】:

    • 感谢您的建议。我尝试将注释(Circuitbreaker 和 Recover)移动到接口,并尝试将 proxyTargetClass 设置为 true。这些解决方案都不起作用。如果我用 Retryable 替换 Circuitbreaker,它会重试。但我失去了断路器功能。
    • 在某个地方发布一个小而完整的项目,展示这种行为,我会看看。
    • 这里是示例应用程序:github.com/pdilip1/spring-circuit-breaker。我故意从服务方法中抛出一个异常来触发断路器。正如我在帖子中提到的,断路器不会重试,但如果我将断路器更改为可重试,它会重试。请看DemoApplication.java(spring boot主应用类),这个类也有controller和service类。
    • 你误解了断路器的工作原理;它是一个有状态的重试拦截器,可以在一些调用失败后打开。请参阅我的答案的编辑。
    • 感谢您的澄清。您提供的同时实现(断路器和重试)的解决方案效果很好!
    猜你喜欢
    • 2018-05-28
    • 2020-09-10
    • 2021-01-23
    • 2020-07-23
    • 2017-08-16
    • 1970-01-01
    • 2021-06-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多