Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Dalam artikel ini, Anda akan mempelajari cara membuat handler HTTP sisi klien yang lajunya membatasi jumlah permintaan yang dikirimnya. Anda akan melihat HttpClient yang mengakses sumber daya "www.example.com". Sumber daya digunakan oleh aplikasi yang mengandalkannya, dan ketika aplikasi membuat terlalu banyak permintaan untuk sumber daya yang sama, itu dapat menyebabkan persaingan sumber daya . Pertikaian sumber daya terjadi ketika sumber daya dikonsumsi oleh terlalu banyak aplikasi, dan sumber daya tidak dapat melayani semua aplikasi yang memintanya. Ini dapat mengakibatkan pengalaman pengguna yang buruk, dan dalam beberapa kasus, bahkan dapat menyebabkan serangan penolakan layanan (DoS). Untuk informasi selengkapnya tentang DoS, lihat OWASP: Penolakan Layanan.
Apa itu pembatasan tarif?
Pembatasan tarif adalah konsep membatasi berapa banyak sumber daya yang dapat diakses. Misalnya, Anda mungkin tahu bahwa database yang diakses aplikasi Anda dapat menangani 1.000 permintaan per menit dengan aman, tetapi mungkin tidak menangani lebih dari itu. Anda dapat menempatkan pembatas tarif di aplikasi yang hanya mengizinkan 1.000 permintaan setiap menit dan menolak permintaan lagi sebelum mereka dapat mengakses database. Dengan demikian, tarif membatasi database Anda dan memungkinkan aplikasi Anda untuk menangani sejumlah permintaan yang aman. Ini adalah pola umum dalam sistem terdistribusi, di mana Anda mungkin memiliki beberapa instans aplikasi yang berjalan, dan Anda ingin memastikan bahwa mereka tidak semua mencoba mengakses database secara bersamaan. Ada beberapa algoritma pembatasan laju yang berbeda untuk mengontrol alur permintaan.
Untuk menggunakan pembatasan tarif di .NET, Anda akan mereferensikan paket System.Threading.RateLimiting NuGet.
Menerapkan subkelas DelegatingHandler
Untuk mengontrol alur permintaan, Anda menerapkan subkelas DelegatingHandler kustom. Ini adalah jenis HttpMessageHandler yang memungkinkan Anda untuk mencegat dan menangani permintaan sebelum dikirim ke server. Anda juga dapat mencegat dan menangani respons sebelum dikembalikan ke pemanggil. Dalam contoh ini, Anda akan menerapkan subkelas DelegatingHandler kustom yang membatasi jumlah permintaan yang dapat dikirim ke satu sumber daya. Pertimbangkan kelas ClientSideRateLimitedHandler kustom berikut:
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();
}
}
}
Kode C# sebelumnya:
- Mewarisi jenis
DelegatingHandler. - Mengimplementasikan antarmuka IAsyncDisposable.
- Menentukan bidang
RateLimiteryang ditetapkan oleh konstruktor. - Mengambil alih metode
SendAsyncuntuk mencegat dan menangani permintaan sebelum dikirim ke server. - Mengambil alih metode DisposeAsync() untuk membuang instans
RateLimiter.
Melihat sedikit lebih dekat pada metode SendAsync, Anda akan melihatnya:
- Bergantung pada instance
RateLimiteruntuk memperolehRateLimitLeasedariAcquireAsync. - Ketika properti
lease.IsAcquiredadalahtrue, permintaan dikirim ke server. - Jika tidak, HttpResponseMessage dikembalikan dengan kode status
429, dan jikaleaseberisi nilaiRetryAfter, headerRetry-Afterdiatur ke nilai tersebut.
Mensimulasikan banyak permintaan konkuren
Untuk menguji subkelas DelegatingHandler kustom ini, Anda akan membuat aplikasi konsol yang meniru banyak permintaan bersamaan. Kelas Program ini membuat HttpClient dengan ClientSideRateLimitedHandlerkustom :
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})");
}
Di aplikasi konsol sebelumnya:
-
TokenBucketRateLimiterOptionsdikonfigurasi dengan batas token8, urutan pemrosesan antreanOldestFirst, batas antrean3, periode pengisian ulang1milidetik, nilai token per periode2, dan nilai pengisian otomatistrue. - Sebuah
HttpClientdibuat menggunakanClientSideRateLimitedHandleryang telah dikonfigurasi denganTokenBucketRateLimiter. - Untuk meniru 100 permintaan, Enumerable.Range membuat 100 URL, masing-masing dengan parameter string kueri unik.
- Dua objek Task ditetapkan dari metode Parallel.ForEachAsync, membagi URL menjadi dua grup.
-
HttpClientdigunakan untuk mengirim permintaanGETke setiap URL, dan respons ditulis ke konsol. - Task.WhenAll menunggu kedua tugas selesai.
Karena HttpClient dikonfigurasi dengan ClientSideRateLimitedHandler, tidak semua permintaan akan berhasil ke sumber daya server. Anda dapat menguji pernyataan ini dengan menjalankan aplikasi konsol. Anda akan melihat bahwa hanya sebagian kecil dari jumlah total permintaan yang dikirim ke server, dan sisanya ditolak dengan kode status HTTP 429. Coba ubah objek options yang digunakan untuk membuat TokenBucketRateLimiter untuk melihat bagaimana jumlah permintaan yang dikirim ke server berubah.
Pertimbangkan contoh output berikut:
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)
Anda akan melihat bahwa entri pertama yang dicatat selalu merupakan respons 429 yang segera dikembalikan, dan entri terakhir selalu merupakan 200 respons. Ini karena batas laju ditemui di sisi klien dan untuk menghindari melakukan panggilan HTTP ke server. Ini adalah hal yang baik karena itu berarti bahwa server tidak dibanjiri permintaan. Ini juga berarti bahwa batas tarif diberlakukan secara konsisten di semua klien.
Perhatikan juga bahwa setiap string kueri URL bersifat unik: periksa parameter iteration untuk melihat bahwa string kueri tersebut bertambah satu untuk setiap permintaan. Parameter ini membantu menggambarkan bahwa 429 respons bukan dari permintaan pertama, melainkan dari permintaan yang dibuat setelah batas tarif tercapai. 200 respons tiba kemudian tetapi permintaan ini dibuat sebelumnya—sebelum batas tercapai.
Untuk memiliki pemahaman yang lebih baik tentang berbagai algoritma pembatasan laju, coba tulis ulang kode ini untuk menerima implementasi RateLimiter yang berbeda. Selain TokenBucketRateLimiter Anda dapat mencoba:
Ringkasan
Dalam artikel ini, Anda belajar cara mengimplementasikan custom ClientSideRateLimitedHandler. Pola ini dapat digunakan untuk menerapkan klien HTTP terbatas tarif untuk sumber daya yang Anda ketahui memiliki batas API. Dengan cara ini, Anda mencegah aplikasi klien membuat permintaan yang tidak perlu ke server, dan Anda juga mencegah aplikasi diblokir oleh server. Selain itu, dengan penggunaan metadata untuk menyimpan nilai waktu coba lagi, Anda juga dapat menerapkan logika coba lagi otomatis.
Lihat juga
- Mengumumkan Pembatasan Tarif untuk .NET
- middleware pembatasan laju di ASP.NET Core
- Arsitektur Azure: Pola pembatasan laju
- Logika coba lagi otomatis di .NET