Ограничение скорости в Spring Cloud Netflix Zuul

Ограничение тарифов в Spring Cloud Netflix Zuul

1. Вступление

Spring Cloud Netflix Zuul - это шлюз с открытым исходным кодом, который обертываетNetflix Zuul. Он добавляет некоторые специфические функции для приложений Spring Boot. К сожалению, ограничение скорости не предусмотрено из коробки.

В этом руководстве мы рассмотримSpring Cloud Zuul RateLimit, который добавляет поддержку запросов с ограничением скорости.

2. Конфигурация Maven

В дополнение к зависимости Spring Cloud Netflix Zuul нам нужно добавитьSpring Cloud Zuul RateLimit кpom.xml нашего приложения:


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


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

3. Пример контроллера

Во-первых, давайте создадим пару конечных точек REST, к которым мы применим ограничения скорости.

Ниже приведен простой класс Spring Controller с двумя конечными точками:

@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?");
    }
}

Как мы видим, не существует специального кода для ограничения скорости конечных точек. Это потому, что мы настроим это в наших свойствах Zuul в файлеapplication.yml. Таким образом, сохраняя наш код отделенным.

4. Зуул Недвижимость

Во-вторых, давайте добавим следующие свойства Zuul в наш файлapplication.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

В разделеzuul.routes мы предоставляем подробную информацию о конечной точке. А в разделеzuul.ratelimit.policy-list, мы предоставляем конфигурации ограничения скорости для наших конечных точек. Свойствоlimit указывает, сколько раз конечная точка может быть вызвана в пределахrefresh-interval.

Как мы видим, мы добавили ограничение скорости в 5 запросов в 60 секунд для конечной точкиserviceSimple. Напротив,serviceAdvanced имеет ограничение скорости 1 запрос в 2 секунды.

Конфигурацияtype указывает, какой подход к ограничению скорости мы хотим использовать. Вот возможные значения:

  • origin - ограничение скорости на основе запроса происхождения пользователя

  • url - ограничение скорости в зависимости от пути запроса нисходящей службы

  • user - ограничение скорости на основе аутентифицированного имени пользователя или «анонимного»

  • Нет значения - действует как глобальная конфигурация для каждой службы. Чтобы использовать этот подход, просто не устанавливайте параметр «тип»

5. Проверка предела скорости

5.1. Запрос в пределах установленной скорости

Затем давайте проверим ограничение скорости:

@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));
}

Здесь мы делаем одиночный вызов конечной точке/greeting/simple. Запрос выполнен успешно, так как он находится в пределах нормы.

Другой ключевой момент заключается в том, чтоwith each response we get back headers providing us with further information on the rate limit. Для вышеуказанного запроса мы получим следующие заголовки:

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

Другими словами:

  • X-RateLimit-Limit-[key]:limit, настроенный для конечной точки

  • X-RateLimit-Remaining-[key]: оставшееся количество попыток дозвона до конечной точки

  • X-RateLimit-Reset-[key]: оставшееся количество миллисекундrefresh-interval, настроенное для конечной точки

Кроме того, если мы немедленно запустим ту же самую конечную точку снова, мы можем получить:

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

Обратите внимание на уменьшенное количество оставшихся попыток и оставшееся количество миллисекунд.

5.2. Запрос на превышение лимита скорости

Посмотрим, что произойдет, если мы превысим лимит скорости:

@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());
}

Здесь мы дважды последовательно вызываем конечную точку/greeting/advanced. Поскольку мы настроили ограничение скорости как один запрос в 2 секунды,the second call will fail. В результате клиенту возвращается код ошибки429 (Too Many Requests).

Ниже приведены заголовки, возвращаемые при достижении ограничения скорости:

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

После этого мы спим 2 секунды. Этоrefresh-interval, настроенный для конечной точки. Наконец, мы снова запускаем конечную точку и получаем успешный ответ.

6. Пользовательский генератор ключей

We can customize the keys sent in the response header using a custom key generator. Это полезно, потому что приложению может потребоваться управление ключевой стратегией помимо опций, предлагаемых свойствомtype.

Например, это можно сделать, создав собственную реализациюRateLimitKeyGenerator. Мы можем добавить дополнительные классификаторы или что-то совершенно другое:

@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();
        }
    };
}

Приведенный выше код добавляет имя метода REST к ключу. Например:

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

Другой ключевой момент заключается в том, что компонентRateLimitKeyGenerator будет автоматически настроенspring-cloud-zuul-ratelimit.

7. Пользовательская обработка ошибок

Каркас поддерживает различные реализации для хранения данных с ограничением скорости. Например, Spring Data JPA и Redis предоставляются. По умолчаниюfailures are just logged as errors использует классDefaultRateLimiterErrorHandler.

Когда нам нужно обрабатывать ошибки по-другому, мы можем определить собственный bean-компонентRateLimiterErrorHandler:

@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
        }
    };
}

Как и bean-компонентRateLimitKeyGenerator, bean-компонентRateLimiterErrorHandler также будет автоматически настроен.

8. Заключение

В этой статье мы увидели, как оценивать API-интерфейсы ограничения с помощью Spring Cloud Netflix Zuul и Spring Cloud Zuul RateLimit.

Как всегда, полный код этой статьи можно найти наover on GitHub.