【问题标题】:Spring Cloud Gateway 500 when an instance is down实例关闭时的 Spring Cloud Gateway 500
【发布时间】:2021-02-03 00:47:38
【问题描述】:

我有一个使用 Spring Cloud 负载均衡器(Spring Cloud 版本:Hoxton.SR6)的 Spring Cloud Gateway(eureka 客户端)应用程序,并且我有一个 Spring Boot 应用程序实例(启用了正常关闭的 Spring Boot 2.3,(eureka客户)。

当我关闭 Spring Boot 服务并通过网关执行请求时,网关会抛出 500 错误(连接被拒绝),而不是 503。1-2 分钟后出现 503。

谁能澄清这是否是预期的行为?

似乎问题来自eureka-client(在我的情况下是1.9.21版本) AtomicReference<Applications> localRegionApps 不经常更新

谢谢!

更新: 我决定更深入地检查这个 500 错误。结果是我的系统(ubuntu)如果不使用端口就会报这个错误:

curl -v localhost:9722
 Rebuilt URL to: localhost:9722/
   Trying 127.0.0.1...
 TCP_NODELAY set
 connect to 127.0.0.1 port 9722 failed: Connection refused
 Failed to connect to localhost port 9722: Connection refused
 Closing connection 0

所以我把我的 application.yml:

spring:
  cloud:
    gateway:
      routes:
        - id: my_route
          uri: http://localhost:9722/

然后,当我的请求被路由到 my_route 并且没有应用程序使用 9722 时,我收到错误:

io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: localhost/127.0.0.1:9722
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ HTTP GET "/internal/mail/internal/health-check" [ExceptionHandlingWebHandler]
Stack trace:
Caused by: java.net.ConnectException: finishConnect(..) failed: Connection refused
    at io.netty.channel.unix.Errors.throwConnectException(Errors.java:124)
    at io.netty.channel.unix.Socket.finishConnect(Socket.java:251)
    at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.doFinishConnect(AbstractEpollChannel.java:672)
    at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.finishConnect(AbstractEpollChannel.java:649)
    at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollOutReady(AbstractEpollChannel.java:529)
    at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:465)
    at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:834)

这似乎是一个意外的异常,因为无法使用断路器或任何网关过滤器来处理它。

是否可以正确处理此错误?在这种情况下我想返回 503

【问题讨论】:

    标签: spring-cloud spring-cloud-gateway


    【解决方案1】:

    你应该使用Cloud Circuit Breaker。

    为此:

    1. 在你的 pom 中声明相应的 starter:

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
       </dependency>
      
    2. 在application.yaml中声明断路器

      spring:
        cloud:
          gateway:
            routes:
              - id: my_route
                uri: http://localhost:9722/
                filters:
                  - name: CircuitBreaker
                    args:
                      name: myCircuitBreaker
                      fallbackUri: forward:/inCaseOfFailureUseThis
      
    3. 声明在失败(例如连接错误)的情况下将调用的端点

      @RequestMapping("/inCaseOfFailureUseThis")
          public Mono<ResponseEntity<String>> inCaseOfFailureUseThis() {
              return Mono.just(ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("body for service failure case"));
      }
      

    【讨论】:

      【解决方案2】:

      将特定异常映射到特定 HTTP 状态代码的最简单方法之一是提供 org.springframework.boot.web.reactive.error.ErrorAttributes 类型的自定义 bean。这是一个例子:

      @Bean
      public ErrorAttributes errorAttributes() {
          return new CustomErrorAttributes(httpStatusExceptionTypeMapper);
      }
      
      public class CustomErrorAttributes extends DefaultErrorAttributes {
          @Override
          public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
              Map<String, Object> attributes = super.getErrorAttributes(request, options);
              Throwable error = getError(request);
              MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations
                  .from(error.getClass(), MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
              HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation);
              attributes.put("status", errorStatus.value());
              return attributes;
          }
      
          private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
              if (error instanceof ResponseStatusException) {
                  return ((ResponseStatusException) error).getStatus();
              }
              return responseStatusAnnotation.getValue("code", HttpStatus.class).orElseGet(() -> {
                 if (error instanceof java.net.ConnectException) {
                     return HttpStatus.SERVICE_UNAVAILABLE;
                 }
                 return HttpStatus.INTERNAL_SERVER_ERROR;
              }
          }
      }
      

      【讨论】:

      • 不需要从超类重复逻辑。只需if (getError(request) instanceof ConnectException) attributes.put("status",SERVICE_UNAVAILABLE);
      【解决方案3】:

      尝试定义您的自定义 ErrorWebExceptionHandler。 看: org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler

      【讨论】:

        猜你喜欢
        • 2016-06-15
        • 2019-12-26
        • 2019-05-10
        • 2020-09-14
        • 2018-04-23
        • 2019-03-25
        • 2021-12-28
        • 2017-07-27
        • 2019-07-04
        相关资源
        最近更新 更多