По мере роста популярности сервиса и увеличения объема трафика мы сталкиваемся с новыми проблемами масштабируемости. Задумайтесь над следующими вопросами: как мы можем обеспечить справедливое распределение ресурсов между клиентами? Какие стратегии можем применить, чтобы поддерживать стабильную стоимость использования ресурсов для бизнес-целей? Как защитить систему от вредоносных вмешательств, таких как DDoS-атаки?
По мере масштабирования бизнеса эти вопросы становятся все более важными. Одним из эффективных методов решения таких проблем является ограничение скорости (rate limiting), также известное как дросселирование. С помощью контроля скорости обработки запросов технология rate limiting обеспечивает справедливое распределение ресурсов между клиентами, повышает безопасность системы и помогает эффективнее управлять расходами.
Что такое ограничение скорости?
Ограничение скорости API — критически важный прием, позволяющий ограничить количество запросов, которые клиент может сделать к сервису в течение определенного времени, обычно измеряемого в секундах. Эта технология имеет решающее значение при разработке отказоустойчивых сервисов, поскольку она помогает защитить ресурсы сервера, обеспечивая их справедливое использование в системе.
Ограничение скорости работает путем наложения лимита на клиентский трафик. Это достигается с помощью различных известных алгоритмов, которые мы рассмотрим ниже. Если клиент превысил лимит скорости, его запросы временно отклоняются, пока лимит не сбросится через заданный интервал времени. Концепция проста: разрешить определенное количество запросов от клиента в течение заданного периода. Однако правильная реализация дросселирования сопряжена с определенными трудностями. Например, как следует реагировать на резкое повышение количества запросов («всплеск»)?
Частый вопрос, касающийся ограничения скорости, звучит так: «Почему я должен дросселировать клиентов, а не масштабировать систему, добавляя дополнительные серверы?» Причина заключается в практичности. Дросселирование должно осуществляться одновременно с масштабированием. В то время как масштабирование требует времени, дросселирование используется для защиты системы в этот период. Так, если добавление нового сервера в парк серверов занимает пять минут, разрешение неограниченного трафика в течение этого времени ухудшит производительность системы.
Зачем ограничивать скорость для клиентов?
Работа сервисов зависит от аппаратного обеспечения. Представьте, что вы являетесь частью команды, которая управляет сервисом на 1000 серверах, используемых множеством клиентов. Если не ограничивать скорость клиентских запросов, может возникнуть ряд проблем. Во-первых, несколько клиентов могут монополизировать ресурсы сервера чрезмерным количеством запросов, что приводит к увеличению задержек и ошибок, негативно влияя на опыт других клиентов. Во-вторых, ошибки на стороне клиента, такие как бесконечные циклы, отправляющие недействительный трафик, могут вызвать поток «неправильного» трафика, что потенциально приведет к перегрузке сервиса. Чтобы уменьшить эти проблемы, необходимо внедрить ограничение скорости на сервисе.
Ограничение скорости дает несколько ключевых преимуществ.
- Предотвращение перегрузки ресурсов сервера одним клиентом, что позволяет избежать эффекта «шумного соседа» и обеспечить справедливое распределение ресурсов между клиентами.
- Поддержание предсказуемой производительности системы. Это имеет решающее значение для сервисов, предоставляющих SLA (соглашения об уровне услуг), такие как время доступности. Контроль входящего трафика важен для соблюдения этих SLA.
- Управление расходами. Еще одно существенное преимущество, особенно при использовании платных внешних API. Ограничение скорости помогает управлять расходами, контролируя частоту вызовов API.
- Ограничение скорости обеспечивает управляемый и постоянный трафик, улучшая время отклика и общую надежность сервиса.
- Смягчение последствий DDoS-атак посредством снижения воздействия на системные ресурсы (хотя ограничение скорости и не является комплексным решением для борьбы с такими атаками).
Преимущества и само значение ограничения скорости вызовов позволяют широко использовать его для большинства публичных API. Как правило, в документации указывается допустимая частота вызовов API с возможностью повышения лимитов в зависимости от уровня подписки. Вот пример ограничения скорости в Twitter:
Как ограничивать скорость?
Ограничение скорости включает несколько ключевых шагов. Во-первых, важно определить инициатора запроса по входящим запросам. Хотя для этого существуют различные методы, наиболее распространенным является использование идентификатора пользователя. В многопользовательских системах можно также провести идентификацию по идентификатору учетной записи (тенант). Еще один вариант — использовать в качестве идентификатора IP-адрес. Однако важно отметить, что IP-адреса не всегда надежны для уникальной идентификации, поскольку несколько инициаторов вызовов могут использовать один и тот же IP-адрес, как это обычно делают интернет-провайдеры.
После определения инициатора вызова следующим шагом будет установление соответствующих ограничений скорости, например 1000 запросов в секунду. Определение подходящего лимита в значительной степени зависит от конкретных требований и предполагает тщательное тестирование и анализ метрик. Помните, что ограничения скорости не обязательно должны быть одинаковыми для всех вызывающих. Лимиты могут различаться в зависимости от уровней подписки или особых требований для важных клиентов. При превышении лимита клиенты должны получать HTTP-код состояния 429 Too Many Requests
. Также полезно включать заголовки ответа, информирующие пользователя о лимите и времени сброса.
В заключение необходимо решить, где применять ограничение скорости. Для этого есть такие варианты, как уровень сервиса, использование промежуточного ПО в конвейере запросов или уровень обратного прокси/балансировщика нагрузки. У каждого способа есть свои преимущества и недостатки. Реализация ограничения скорости на уровне обратного прокси позволяет использовать встроенные функции прокси, например сам алгоритм ограничения скорости. Однако при таком подходе может не учитываться подробный контекст приложения, что затрудняет реализацию сложной логики. И наоборот, применение ограничения скорости на уровне сервиса обеспечивает полный контекст для сложной логики, но увеличивает нагрузку на сервер. На практике, однако, эти накладные расходы минимальны, поскольку большинство операций выполняется в памяти.
Алгоритмы ограничения скорости
Существует несколько алгоритмов ограничения скорости. Рассмотрим два наиболее важных из них, которые часто используются в большинстве сценариев.
Счетчик фиксированного окна
Этот алгоритм, несмотря на свою простоту, очень эффективен. Он работает по принципу разделения времени на фиксированные интервалы, например в одну секунду, а затем ограничивает количество запросов, которое может сделать абонент в течение каждого интервала. Если абонент превысит этот лимит, все дополнительные запросы в течение этого периода будут отклонены (подвергнуты дросселированию) до начала следующего интервала. Так, можно поставить ограничение на максимум 1000 запросов в секунду.
Плюсы. Это эффективный алгоритм ограничения скорости с простой реализацией.
Минусы. Существенным недостатком этого метода является его неспособность учитывать «всплески» запросов. Трафик от клиентов часто меняется, и во многих ситуациях желательно учитывать эти колебания, включая «всплески» трафика. К сожалению, данный алгоритм не рассчитан на такие случаи. Возьмем, к примеру, пользователя A (user A
), который обычно использует только 100 из выделенных ему 1000 запросов в секунду. Однако раз в час происходит «всплеск» из 2000 вызовов за одну секунду. При использовании алгоритма счетчика фиксированного окна мы бы неизбежно вынуждены были дросселировать пользователя A в эти критические моменты повышенного количества вызовов.
Реализация кода
Обратите внимание: приведенные примеры кода реализованы на языке Java. Однако выбор языка программирования не имеет принципиального значения, поскольку эти примеры не имеют отношения к специфике того или иного языка. Наша цель — изучить основные этапы реализации данного алгоритма. Важно также отметить, что представленные здесь реализации не готовы к промышленному использованию. В них отсутствуют такие важные функции, как синхронизация и поддержка многопоточности, что чрезвычайно важно для кода, развернутого в производственной среде. Код находится в свободном доступе на GitHub для отсылок и использования в будущем.
Прежде всего, нам нужен класс, предназначенный для сбора информации о конкретном идентификаторе ограничения скорости, который соответствует инициатору вызова. Этот класс будет в первую очередь фокусироваться на двух аспектах: обрабатывающая способность (исходя из лимита скорости) и временная метка текущего окна, которая будет сравниваться с временной меткой в данный момент.
import java.time.Instant; public class WindowRateLimitKeyDescriptor { private final String key; private int rateLimitCapacityUsed; private Instant lastWindowTimestamp; public WindowRateLimitKeyDescriptor(String key, int rateLimitCapacityUsed, Instant lastWindowTimestamp) { this.key = key; this.rateLimitCapacityUsed = rateLimitCapacityUsed; this.lastWindowTimestamp = lastWindowTimestamp; } public String getKey() { return key; } public int getRateLimitCapacityUsed() { return rateLimitCapacityUsed; } public void setRateLimitCapacityUsed(int rateLimitCapacityUsed) { this.rateLimitCapacityUsed = rateLimitCapacityUsed; } public void incrementRateLimitCapacityUsed() { this.rateLimitCapacityUsed++; } public Instant getLastWindowTimestamp() { return lastWindowTimestamp; } public void setLastWindowTimestamp(Instant lastWindowTimestamp) { this.lastWindowTimestamp = lastWindowTimestamp; } }
Далее создадим класс ограничителя скорости, содержащий метод isRequestAllowed
, который определяет, должен ли запрос быть разрешен или дросселирован. Обратите внимание: интерфейс RateLimiter
определяет метод isRequestAllowed
, который является общим для всех алгоритмов ограничения скорости.
import java.time.Instant; import java.util.HashMap; import java.util.Map; public class FixedWindowRateLimiter implements RateLimiter { private final int rateLimit; private final long windowMillisecondsInterval; private final TimestampProvider timestampProvider; private final Map<String, WindowRateLimitKeyDescriptor> rateLimitKeyDescriptorMap = new HashMap<>(); public FixedWindowRateLimiter(int rateLimit, long windowMillisecondsInterval, TimestampProvider timestampProvider) { this.rateLimit = rateLimit; this.windowMillisecondsInterval = windowMillisecondsInterval; this.timestampProvider = timestampProvider; } public boolean isRequestAllowed(String key) { if (!rateLimitKeyDescriptorMap.containsKey(key)) { insertRateLimitDescriptor(key); } WindowRateLimitKeyDescriptor rateLimitKeyDescriptor = rateLimitKeyDescriptorMap.get(key); Instant currentTimestamp = timestampProvider.get(); if(isWindowExpired(rateLimitKeyDescriptor, currentTimestamp)) { resetKeyDescriptorUsage(rateLimitKeyDescriptor, currentTimestamp); } rateLimitKeyDescriptor.incrementRateLimitCapacityUsed(); return rateLimitKeyDescriptor.getRateLimitCapacityUsed() <= rateLimit; } private void insertRateLimitDescriptor(String key) { rateLimitKeyDescriptorMap.put(key, new WindowRateLimitKeyDescriptor(key, 0, timestampProvider.get())); } private boolean isWindowExpired(WindowRateLimitKeyDescriptor rateLimitKeyDescriptor, Instant currentTimestamp) { return currentTimestamp.toEpochMilli() - rateLimitKeyDescriptor.getLastWindowTimestamp().toEpochMilli() > windowMillisecondsInterval; } private void resetKeyDescriptorUsage(WindowRateLimitKeyDescriptor rateLimitKeyDescriptor, Instant currentTimestamp) { rateLimitKeyDescriptor.setRateLimitCapacityUsed(0); rateLimitKeyDescriptor.setLastWindowTimestamp(currentTimestamp); } }
Основная функциональность заключается в проверке того, не истек ли срок действия окна и сбросе информации об инициаторе вызова в соответствии с этим. Далее увеличиваем обрабатывающую способность и оцениваем, достаточно ли ее осталось в окне для одобрения запроса.
Перейдем к добавлению тестов. Я использовал Mockito, чтобы смоделировать поведение провайдера временных меток.
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.time.Duration;
import java.time.Instant;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class FixedWindowRateLimiterTest {
private final TimestampProvider timestampProvider = Mockito.mock(TimestampProvider.class);
@Test
void isRequestAllowed_shouldAllowRequest_whenWithinRateLimit() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.now());
FixedWindowRateLimiter fixedWindowRateLimiter = new FixedWindowRateLimiter(100, Duration.ofSeconds(1).toMillis(), timestampProvider);
String rateLimitKey = "user1";
assertTrue(fixedWindowRateLimiter.isRequestAllowed(rateLimitKey));
assertTrue(fixedWindowRateLimiter.isRequestAllowed(rateLimitKey));
assertTrue(fixedWindowRateLimiter.isRequestAllowed(rateLimitKey));
}
@Test
void isRequestAllowed_shouldDenyRequest_whenRateLimitExceeded() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.now());
FixedWindowRateLimiter fixedWindowRateLimiter = new FixedWindowRateLimiter(1, Duration.ofSeconds(1).toMillis(), timestampProvider);
String rateLimitKey = "user1";
assertTrue(fixedWindowRateLimiter.isRequestAllowed(rateLimitKey));
assertFalse(fixedWindowRateLimiter.isRequestAllowed(rateLimitKey)); // запрос нужно дросселировать, assertFalse
}
@Test
void isRequestAllowed_shouldAllowRequest_whenUsagePassedWindowExpiry() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.ofEpochMilli(0));
FixedWindowRateLimiter fixedWindowRateLimiter = new FixedWindowRateLimiter(1, Duration.ofSeconds(1).toMillis(), timestampProvider);
String rateLimitKey = "user1";
assertTrue(fixedWindowRateLimiter.isRequestAllowed(rateLimitKey));
Mockito.when(timestampProvider.get()).thenReturn(Instant.ofEpochMilli(2000)); // прошло 2 секунды
assertTrue(fixedWindowRateLimiter.isRequestAllowed(rateLimitKey)); // запрос не нужно дросселировать, так как окно сбрасывается
}
}
Алгоритм Token Bucket
Это самый популярный алгоритм ограничения скорости, который используется повсеместно. В его основе лежит концепция корзины, заполненной токенами. Каждому вызывающему предоставляется своеобразная корзина, содержащая токены, причем каждый запрос потребляет один токен. Токены пополняются постоянно (например, каждую секунду), и если в корзине, имеющей максимальную емкость, заканчиваются токены, новые запросы отклоняются до тех пор, пока не будут добавлены новые токены. Основное преимущество такого алгоритма заключается в накоплении токенов до установленного предела, что позволяет клиентам использовать их с разной скоростью, в том числе во время «всплесков» большого количества трафика.
Допустим, у пользователя A (user A
) есть корзина вместимостью 5000 токенов и скоростью пополнения 1000 токенов в секунду. Если в данный момент у него осталось 4000 токенов (вероятно, из-за медленного потребления, являющегося ниже скорости пополнения), он способен справиться с внезапным «всплеском» до 4000 запросов, пока его запас токенов не иссякнет.
Реализация
Аналогично алгоритму с фиксированным окном, создадим класс дескриптора для сохранения информации об инициаторе вызова. В данном случае к основным данным относятся скорость пополнения корзины за определенный интервал времени, текущая емкость корзины и время последнего обращения вызывающего.
import java.time.Instant; public class BucketRateLimitKeyDescriptor { private final String key; private int availableCapacity; private final int refillsPerInterval; private Instant lastAccessedTime; public BucketRateLimitKeyDescriptor(String key, int availableCapacity, int refillsPerInterval, Instant lastAccessedTime) { this.key = key; this.availableCapacity = availableCapacity; this.refillsPerInterval = refillsPerInterval; this.lastAccessedTime = lastAccessedTime; } public String getKey() { return key; } public int getAvailableCapacity() { return availableCapacity; } public void setAvailableCapacity(int availableCapacity) { this.availableCapacity = availableCapacity; } public void decrementAvailableCapacity() { this.availableCapacity--; } public int getRefillsPerInterval() { return refillsPerInterval; } public Instant getLastAccessedTime() { return lastAccessedTime; } public void setLastAccessedTime(Instant lastAccessedTime) { this.lastAccessedTime = lastAccessedTime; } }
Класс TokenBucketRateLimiter
:
import java.time.Instant; import java.util.HashMap; import java.util.Map; public class TokenBucketRateLimiter implements RateLimiter { final private Map<String, BucketRateLimitKeyDescriptor> rateLimitKeyDescriptorMap = new HashMap<>(); private final int rateLimit; private final int refillsPerInterval; private final long refillIntervalMilliseconds; private final TimestampProvider timestampProvider; public TokenBucketRateLimiter(int rateLimit, int refillsPerInterval, long refillIntervalMilliseconds, TimestampProvider timestampProvider) { this.rateLimit = rateLimit; this.refillsPerInterval = refillsPerInterval; this.refillIntervalMilliseconds = refillIntervalMilliseconds; this.timestampProvider = timestampProvider; } @Override public boolean isRequestAllowed(String key) { if (!rateLimitKeyDescriptorMap.containsKey(key)) { insertRateLimitDescriptor(key); } BucketRateLimitKeyDescriptor rateLimitKeyDescriptor = rateLimitKeyDescriptorMap.get(key); Instant currentTimestamp = timestampProvider.get(); refillBucket(rateLimitKeyDescriptor, currentTimestamp); if (rateLimitKeyDescriptor.getAvailableCapacity() == 0) { return false; } rateLimitKeyDescriptor.decrementAvailableCapacity(); return true; } private int getRefillsIntervals(BucketRateLimitKeyDescriptor rateLimitKeyDescriptor, Instant currentTimestamp) { return (int) ((currentTimestamp.toEpochMilli() - rateLimitKeyDescriptor.getLastAccessedTime().toEpochMilli()) / refillIntervalMilliseconds); } private void refillBucket(BucketRateLimitKeyDescriptor rateLimitKeyDescriptor, Instant currentTimestamp) { int refillsIntervals = getRefillsIntervals(rateLimitKeyDescriptor, currentTimestamp); rateLimitKeyDescriptor.setAvailableCapacity(Math.min(rateLimitKeyDescriptor.getAvailableCapacity() + (refillsIntervals * rateLimitKeyDescriptor.getRefillsPerInterval()), rateLimit)); if (refillsIntervals > 0) { rateLimitKeyDescriptor.setLastAccessedTime(currentTimestamp); } } private void insertRateLimitDescriptor(String key) { rateLimitKeyDescriptorMap.put(key, new BucketRateLimitKeyDescriptor(key, rateLimit, refillsPerInterval, timestampProvider.get())); } }
Ключевые функции метода isRequestAllowed
:
- При получении запроса корзина пополняется токенами. Объем пополнения рассчитывается на основе времени, прошедшего с момента последнего запроса, с использованием числа
refillsPerInterval
для определения соответствующего количества токенов.
- После пополнения следующим шагом будет проверка доступности хотя бы одного токена в корзине.
- Если токен доступен, количество токенов будет уменьшено, и запрос будет выполнен. И наоборот, если токенов нет, указываем, что запрос нужно отклонить, возвращая из метода
false
.
Добавим модульные тесты:
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.time.Instant;
import static org.junit.jupiter.api.Assertions.*;
class TokenBucketRateLimiterTest {
private final TimestampProvider timestampProvider = Mockito.mock(TimestampProvider.class);
@Test
void isRequestAllowed_shouldAllowRequest_whenAvailableCapacityExists() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.now());
TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter(10, 5, 1000, timestampProvider);
String key = "user1";
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
}
@Test
void isRequestAllowed_shouldDenyRequest_whenNoAvailableCapacity() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.now());
TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter(1, 5, 1000, timestampProvider);
String key = "user1";
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
assertFalse(tokenBucketRateLimiter.isRequestAllowed(key));
}
@Test
void isRequestAllowed_shouldAllowRequest_whenBucketWasRefilled() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.ofEpochMilli(0));
TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter(1, 1, 1000, timestampProvider);
String key = "user1";
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
Mockito.when(timestampProvider.get()).thenReturn(Instant.ofEpochMilli(1000)); // should refill bucket
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
}
@Test
void isRequestAllowed_shouldRefillBucketWith2Intervals() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.ofEpochMilli(0));
TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter(2, 1, 1000, timestampProvider);
String key = "user1";
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
Mockito.when(timestampProvider.get()).thenReturn(Instant.ofEpochMilli(2000)); // нужно пополнить корзину дважды, так как прошло две секунды
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
assertFalse(tokenBucketRateLimiter.isRequestAllowed(key)); // это должно окончиться неудачей, так как не осталось больше токенов
}
@Test
void isRequestAllowed_shouldAllowBurstOfRequests_withLowRefillRate() {
Mockito.when(timestampProvider.get()).thenReturn(Instant.ofEpochMilli(0));
TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter(50, 5, 1000, timestampProvider);
String key = "user1";
for (int i = 0; i < 50; i++) {
assertTrue(tokenBucketRateLimiter.isRequestAllowed(key));
}
assertFalse(tokenBucketRateLimiter.isRequestAllowed(key)); // это должно окончиться неудачей, так как все токены потрачены
}
}
Ограничение скорости: взгляд со стороны клиента
Когда вы сталкиваетесь с ограничением скорости или дросселированием от веб-сервиса, прежде всего следует определить ситуацию. Обычно сервер сигнализирует о проблеме кодом ответа 429 Too Many Requests
. В теле ответа также могут быть сообщения типа ThrottlingException
или RateLimitExceededException
.
Некоторые сервисы помогают справиться с этой проблемой, отправляя HTTP-заголовок Retry-After, в котором указывается рекомендуемое время ожидания перед повторным запросом. Если вы заметили этот заголовок, нужно выдержать паузу в течение указанного периода.
Если заголовок Retry-After
отсутствует, практическим подходом является применение экспоненциального откладывания. Эта стратегия предполагает постепенное увеличение времени ожидания между последующими запросами, что позволяет сбросить ограничение скорости. Кроме того, добавление «джиттера», или случайных изменений в интервалах повторных запросов, может предотвратить проблемы синхронизации, возникающие при одновременном дросселировании нескольких клиентов. Такой подход обеспечивает ступенчатость повторных попыток, снижая риск перегрузки сервиса.
Другой метод — применение ограничения скорости на стороне клиента. Если распределять запросы к сервису во времени, а не выдавать их быстрыми «всплесками», можно более эффективно придерживаться установленных ограничений скорости сервиса.
Наиболее важным аспектом работы с дросселированием является предоставление достаточного времени перед повторной попыткой запроса для сброса лимитов сервера.
Заключение
Теперь вы понимаете, какую существенную роль играет ограничение скорости в защите API.
Мы убедились в том, что ограничение скорости очень важно (хотя поначалу это может показаться нелогичным). Оно защищает ресурсы сервера от перегрузки клиентами и повышает общую предсказуемость работы сервиса и качество обслуживания клиентов.
Мы рассмотрели популярные алгоритмы, такие как алгоритм фиксированного окна и алгоритм Token Bucket, и узнали, как их реализовать.
С точки зрения клиента, мы узнали об эффективных стратегиях, таких как повторные попытки и экспоненциальное откладывание для изящной обработки исключений при дросселировании.
Что касается процесса дросселирования, то здесь остается еще одна интересная задача: достижение эффективного дросселирования в рамках распределенной системы. В современных условиях, когда большинство сервисов масштабируются горизонтально, синхронизация состояния дросселирования на нескольких серверах может стать критически важной.
Помните, что ограничение скорости — это важный инструмент в арсенале разработчика программного обеспечения. Используйте его с умом, чтобы защитить систему, повысить производительность и улучшить пользовательский опыт.
Читайте также:
- Как определить и протестировать SLO
- Пять распространенных ошибок производительности баз данных при разработке API
- Волшебство веб-разработки: создаем цифровую страну чудес
Читайте нас в Telegram, VK и Дзен
Перевод статьи Nadar Alpenidze: Designing Resilient APIs: Mastering the Art of Rate Limiting