不遵守色带重试属性

Ribbon Retry properties not respected

我有一个 zuul 网关应用程序,它从客户端应用程序接收请求并使用负载平衡 rest 模板将请求转发到具有 2 个端点的微服务,比如端点 1 和端点 2(两个端点之间的负载平衡轮robbin 目前还可以,但我希望它是基于可用性的)。

这是我面临的问题 -

我是 netflix 组件堆栈的新手...如果我遗漏了一些明显的东西,请告知。谢谢

<?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.mycomp</groupId>
    <artifactId>zuul-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>zuul-gateway</name>
    <description>Spring Boot Zuul</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Edgware.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-lambda</artifactId>
            <version>1.11.242</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.3.10</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.netflix-commons</groupId>
            <artifactId>netflix-commons-util</artifactId>
            <version>0.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </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.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

我的 application.yml 如下所示 -

eureka:
client:
    healthcheck:
      enabled: true
    lease:
      duration: 5
    service-url:
      defaultZone: http://localhost:8761/eureka/

ingestWithOutEureka:
  ribbon:
    eureka:
      enabled: false
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
    listOfServers: http://demo-nlb-6a67d59c901ecd128.elb.us-west-2.amazonaws.com,http://demo-nlb-124321w2a123ecd128.elb.us-west-2.amazonaws.com
    okToRetryOnAllOperations: true
    ConnectTimeout: 500
    ReadTimeout: 1000
    MaxAutoRetries: 5
    MaxAutoRetriesNextServer: 5
    MaxTotalHttpConnections: 500
    MaxConnectionsPerHost: 100
    retryableStatusCodes: 404,503
    okhttp:
      enabled: true

zuul:
  debug:
    request: true
    parameter: true
  ignored-services: '*'
  routes:
    ingestServiceELB:
      path: /ingestWithoutEureka/ingest/**
      retryable: true
      url: http://dummyURL

management.security.enabled : false

spring:
  application:
    name: zuul-gateway
  cloud:
    loadbalancer:
      retry:
        enabled: true

logging:
  level:
    org:
      apache:
        http: DEBUG
    com:
      netflix: DEBUG

hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: THREAD
          thread:
            timeoutInMilliseconds: 60000

我的申请 class 如下所示

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulGatewayApplication {

    @Bean
    public InterceptionFilter addInterceptionFilter() {
        return new InterceptionFilter();
    }

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

最后我的 zuul 过滤器如下所示 - 包裹 com.zuulgateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.stream.Collectors;

public class InterceptionFilter extends ZuulFilter{
    private static final String REQUEST_PATH = "/ingestWithoutEureka";

    @LoadBalanced
    @Bean
    RestTemplate loadBalanced() {
        //RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }

    @Autowired
    @LoadBalanced
    private RestTemplate loadBalancedRestTemplate;

    @Override
    public String filterType() {
        return "route";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String method = request.getMethod();
        String requestURI = request.getRequestURI();
        return requestURI.startsWith(REQUEST_PATH);
    }

    @Override
    public Object run() {

        RequestContext ctx = RequestContext.getCurrentContext();
        try {
            String requestPayload = ctx.getRequest().getReader().lines().collect(Collectors.joining(System.lineSeparator()));

            String response = loadBalancedRestTemplate.postForObject("http://ingestWithOutEureka/ingest", requestPayload, String.class);

            ctx.setResponseStatusCode(200);
            ctx.setResponseBody(response);
        } catch (IOException e) {
            ctx.setResponseStatusCode(500);
            ctx.setResponseBody("{ \"error\" : " + e.getMessage() + " }");
            System.out.println("Exception during feign call - " + e.getMessage());
            e.printStackTrace();
        } finally {
            ctx.setSendZuulResponse(false);
            ctx.getResponse().setContentType("application/json");
        }

        return null;
    }
}

所以,这是对我有用的解决方案 -

问题 1 - 尽管配置了 ribbon.<client>.OkToRetryOnAllOperations: true,但重试仍无法正常工作。功能区显然忽略了我的配置。

解决方案: - 很奇怪,但经过一些调试后我注意到,只有在全局配置一开始就存在时,Ribbon 才会选择客户端级别的配置。

一旦我将全局 "OkToRetryOnAllOperations" 设置为 "true" 或 "false",如下所示,功能区开始按预期拾取 ribbon.<client>.OkToRetryOnAllOperations,我可以看到重试正在发生。

ribbon:
  OkToRetryOnAllOperations: false

问题 2 - 此外,即使在设置读取超时和连接超时配置后,我也没有看到功能区符合配置,仍然需要 2从服务器抛出错误的分钟数

解决方案 2 - 虽然在上面的解决方案 1 中建议的更改之后功能区开始重试请求,但我没有看到功能区支持 <client>.ribbon.ReadTimeout<client>.ribbon.ConnectTimeout

花了一些时间,我认为这是因为使用了RestTemplate

虽然 spring 文档提到您可以在使用 load balanced RestTemplate 时使用 load balanced RestTemplate for achieving retries, it does not mention that the timeouts wont work with it. Based on this SO answer from 2014, it looks like while ribbon has been added as a interceptor 来实现 serviceId 到 URI 的解析,但功能区不使用底层 HTTP 客户端和使用 RestTemplate 提供的 http 客户端。因此,功能区特定 <client>.ribbon.ReadTimeout<client>.ribbon.ConnectTimeout 不被接受。在我将超时添加到 RestTemplate 之后,请求开始以预期的时间间隔超时。

最后, 问题 3 - 我通过将自定义 http 客户端传递到 rest 模板来启用日志。

@LoadBalanced
@Bean
RestTemplate loadBalanced() {
    RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
    System.out.println("returning load balanced rest client");
    ((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setReadTimeout(1000*30);
    ((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectTimeout(1000*3);
    ((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectionRequestTimeout(1000*3);
    return restTemplate;
}

@Bean
LoadBalancedBackOffPolicyFactory backOffPolicyFactory() {
    return new LoadBalancedBackOffPolicyFactory() {
        @Override
        public BackOffPolicy createBackOffPolicy(String service) {
            return new ExponentialBackOffPolicy();
        }
    };
}

通过所有更改,我看到请求重试正在发生,超时和指数退避以及请求/响应日志按预期可见。祝你好运!