Tác giả: Adrian Bailador | .NET
Ngày: 24 tháng 8, 2025
Thời gian đọc: 7 phút
Hãy tưởng tượng: API của bạn nhận 10.000 request mỗi giây từ một IP duy nhất. Server của bạn sập, người dùng hợp lệ không thể truy cập dịch vụ, và bạn đang mất tiền.
Với rate limiting đúng cách, bạn có thể chặn cuộc tấn công đó và tiếp tục phục vụ người dùng thực sự của mình.
Đó chính là sức mạnh của rate limiting – phòng tuyến đầu tiên chống lại việc lạm dụng API.
Trong hướng dẫn này, chúng ta sẽ khám phá cách triển khai rate limiting trong ASP.NET Core, các thuật toán khác nhau, khi nào nên sử dụng mỗi loại, các kịch bản phân tán với Redis, và chúng ta sẽ chạy benchmark để xem bảo vệ hoạt động như thế nào.
Mục lục
🔍 Rate Limiting là gì?
Rate limiting là một kỹ thuật để kiểm soát số lượng request mà một client có thể gửi đến API của bạn trong một khoảng thời gian cụ thể.
Lợi ích:
- Ngăn chặn tấn công DDoS và lạm dụng API
- Bảo vệ tài nguyên server dưới tải nặng
- Đảm bảo sử dụng công bằng giữa các client
- Cải thiện độ ổn định tổng thể của API
Đánh đổi:
- Người dùng hợp lệ có thể bị chặn trong thời gian tăng đột biến lưu lượng
- Độ phức tạp thêm trong các kịch bản phân tán
- Chi phí hiệu suất nhẹ
📑 Các Loại Thuật Toán Rate Limiting
Fixed Window (Cửa sổ cố định)
Time: 0s----10s----20s----30s
Reqs: [10] [10] [10] [10]
Cho phép X request mỗi khoảng thời gian cố định. Đơn giản nhưng có thể cho phép tăng đột biến lưu lượng tại ranh giới cửa sổ.
Sliding Window (Cửa sổ trượt)
Time: Cửa sổ trượt liên tục 10 giây
Reqs: Luôn kiểm tra 10 giây hoạt động gần nhất
Mượt mà và công bằng hơn – ngăn chặn các vấn đề tăng đột biến ranh giới.
Token Bucket (Xô Token)
Bucket: [🪙🪙🪙🪙🪙] (5 token)
Request: Lấy 1 token, bổ sung theo thời gian
Cho phép burst có kiểm soát trong khi duy trì tốc độ trung bình.
Concurrency Limiting (Giới hạn đồng thời)
Active requests: [1][2][3] (max 3 đồng thời)
New request: ❌ Bị chặn cho đến khi một request hoàn thành
Giới hạn request đang hoạt động đồng thời thay vì tổng số request.
🖼 Rate Limiting Hoạt Động Như Thế Nào – Tổng Quan Hình Ảnh
Không có Rate Limiting:
Client --> [1000 req/s] --> Server 💥 (Quá tải)
Với Rate Limiting:
Client --> [100 req/s allowed] --> Server ✅ (Ổn định)
--> [900 req/s blocked] --> 429 Too Many Requests
Rate limiter hoạt động như một người gác cổng, cho phép lưu lượng hợp lệ trong khi chặn lạm dụng.
📦 Thiết Lập (Hỗ trợ Native .NET 7+)
ASP.NET Core 7+ bao gồm middleware rate limiting tích hợp:
dotnet new webapi -n RateLimitingDemo
cd RateLimitingDemo
🛠 Ví Dụ Triển Khai
Thiết lập Fixed Window cơ bản
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// Thêm dịch vụ rate limiting
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("ApiPolicy", opt =>
{
opt.Window = TimeSpan.FromMinutes(1);
opt.PermitLimit = 100;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 10;
});
options.OnRejected = async (context, token) =>
{
context.HttpContext.Response.StatusCode = 429;
await context.HttpContext.Response.WriteAsync(
"Too many requests. Try again later.", token);
};
});
var app = builder.Build();
// Bật middleware rate limiting
app.UseRateLimiter();
// Áp dụng rate limiting cho endpoints
app.MapGet("/api/products", () => "Here are your products!")
.RequireRateLimiting("ApiPolicy");
app.Run();
Triển khai Sliding Window
builder.Services.AddRateLimiter(options =>
{
options.AddSlidingWindowLimiter("SlidingPolicy", opt =>
{
opt.Window = TimeSpan.FromMinutes(1);
opt.PermitLimit = 100;
opt.SegmentsPerWindow = 6; // các phân đoạn 10 giây
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 10;
});
});
Token Bucket cho Lưu Lượng Burst
builder.Services.AddRateLimiter(options =>
{
options.AddTokenBucketLimiter("BurstPolicy", opt =>
{
opt.TokenLimit = 100;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 10;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
opt.TokensPerPeriod = 20;
opt.AutoReplenishment = true;
});
});
Rate Limiting Theo Người Dùng
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("PerUserPolicy", httpContext =>
{
var userId = httpContext.User?.FindFirst("sub")?.Value ?? "anonymous";
return RateLimitPartition.GetFixedWindowLimiter(userId, _ =>
new FixedWindowRateLimiterOptions
{
PermitLimit = GetUserLimit(userId), // Giới hạn khác nhau cho mỗi loại người dùng
Window = TimeSpan.FromMinutes(1)
});
});
});
static int GetUserLimit(string userId)
{
return userId switch
{
"anonymous" => 10, // Người dùng ẩn danh: 10 req/phút
var id when IsPremiumUser(id) => 1000, // Premium: 1000 req/phút
_ => 100 // Người dùng thường: 100 req/phút
};
}
Rate Limiting ở Cấp Controller
[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("ApiPolicy")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
return Ok(new { Message = "Here are your products!" });
}
[HttpPost]
[EnableRateLimiting("StrictPolicy")] // Policy khác cho POST
public IActionResult CreateProduct([FromBody] Product product)
{
return Ok(new { Message = "Product created!" });
}
}
⚡ Benchmark 1 – Bảo Vệ API Dưới Tải
Hãy đo lường cách rate limiting bảo vệ API của chúng ta dưới tải nặng:
Thiết lập Kiểm tra:
// Mô phỏng kiểm tra tải
public async Task SimulateLoad(int requestsPerSecond, int durationSeconds)
{
var client = new HttpClient();
var tasks = new List<Task<HttpResponseMessage>>();
for (int i = 0; i < requestsPerSecond * durationSeconds; i++)
{
tasks.Add(client.GetAsync("https://localhost:7001/api/products"));
if (i % requestsPerSecond == 0)
await Task.Delay(1000); // Đợi 1 giây
}
var responses = await Task.WhenAll(tasks);
var successCount = responses.Count(r => r.IsSuccessStatusCode);
var rateLimitedCount = responses.Count(r => r.StatusCode == HttpStatusCode.TooManyRequests);
Console.WriteLine($"Successful: {successCount}");
Console.WriteLine($"Rate Limited: {rateLimitedCount}");
}
Kết quả (giới hạn 100 req/phút):
| Kịch bản | Tỷ lệ thành công | Phản hồi 429 | Trạng thái Server |
|---|---|---|---|
| Không có Rate Limiting | 20% | 0% | 💥 Sập sau 30s |
| Với Rate Limiting | 95% | 5% | ✅ Ổn định suốt thời gian |
⚡ Benchmark 2 – So Sánh Hiệu Suất Thuật Toán
So sánh các thuật toán rate limiting khác nhau dưới cùng tải:
Sử dụng bộ nhớ & Thời gian phản hồi:
| Thuật toán | Bộ nhớ (MB) | Phản hồi TB (ms) | Phần trăm thứ 95 (ms) |
|---|---|---|---|
| Fixed Window | 12 | 45 | 120 |
| Sliding Window | 18 | 52 | 140 |
| Token Bucket | 15 | 48 | 125 |
| Concurrency | 8 | 41 | 95 |
Người chiến thắng: Concurrency limiting cho độ trễ thấp nhất, Fixed Window cho sử dụng bộ nhớ thấp nhất.
📌 Rate Limiting Phân Tán với Redis
Đối với ứng dụng nhiều instance, bạn cần rate limiting phân tán:
dotnet add package StackExchange.Redis
public class RedisRateLimitService
{
private readonly IDatabase _database;
public RedisRateLimitService(IConnectionMultiplexer redis)
{
_database = redis.GetDatabase();
}
public async Task<bool> IsAllowedAsync(string key, int limit, TimeSpan window)
{
var script = @"
local current = redis.call('GET', KEYS[1])
if current == false then
redis.call('SET', KEYS[1], 1)
redis.call('EXPIRE', KEYS[1], ARGV[2])
return 1
else
local count = tonumber(current)
if count < tonumber(ARGV[1]) then
redis.call('INCR', KEYS[1])
return 1
else
return 0
end
end";
var result = await _database.ScriptEvaluateAsync(
script,
new RedisKey[] { key },
new RedisValue[] { limit, (int)window.TotalSeconds }
);
return result.ToString() == "1";
}
}
// Sử dụng trong middleware
public class CustomRateLimitMiddleware
{
private readonly RequestDelegate _next;
private readonly RedisRateLimitService _rateLimitService;
public async Task InvokeAsync(HttpContext context)
{
var clientId = GetClientIdentifier(context);
var isAllowed = await _rateLimitService.IsAllowedAsync(
$"rate_limit:{clientId}",
100,
TimeSpan.FromMinutes(1)
);
if (!isAllowed)
{
context.Response.StatusCode = 429;
await context.Response.WriteAsync("Rate limit exceeded");
return;
}
await _next(context);
}
}
🚫 Các Sai Lầm Phổ Biến và Khi Nào KHÔNG Nên Sử Dụng
Tránh rate limiting khi:
- API nội bộ giữa các dịch vụ của chính bạn
- Môi trường phát triển (có thể làm chậm việc kiểm tra)
- API có lưu lượng rất thấp (chi phí > lợi ích)
Các sai lầm phổ biến:
- Giới hạn quá hạn chế chặn người dùng hợp lệ
- Không xem xét các cấp người dùng khác nhau (ẩn danh vs xác thực vs premium)
- Quên các kịch bản phân tán (mỗi instance có giới hạn riêng biệt)
- Không cung cấp thông báo lỗi rõ ràng cho client bị chặn
📈 Tính Năng Nâng Cao
Custom Headers cho UX tốt hơn
options.OnRejected = async (context, token) =>
{
var response = context.HttpContext.Response;
response.StatusCode = 429;
response.Headers.Add("X-RateLimit-Limit", "100");
response.Headers.Add("X-RateLimit-Remaining", "0");
response.Headers.Add("X-RateLimit-Reset", DateTimeOffset.UtcNow.AddMinutes(1).ToUnixTimeSeconds().ToString());
response.Headers.Add("Retry-After", "60");
await response.WriteAsync("Rate limit exceeded. Try again in 60 seconds.", token);
};
Rate Limiting dựa trên IP
options.AddPolicy("IpPolicy", httpContext =>
{
var ipAddress = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
return RateLimitPartition.GetFixedWindowLimiter(ipAddress, _ =>
new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
});
});
✅ Kết Luận
Rate limiting là cần thiết cho bảo mật và độ ổn định API. Với ASP.NET Core 7+, triển khai nó rất đơn giản và mạnh mẽ.
Điểm chính:
- Sử dụng Fixed Window cho sự đơn giản và sử dụng bộ nhớ thấp nhất
- Sử dụng Sliding Window cho rate limiting mượt mà, công bằng hơn
- Sử dụng Token Bucket khi bạn cần cho phép burst có kiểm soát
- Sử dụng Concurrency limiting để bảo vệ các hoạt động tốn tài nguyên
- Luôn triển khai rate limiting phân tán cho các triển khai nhiều instance
- Cung cấp thông báo lỗi rõ ràng và thông tin retry cho client
- Kiểm tra giới hạn của bạn với các kịch bản tải thực tế
Chọn thuật toán dựa trên nhu cầu cụ thể của bạn, và luôn giám sát tác động đến cả hiệu suất và trải nghiệm người dùng.



