«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 нашего приложения:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
3. Пример контроллера ~~ ~ Во-первых, давайте создадим пару конечных точек REST, к которым мы применим ограничения скорости.
Ниже приведен простой класс контроллера Spring с двумя конечными точками:
Как мы видим, нет специального кода для ограничения скорости конечных точек. Это потому, что мы настроим это в наших свойствах Zuul в файле application.yml. Таким образом, сохраняя наш код несвязанным.
@Controller
@RequestMapping("/greeting")
public class GreetingController {
@GetMapping("/simple")
public ResponseEntity<String> getSimple() {
return ResponseEntity.ok("Hi!");
}
@GetMapping("/advanced")
public ResponseEntity<String> getAdvanced() {
return ResponseEntity.ok("Hello, how you doing?");
}
}
4. Свойства Zuul
Во-вторых, давайте добавим следующие свойства Zuul в наш файл application.yml:
В zuul.routes мы предоставляем сведения о конечной точке. И в zuul.ratelimit.policy-list мы предоставляем конфигурации ограничения скорости для наших конечных точек. Свойство limit определяет количество вызовов конечной точки в течение интервала обновления.
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
Как мы видим, мы добавили ограничение скорости 5 запросов в 60 секунд для конечной точки serviceSimple. Напротив, serviceAdvanced имеет ограничение скорости 1 запрос в 2 секунды.
Конфигурация типа указывает, какой подход к ограничению скорости мы хотим использовать. Вот возможные значения:
origin — ограничение скорости на основе URL-адреса запроса источника пользователя — ограничение скорости на основе пути запроса нижестоящего пользователя службы — ограничение скорости на основе аутентифицированного имени пользователя или — ˜anonymous’ Нет значения — действует как глобальная конфигурация для каждой службы. Чтобы использовать этот подход, просто не устанавливайте параметр «type»
-
5. Тестирование ограничения скорости
5.1. Запрос в пределах ограничения скорости
Далее, давайте проверим ограничение скорости:
Здесь мы делаем один вызов конечной точке /greeting/simple. Запрос выполнен успешно, поскольку он находится в пределах ограничения скорости.
@Test
public void whenRequestNotExceedingCapacity_thenReturnOkResponse() {
ResponseEntity<String> 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));
assertThat(
parseInt(headers.getFirst(HEADER_RESET + key)),
is(both(greaterThanOrEqualTo(0)).and(lessThanOrEqualTo(60000)))
);
}
Другим ключевым моментом является то, что с каждым ответом мы получаем заголовки, содержащие дополнительную информацию об ограничении скорости. Для приведенного выше запроса мы получим следующие заголовки:
Другими словами:
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]: лимит, настроенный для конечной точки X-RateLimit-Remaining-[key]: оставшееся количество попыток вызова конечной точки X-RateLimit-Reset-[key]: оставшееся количество миллисекунд интервала обновления, настроенного для конечной точки
-
Кроме того, если мы немедленно снова запустим ту же конечную точку, мы можем получить :
Обратите внимание на уменьшение оставшегося количества попыток и оставшегося количества миллисекунд.
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. Запрос, превышающий лимит скорости
Давайте посмотрим, что происходит, когда мы превышаем лимит скорости:
Здесь мы вызываем конечную точку /greeting/advanced дважды в быстрой последовательности. Поскольку мы настроили ограничение скорости как один запрос в 2 секунды, второй вызов не будет выполнен. В результате клиенту возвращается код ошибки 429 (слишком много запросов).
@Test
public void whenRequestExceedingCapacity_thenReturnTooManyRequestsResponse() throws InterruptedException {
ResponseEntity<String> 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());
}
Ниже приведены заголовки, возвращаемые при достижении ограничения скорости:
После этого мы спим в течение 2 секунд. Это интервал обновления, настроенный для конечной точки. Наконец, мы снова запускаем конечную точку и получаем успешный ответ.
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
6. Генератор пользовательских ключей
Мы можем настроить ключи, отправляемые в заголовке ответа, с помощью собственного генератора ключей. Это полезно, потому что приложению может потребоваться управлять ключевой стратегией помимо опций, предлагаемых свойством type.
Например, это можно сделать, создав собственную реализацию RateLimitKeyGenerator. Мы можем добавить дополнительные квалификаторы или что-то совершенно другое:
Приведенный выше код добавляет имя метода REST к ключу. Например:
@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();
}
};
}
Другим ключевым моментом является то, что bean-компонент RateLimitKeyGenerator будет автоматически настроен с помощью spring-cloud-zuul-ratelimit.
X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1_GET: 5
7. Пользовательская обработка ошибок
«Платформа поддерживает различные реализации для хранения данных с ограничением скорости. Например, предоставляются Spring Data JPA и Redis. По умолчанию сбои просто регистрируются как ошибки с использованием класса DefaultRateLimiterErrorHandler.
Когда нам нужно по-другому обрабатывать ошибки, мы можем определить собственный bean-компонент RateLimiterErrorHandler:
Подобно bean-компоненту RateLimitKeyGenerator, 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
}
};
}
8. Заключение
В этой статье мы увидели, как ограничить API с помощью Spring Cloud Netflix Zuul и Spring Cloud Zuul RateLimit.
Как всегда, полный код этой статьи можно найти на GitHub.
«