Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
В этой статье вы узнаете, как создать клиентский обработчик HTTP, который ограничивает количество отправляемых запросов. Вы увидите HttpClient, который обращается к ресурсу "www.example.com". Ресурсы используются приложениями, которые полагаются на них, и когда приложение делает слишком много запросов для одного ресурса, это может привести к возникновению спорных ресурсов. Состязание по ресурсам возникает, когда ресурс потребляется слишком большим количеством приложений, и ресурс не может обслуживать все приложения, запрашивающие его. Это может привести к плохому интерфейсу пользователя, и в некоторых случаях это может даже привести к атаке типа "отказ в обслуживании" (DoS). Дополнительные сведения о DoS см. в статье OWASP: отказ в обслуживании.
Что такое ограничение скорости?
Ограничение скорости — это концепция ограничения объема доступа к ресурсу. Например, вы можете знать, что доступ к базе данных приложения может безопасно обрабатывать 1000 запросов в минуту, но он может не обрабатывать гораздо больше, чем это. Вы можете поместить ограничение скорости в приложение, которое разрешает только 1000 запросов каждую минуту и отклоняет все больше запросов, прежде чем они смогут получить доступ к базе данных. Таким образом, ограничивая количество запросов к вашей базе данных, вы позволяете приложению безопасно обрабатывать их. Это распространенный шаблон в распределенных системах, где может быть несколько экземпляров запущенного приложения, и вы хотите убедиться, что они не все пытаются получить доступ к базе данных одновременно. Существует несколько различных алгоритмов ограничения скорости для управления потоком запросов.
Чтобы использовать ограничение скорости в .NET, вы будете ссылаться на пакет NuGet System.Threading.RateLimiting .
Реализуйте подкласс DelegatingHandler
Для управления потоком запросов реализуется пользовательский DelegatingHandler подкласс. Это тип HttpMessageHandler , который позволяет перехватывать и обрабатывать запросы перед отправкой на сервер. Можно также перехватывать и обрабатывать ответы, прежде чем они будут возвращены вызывающему объекту. В этом примере вы реализуете пользовательский DelegatingHandler подкласс, ограничивающий количество запросов, которые могут отправляться одному ресурсу. Рассмотрим следующий пользовательский ClientSideRateLimitedHandler класс:
internal sealed class ClientSideRateLimitedHandler(
RateLimiter limiter)
: DelegatingHandler(new HttpClientHandler()), IAsyncDisposable
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
using RateLimitLease lease = await limiter.AcquireAsync(
permitCount: 1, cancellationToken);
if (lease.IsAcquired)
{
return await base.SendAsync(request, cancellationToken);
}
var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests);
if (lease.TryGetMetadata(
MetadataName.RetryAfter, out TimeSpan retryAfter))
{
response.Headers.Add(
"Retry-After",
((int)retryAfter.TotalSeconds).ToString(
NumberFormatInfo.InvariantInfo));
}
return response;
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
await limiter.DisposeAsync().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
limiter.Dispose();
}
}
}
Предыдущий код C#:
- Наследует
DelegatingHandlerтип. - Реализует интерфейс IAsyncDisposable.
- Определяет
RateLimiterполе, назначенное конструктором. - Переопределяет метод
SendAsync, чтобы перехватывать и обрабатывать запросы перед их отправкой на сервер. - Переопределяет метод DisposeAsync() для освобождения экземпляра
RateLimiter.
Посмотрите немного ближе к методу SendAsync , вы увидите, что это:
- Основан на экземпляре
RateLimiter, чтобы получитьRateLimitLeaseотAcquireAsync. -
lease.IsAcquiredКогда свойство имеет значениеtrue, запрос отправляется серверу. - В противном случае возвращается HttpResponseMessage с кодом состояния
429, и еслиleaseсодержит значениеRetryAfter, тоRetry-Afterзаголовок задается этим значением.
Эмулировать множество одновременных запросов
Чтобы поместить этот настраиваемый DelegatingHandler подкласс в тест, вы создадите консольное приложение, которое эмулирует множество одновременных запросов. Этот Program класс создает HttpClient с пользовательским ClientSideRateLimitedHandler:
var options = new TokenBucketRateLimiterOptions
{
TokenLimit = 8,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = true
};
// Create an HTTP client with the client-side rate limited handler.
using HttpClient client = new(
handler: new ClientSideRateLimitedHandler(
limiter: new TokenBucketRateLimiter(options)));
// Create 100 urls with a unique query string.
var oneHundredUrls = Enumerable.Range(0, 100).Select(
i => $"https://example.com?iteration={i:0#}");
// Flood the HTTP client with requests.
var floodOneThroughFortyNineTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(0..49),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
var floodFiftyThroughOneHundredTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(^50..),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
await Task.WhenAll(
floodOneThroughFortyNineTask,
floodFiftyThroughOneHundredTask);
static async ValueTask GetAsync(
HttpClient client, string url, CancellationToken cancellationToken)
{
using var response =
await client.GetAsync(url, cancellationToken);
Console.WriteLine(
$"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})");
}
В предыдущем консольном приложении:
- Они
TokenBucketRateLimiterOptionsнастраиваются с ограничением на токены8, порядком обработки очередиOldestFirst, ограничением очереди3, периодом пополнения в1миллисекунд, значением токенов за период2, и значением автоматического пополненияtrue. - Создается
HttpClientвместе сClientSideRateLimitedHandler, который настроен с помощьюTokenBucketRateLimiter. - Чтобы эмулировать 100 запросов, Enumerable.Range создает 100 URL-адресов, каждый из которых имеет уникальный параметр строки запроса.
- Два Task объекта назначаются из Parallel.ForEachAsync метода, разделяя URL-адреса на две группы.
- Используется
HttpClientдля отправкиGETзапроса на каждый URL-адрес, а ответ записывается в консоль. - Task.WhenAll Ожидает завершения обеих задач.
Поскольку HttpClient настроен с помощью ClientSideRateLimitedHandler, не все запросы попадут на ресурс сервера. Это утверждение можно проверить, выполнив консольное приложение. Вы увидите, что на сервер отправляется только часть общего количества запросов, а остальные отклоняются с кодом 429состояния HTTP. Попробуйте изменить объект options, используемый для создания TokenBucketRateLimiter, чтобы увидеть, как изменяется количество запросов, отправляемых серверу.
Рассмотрим следующий пример выходных данных:
URL: https://example.com?iteration=06, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=60, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=55, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=59, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=57, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=11, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=63, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=13, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=62, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=65, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=64, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=67, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=14, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=68, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=16, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=69, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=70, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=71, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=17, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=18, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=72, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=73, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=74, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=19, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=75, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=76, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=79, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=77, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=21, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=78, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=81, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=22, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=80, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=20, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=82, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=83, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=23, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=84, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=24, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=85, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=86, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=25, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=87, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=26, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=88, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=89, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=27, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=90, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=28, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=91, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=94, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=29, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=93, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=96, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=92, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=95, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=31, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=30, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=97, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=98, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=99, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=32, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=33, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=34, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=35, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=36, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=37, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=38, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=39, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=40, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=41, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=42, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=43, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=44, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=45, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=46, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=47, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=48, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=15, HTTP status code: OK (200)
URL: https://example.com?iteration=04, HTTP status code: OK (200)
URL: https://example.com?iteration=54, HTTP status code: OK (200)
URL: https://example.com?iteration=08, HTTP status code: OK (200)
URL: https://example.com?iteration=00, HTTP status code: OK (200)
URL: https://example.com?iteration=51, HTTP status code: OK (200)
URL: https://example.com?iteration=10, HTTP status code: OK (200)
URL: https://example.com?iteration=66, HTTP status code: OK (200)
URL: https://example.com?iteration=56, HTTP status code: OK (200)
URL: https://example.com?iteration=52, HTTP status code: OK (200)
URL: https://example.com?iteration=12, HTTP status code: OK (200)
URL: https://example.com?iteration=53, HTTP status code: OK (200)
URL: https://example.com?iteration=07, HTTP status code: OK (200)
URL: https://example.com?iteration=02, HTTP status code: OK (200)
URL: https://example.com?iteration=01, HTTP status code: OK (200)
URL: https://example.com?iteration=61, HTTP status code: OK (200)
URL: https://example.com?iteration=05, HTTP status code: OK (200)
URL: https://example.com?iteration=09, HTTP status code: OK (200)
URL: https://example.com?iteration=03, HTTP status code: OK (200)
URL: https://example.com?iteration=58, HTTP status code: OK (200)
URL: https://example.com?iteration=50, HTTP status code: OK (200)
Вы заметите, что первые журнальные записи всегда содержат сразу возвращаемые ответы 429, а последние всегда содержат ответы 200. Это связано с тем, что ограничение скорости встречается на стороне клиента и избегает вызова HTTP к серверу. Это хорошо, потому что это означает, что сервер не затоплен запросами. Это также означает, что ограничение скорости применяется согласованно для всех клиентов.
Обратите внимание, что строка запроса каждого URL-адреса уникальна: проверьте iteration параметр, чтобы увидеть, что он увеличивается по одному для каждого запроса. Этот параметр помогает показать, что ответы 429 не приходят с первых запросов, а появляются из запросов, сделанных после достижения ограничения скорости. 200 ответов поступают позже, но эти запросы были сделаны ранее, прежде чем было достигнуто ограничение.
Чтобы лучше понять различные алгоритмы ограничения скорости, попробуйте переписать этот код, чтобы принять другую RateLimiter реализацию. Помимо TokenBucketRateLimiter, вы можете попробовать:
Сводка
Из этой статьи вы узнали, как реализовать настраиваемый ClientSideRateLimitedHandler. Этот шаблон можно использовать для реализации ограниченного по скорости HTTP-клиента для ресурсов, которые вы знаете, имеют ограничения API. Таким образом, вы запрещаете клиентскому приложению делать ненужные запросы к серверу, и вы также не блокируете приложение сервером. Кроме того, при использовании метаданных для хранения значений времени повтора можно также реализовать логику автоматического повтора.