利用 AspNetCoreRateLimit與Redis 進行分佈式請求限流機制-簡介
前言
在面對消費者導向的產品時,系統可能會遇到請求流量的尖峰與離峰期。透過雲原生技術的自動擴展和縮容機制,可以有效應對這樣的工作負載變化。然而,在分散式系統架構下,若串接其他的下游服務,比如第三方 API 、關聯式資料庫的寫入等,能否保持相同的吞吐量,對應來自上游服務的流量?
當然,可以採用一些設計,例如生產者-消費者模式、輪詢機制、垂直擴展或使用可讀副本等。但假如在應用上需要同步回傳結果,且涉及關鍵資源存取的場景下,該如何進行設計?這時,可能就需要實施限流策略,確保系統可以防止過度的資源使用,讓關鍵操作成功執行。
為了進一步說明如何實現流量限制,使用AspNetCoreRateLimit 與 Redis 整合為例子來展示這一過程。
AspNetCoreRateLimit(詳細介紹)
提供了 .NET Middleware 插件,能夠根據特定的 IP 地址、HTTP Header 中的自定義 ID,或對特定的 HTTP 目標端點進行請求速率限制。它不僅支持在記憶體中處理限流數據,也能使用 Redis 或其他存儲方案來保存這些數據。
StackExchange.Redis(詳細介紹)
它由 Stack Exchange 開發,也算是與Redis溝通的主流套件之一,支援多種 Redis 的特性,包括基本KeyValue操作、Pub/Sub、Stream 等…。作為與 AspNetCoreRateLimit 整合的儲存目標。
AspNetCoreRateLimit.Redis
依賴於AspNetCoreRateLimit,他提供了 RedisProcessingStrategy (fixed window rate limiting) 。
其他算法可參照(Sliding window rate limiting)
實際操作
下載套件,目前範例是採用 .NET6兼容版本
- AspNetCoreRateLimit (5.0.0)
- AspNetCoreRateLimit.Redis (2.0.0)
- Microsoft.Extensions.Caching.StackExchangeRedis (6.0.27)
- StackExchange.Redis (2.7.20)
範例程式碼
internal class Program
{
private static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 從appsettings.json取得設定
builder.Services.AddOptions();
// 註冊限流共通設定
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
// 註冊IP限制策略
builder.Services.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies"));
// 註冊共通元件,如IP解析器、ClientID解析器,以及計數器鍵生成器..等
// 裡面的邏輯會依賴上面的設定
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
// 註冊REDIS計數器的資料儲存方式,需要依賴IConnectionMultiplexer
builder.Services.AddSingleton<IConnectionMultiplexer>(
ConnectionMultiplexer.Connect(builder.Configuration["Redis:ConnectionString"]));
// 註冊限流策略的資料儲存方式,這邊儲存於Redis
builder.Services.AddStackExchangeRedisCache(option =>
{
option.Configuration = builder.Configuration["Redis:ConnectionString"];
option.InstanceName = builder.Configuration["Redis:InstanceName"];
});
// 註冊 REDIS計數器(fixed window)
// 註冊 限流策略的資料存放服務
builder.Services.AddRedisRateLimiting();
var app = builder.Build();
// 設定中繼管道,在流量進入時進行限流
app.UseIpRateLimiting();
// 這個方法會將IP限制策略資料寫入Redis
// 如果是分散式架構,這個方法可能會覆蓋掉Redis的資料,建議集中式管理
using (var scope = app.Services.CreateScope())
{
var clientPolicyStore = scope.ServiceProvider.GetRequiredService<IIpPolicyStore>();
await clientPolicyStore.SeedAsync();
}
// 以下配置忽略
...
await app.RunAsync();
}
}
如上述程式碼的註解部分,主要是說明如何在依賴注入階段,將限流設定以及演算法、儲存策略進行基本配置。
appsetting.json 設定如下,詳細配置可參考(這裏)
{
// 其他設定忽略
...
"Redis": {
"ConnectionString": "<Redis服務端點>:6379",
"InstanceName": "Policy"
},
"IpRateLimiting": {
"IpPolicyPrefix":"",
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": true,
"RealIpHeader": "X-Real-IP", // 解析來源Headerkey = X-Real-IP
"HttpStatusCode": 429, // 爆量時回傳的httpcode
"IpWhitelist": [],
"EndpointWhitelist": [],
"ClientWhitelist": [],
"GeneralRules": [] // 通用的路由規則,此範例為空,依據IpPolicy為主
},
"IpRateLimitPolicies": {
"IpRules": [
{
"Ip": "8.8.8.8", //限制來源IP為 8.8.8.8
"Rules": [
{
"Endpoint": "*", //所有Endpoint
"Period": "1m", //一分鐘內
"Limit": 60 //最多60個請求
}
]
}
]
}
}
透過k6壓測工具執行結果
import http from 'k6/http';
import { sleep ,check} from 'k6';
export let options = {
vus: 2, // 固定負載,分別以1、2個vu 來測試
duration: '1m', // 持續時間一分鐘
};
export default function () {
const params = {
headers: {
'X-Real-IP': '8.8.8.8', // 模擬來源為8.8.8.8
},
};
// 這邊是要測試的API
let response = http.get('http://localhost:55003/WeatherForecast', params);
// 查看response httpcode
console.log(response.status);
// 驗證結果
check(response, {
'is status 200': (r) => r.status === 200,
});
// 間隔1秒
sleep(1);
}
單個Instance驗證 :
多個Instance驗證 ,透過k3d:
以上的範例程式碼可參考 (這裏)。
展示了簡單的例子,如何透過 AspNetCoreRateLimit 和 Redis 實現請求限流的基本示例與其驗證結果。進一步細化配置後,我們能夠實現更精細化的控制策略,例如開啟監控模式、針對特定 IP 段或特定路由進行限流等。此外,通過不同的限流算法和動態調整策略,可以根據即時流量去進行調整,從而提升系統的可靠性和用戶體驗。