Limitação de taxa no Spring Cloud Netflix Zuul

Limitação de taxa no Spring Cloud Netflix Zuul

1. Introdução

Spring Cloud Netflix Zuul é um gateway de código aberto que envolveNetflix Zuul. Ele adiciona alguns recursos específicos para aplicativos Spring Boot. Infelizmente, a limitação de taxa não é fornecida imediatamente.

Neste tutorial, exploraremosSpring Cloud Zuul RateLimit, que adiciona suporte para solicitações de limitação de taxa.

2. Configuração do Maven

Além da dependência do Spring Cloud Netflix Zuul, precisamos adicionarSpring Cloud Zuul RateLimit aopom.xml do nosso aplicativo:


    org.springframework.cloud
    spring-cloud-starter-netflix-zuul


    com.marcosbarbero.cloud
    spring-cloud-zuul-ratelimit
    2.2.0.RELEASE

3. Controlador de exemplo

Em primeiro lugar, vamos criar alguns endpoints REST aos quais aplicaremos os limites de taxa.

Abaixo está uma classe simples do Spring Controller com dois pontos de extremidade:

@Controller
@RequestMapping("/greeting")
public class GreetingController {

    @GetMapping("/simple")
    public ResponseEntity getSimple() {
        return ResponseEntity.ok("Hi!");
    }

    @GetMapping("/advanced")
    public ResponseEntity getAdvanced() {
        return ResponseEntity.ok("Hello, how you doing?");
    }
}

Como podemos ver, não há código específico para a taxa limite dos pontos finais. Isso ocorre porque vamos configurar isso em nossas propriedades Zuul dentro do arquivoapplication.yml. Assim, mantendo nosso código desacoplado.

4. Propriedades Zuul

Em segundo lugar, vamos adicionar as seguintes propriedades Zuul em nosso arquivoapplication.yml:

zuul:
  routes:
    serviceSimple:
      path: /greeting/simple
      url: forward:/
    serviceAdvanced:
      path: /greeting/advanced
      url: forward:/
  ratelimit:
    enabled: true
    repository: JPA
    policy-list:
      serviceSimple:
        - limit: 5
          refresh-interval: 60
          type:
            - origin
      serviceAdvanced:
        - limit: 1
          refresh-interval: 2
          type:
            - origin
  strip-prefix: true

Emzuul.routes, fornecemos os detalhes do endpoint. E emzuul.ratelimit.policy-list,, fornecemos as configurações de limite de taxa para nossos terminais. A propriedadelimit especifica o número de vezes que o terminal pode ser chamado emrefresh-interval.

Como podemos ver, adicionamos um limite de taxa de 5 solicitações por 60 segundos para o endpointserviceSimple. Em contraste,serviceAdvanced tem um limite de taxa de 1 solicitação por 2 segundos.

A configuração detype especifica qual abordagem de limite de taxa queremos seguir. Aqui estão os valores possíveis:

  • origin - limite de taxa com base na solicitação de origem do usuário

  • url - limite de taxa com base no caminho de solicitação do serviço downstream

  • user - limite de taxa com base no nome de usuário autenticado ou 'anônimo'

  • Sem valor - atua como uma configuração global por serviço. Para usar essa abordagem, apenas não defina o parâmetro 'type'

5. Testando o limite de taxa

5.1. Solicitação dentro do limite de taxa

A seguir, vamos testar o limite de taxa:

@Test
public void whenRequestNotExceedingCapacity_thenReturnOkResponse() {
    ResponseEntity response = restTemplate.getForEntity(SIMPLE_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application_serviceSimple_127.0.0.1";

    assertEquals("5", headers.getFirst(HEADER_LIMIT + key));
    assertEquals("4", headers.getFirst(HEADER_REMAINING + key));
    assertEquals("60000", headers.getFirst(HEADER_RESET + key));
}

Aqui, fazemos uma única chamada para o ponto final/greeting/simple. A solicitação foi bem-sucedida, pois está dentro do limite da taxa.

Outro ponto importante é quewith each response we get back headers providing us with further information on the rate limit. para a solicitação acima, obteríamos os seguintes cabeçalhos:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 4
X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 60000

Em outras palavras:

  • X-RateLimit-Limit-[key]:limit configurado para o endpoint

  • X-RateLimit-Remaining-[key]: o número restante de tentativas de chamar o terminal

  • X-RateLimit-Reset-[key]: o número restante de milissegundos dorefresh-interval configurado para o ponto final

Além disso, se dispararmos imediatamente o mesmo terminal novamente, poderemos obter:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 3
X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 57031

Observe a diminuição do número restante de tentativas e o número restante de milissegundos.

5.2. Solicitação que ultrapassa o limite de taxa

Vamos ver o que acontece quando ultrapassamos o limite da taxa:

@Test
public void whenRequestExceedingCapacity_thenReturnTooManyRequestsResponse() throws InterruptedException {
    ResponseEntity response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());

    for (int i = 0; i < 2; i++) {
        response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    }

    assertEquals(TOO_MANY_REQUESTS, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application_serviceAdvanced_127.0.0.1";

    assertEquals("1", headers.getFirst(HEADER_LIMIT + key));
    assertEquals("0", headers.getFirst(HEADER_REMAINING + key));
    assertNotEquals("2000", headers.getFirst(HEADER_RESET + key));

    TimeUnit.SECONDS.sleep(2);

    response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());
}

Aqui, chamamos o ponto final de/greeting/advanced duas vezes em sucessões rápidas. Como configuramos o limite de taxa como uma solicitação por 2 segundos,the second call will fail. Como resultado, o código de erro429 (Too Many Requests) é retornado ao cliente.

Abaixo estão os cabeçalhos retornados quando o limite de taxa é atingido:

X-RateLimit-Limit-rate-limit-application_serviceAdvanced_127.0.0.1: 1
X-RateLimit-Remaining-rate-limit-application_serviceAdvanced_127.0.0.1: 0
X-RateLimit-Reset-rate-limit-application_serviceAdvanced_127.0.0.1: 268

Depois disso, dormimos por 2 segundos. Este é orefresh-interval configurado para o nó de extremidade. Finalmente, disparamos o terminal novamente e obtemos uma resposta bem-sucedida.

6. Gerador de chave personalizado

We can customize the keys sent in the response header using a custom key generator. Isso é útil porque o aplicativo pode precisar controlar a estratégia-chave além das opções oferecidas pela propriedadetype.

Por exemplo, isso pode ser feito criando uma implementaçãoRateLimitKeyGenerator personalizada. Podemos adicionar outros qualificadores ou algo completamente diferente:

@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties,
  RateLimitUtils rateLimitUtils) {
    return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
        @Override
        public String key(HttpServletRequest request, Route route,
          RateLimitProperties.Policy policy) {
            return super.key(request, route, policy) + "_" + request.getMethod();
        }
    };
}

O código acima anexa o nome do método REST à chave. Por exemplo:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1_GET: 5

Outro ponto importante é que o beanRateLimitKeyGenerator será configurado automaticamente porspring-cloud-zuul-ratelimit.

7. Tratamento de erro personalizado

A estrutura suporta várias implementações para armazenamento de dados com limite de taxa. Por exemplo, Spring Data JPA e Redis são fornecidos. Por padrão,failures are just logged as errors usando a classeDefaultRateLimiterErrorHandler.

Quando precisamos lidar com os erros de maneira diferente, podemos definir um beanRateLimiterErrorHandler personalizado:

@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
    return new DefaultRateLimiterErrorHandler() {
        @Override
        public void handleSaveError(String key, Exception e) {
            // implementation
        }

        @Override
        public void handleFetchError(String key, Exception e) {
            // implementation
        }

        @Override
        public void handleError(String msg, Exception e) {
            // implementation
        }
    };
}

Semelhante ao beanRateLimitKeyGenerator, o beanRateLimiterErrorHandler também será configurado automaticamente.

8. Conclusão

Neste artigo, vimos como classificar APIs de limite usando o Spring Cloud Netflix Zuul e o Spring Cloud Zuul RateLimit.

Como sempre, o código completo deste artigo pode ser encontradoover on GitHub.