API 介面限流完整技術指南 目錄
前言
為什麼需要限流
常見限流策略
.NET Core 實作方案
分散式限流方案
最佳實踐建議
環境配置建議
效能測試方法
結論
前言 在現代微服務架構中,API 限流(Rate Limiting)是一個不可或缺的重要機制。它能確保系統的穩定性和可用性,防止API被過度使用或遭受惡意攻擊。本文將詳細介紹 API 限流的概念、策略以及在 .NET Core 中的具體實作方法。
為什麼需要限流 實作 API 限流有以下幾個重要原因:
保護系統資源 :防止單一客戶端消耗過多系統資源
確保服務品質 :為所有使用者提供穩定的服務體驗
防止惡意攻擊 :降低 DDoS 攻擊的影響
控制成本 :特別是在使用雲端服務時,可以有效控制資源使用成本
常見限流策略
限流策略
實現複雜度
內存消耗
精確度
突發流量處理
分佈式實現
固定窗口
低
低
低
差
易
滑動窗口
中
中
高
中
中
令牌桶
中
中
高
優
中
漏桶
高
高
高
差
難
1. 固定窗口計數器(Fixed Window Counter) 最簡單的限流策略,在固定的時間窗口內計算請求次數。 基本原理:
設定一個固定的時間窗口(比如1分鐘)
在這個窗口內允許的最大請求數(比如100次)
每當有新請求時,檢查當前窗口內的請求數是否超過限制 舉個例子: 假設我們限制每分鐘最多100個請求:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 簡單示意 current_minute = 13:01 最大請求數 = 100 當前計數 = 0 當收到請求時: 如果 current_minute 還沒過: 當前計數 += 1 如果 當前計數 <= 最大請求數: 允許請求 否則: 拒絕請求 否則: 重設計數器為1 更新current_minute 允許請求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class FixedWindowRateLimiter { private readonly int _limit; private readonly TimeSpan _window; private int _counter; private DateTime _lastReset; private readonly object _lock = new object (); public FixedWindowRateLimiter (int limit, TimeSpan window ) { _limit = limit; _window = window; _lastReset = DateTime.UtcNow; } public bool ShouldAllowRequest () { lock (_lock) { var now = DateTime.UtcNow; if (now - _lastReset > _window) { _counter = 0 ; _lastReset = now; } if (_counter >= _limit) return false ; _counter++; return true ; } } } var limiter = new FixedWindowRateLimiter(100 , TimeSpan.FromMinutes(1 ));if (limiter.ShouldAllowRequest()){ } else { }
優點:
實現簡單
內存佔用少
容易理解
缺點:
邊界問題: 假設限制是每分鐘100次請求 用戶可能在 13:00:59 發送100個請求 然後在 13:01:01 又發送100個請求 實際上在2秒內發送了200個請求,可能造成系統壓力
突發流量不平滑: 窗口切換時會立即重設計數器,可能導致流量不均勻
適用場景:
簡單的 API 限流
單機應用的資源控制
對精確度要求不高的場景
適合做為快速原型或簡單實現
不適用場景:
分佈式系統
高併發環境
需要平滑限流的場景
對限流精確度要求高的場景
可能的優化方向:
使用 Redis 實現分佈式限流
新增監控和統計功能
實現重試機制
加入日誌記錄
程式碼實現注意事項:
使用 UTC 時間避免時區問題
考慮併發安全
注意時間計算的精確度
考慮記憶體使用效率
與其他限流演算法比較:
比滑動窗口簡單,但精確度低
比令牌桶少佔用記憶體
比漏桶實現簡單,但控制粒度較粗
實際應用建議:
在簡單場景下使用
配合監控系統
根據實際需求調整窗口大小
考慮是否需要持久化計數器
2. 滑動窗口計數器(Sliding Window Counter) 比固定窗口更精確的限流方式,能避免窗口邊界問題。 基本原理:
把時間窗口分成更小的時間片(bucket)
使用滾動方式統計,保持更平滑的限流效果
舉個具體例子: 假設要限制每分鐘最多100個請求,我們可以:
把1分鐘分成6個10秒的小窗口
持續記錄最近6個小窗口的請求數
隨著時間推移,持續滾動更新這些小窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class SlidingWindowRateLimiter { private readonly Dictionary<long , int > _windows = new (); private readonly int _limit; private readonly int _windowMilliseconds; private readonly int _bucketSizeMillis; private readonly object _lock = new (); public SlidingWindowRateLimiter (int limit, int windowSeconds, int bucketSizeMillis = 1000 ) { _limit = limit; _windowMilliseconds = windowSeconds * 1000 ; _bucketSizeMillis = bucketSizeMillis; } public bool ShouldAllowRequest () { lock (_lock) { var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var currentBucket = now - (now % _bucketSizeMillis); var windowStart = now - _windowMilliseconds; _windows.Keys.Where(k => k < windowStart).ToList() .ForEach(k => _windows.Remove(k)); var requestCount = _windows.Values.Sum(); if (requestCount >= _limit) return false ; if (!_windows.ContainsKey(currentBucket)) _windows[currentBucket] = 0 ; _windows[currentBucket]++; return true ; } } } var rateLimiter = new SlidingWindowRateLimiter(100 , 60 , 1000 );if (rateLimiter.ShouldAllowRequest()){ } else { }
1 2 3 4 時間軸: [10s] [10s] [10s] [10s] [10s] [10s] 初始: [12] [20] [15] [13] [10] [5] = 75個請求 滑動後: [20] [15] [13] [10] [5] [8] = 71個請求
優點:
更平滑的限流效果
避免了固定窗口的邊界問題
更精確的流量控制
使用場景:
API限流:每分鐘限制用戶API調用次數
流量控制:限制每秒數據庫查詢次數
服務保護:限制單一IP的訪問頻率
資源訪問控制:限制檔案下載速率
注意事項:
需要更多的內存來存儲各個小窗口的計數
實現相對複雜
需要定期清理過期的計數數據
實際應用建議:
根據實際需求選擇合適的窗口大小和小窗口大小
考慮使用 Redis 等分佈式存儲來實現分佈式環境下的限流
可以配合其他限流演算法一起使用,達到更好的效果
實際應用中的數值參考:
小窗口大小建議值:100ms ~ 1s
總窗口大小建議值:
API限流:1s ~ 60s
爬蟲限流:1min ~ 1hour
業務限流:根據具體場景定義
比較:
比固定窗口更平滑
比令牌桶實現稍簡單
比漏桶更靈活
3. 令牌桶算法(Token Bucket) 令牌桶算法是一種更靈活的限流策略,特別適合處理突發流量。 基本原理:
系統以固定速率往桶中放入令牌
桶可以儲存固定數量的令牌(桶容量)
每個請求需要消耗一個或多個令牌
如果桶中令牌不足,請求將被拒絕
即使桶是空的,令牌仍會持續生成並加入桶中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class TokenBucketRateLimiter { private readonly int _bucketCapacity; private readonly int _tokensPerSecond; private double _currentTokens; private DateTime _lastRefillTime; private readonly object _lock = new object (); public TokenBucketRateLimiter (int bucketCapacity, int tokensPerSecond ) { _bucketCapacity = bucketCapacity; _tokensPerSecond = tokensPerSecond; _currentTokens = bucketCapacity; _lastRefillTime = DateTime.UtcNow; } public bool ShouldAllowRequest (int tokens = 1 ) { lock (_lock) { RefillTokens(); if (_currentTokens >= tokens) { _currentTokens -= tokens; return true ; } return false ; } } private void RefillTokens () { var now = DateTime.UtcNow; var timeElapsed = (now - _lastRefillTime).TotalSeconds; var tokensToAdd = timeElapsed * _tokensPerSecond; _currentTokens = Math.Min(_bucketCapacity, _currentTokens + tokensToAdd); _lastRefillTime = now; } public double GetCurrentTokens () { lock (_lock) { RefillTokens(); return _currentTokens; } } } var rateLimiter = new TokenBucketRateLimiter(100 , 10 );if (rateLimiter.ShouldAllowRequest()){ } if (rateLimiter.ShouldAllowRequest(5 )) { }
優點:
支援突發流量(最大允許突發等於桶容量)
令牌生成速率穩定,可以更好地保護系統
支援不同權重的請求(可消耗不同數量的令牌)
實現相對簡單,容易理解
缺點:
令牌桶容量和生成速率的設置需要經驗
分佈式環境實現相對複雜
可能存在輕微的時間精度問題
適用場景:
API 限流
網絡流量控制
需要處理突發流量的場景
需要對不同請求進行差異化處理的場景
實現注意事項:
時間計算要使用 UTC 時間
需要考慮併發安全
浮點數計算可能存在精度問題
令牌生成速率要根據系統能力設置
分佈式實現建議:
使用 Redis 實現:
INCR:計數器
EXPIRE:設置過期時間
Lua 腳本:保證原子性
使用 Redis 時間戳記錄上次更新時間
分佈式鎖確保併發安全
效能優化建議:
使用批次令牌生成
緩存當前令牌數量
使用異步方式生成令牌
實現令牌預熱機制
監控指標:
令牌生成速率
當前桶內令牌數量
請求被拒絕率
令牌使用率
與其他限流演算法比較:
比固定窗口更平滑
比滑動窗口更靈活
比漏桶允許更多突發流量
4. 漏桶算法(Leaky Bucket) 漏桶算法提供了最嚴格的速率控制,適合需要穩定輸出的場景。 基本原理:
請求先進入桶中,桶有固定容量
桶以固定速率漏出請求
如果桶滿了,新請求會被拒絕
不論輸入速率如何,輸出速率都是恆定的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class LeakyBucketRateLimiter { private readonly Queue<DateTime> _bucket = new (); private readonly int _bucketCapacity; private readonly TimeSpan _leakInterval; private readonly object _lock = new object (); private DateTime _lastLeakTime; public LeakyBucketRateLimiter (int bucketCapacity, TimeSpan leakInterval ) { _bucketCapacity = bucketCapacity; _leakInterval = leakInterval; _lastLeakTime = DateTime.UtcNow; } public bool ShouldAllowRequest () { lock (_lock) { var now = DateTime.UtcNow; LeakRequests(now); if (_bucket.Count < _bucketCapacity) { _bucket.Enqueue(now); return true ; } return false ; } } private void LeakRequests (DateTime now ) { var elapsedTime = now - _lastLeakTime; var leaksCount = (int )(elapsedTime.TotalMilliseconds / _leakInterval.TotalMilliseconds); if (leaksCount > 0 ) { leaksCount = Math.Min(leaksCount, _bucket.Count); for (int i = 0 ; i < leaksCount; i++) { _bucket.Dequeue(); } _lastLeakTime = now; } } public int GetCurrentQueueSize () { lock (_lock) { return _bucket.Count; } } } var rateLimiter = new LeakyBucketRateLimiter(100 , TimeSpan.FromMilliseconds(100 ));if (rateLimiter.ShouldAllowRequest()){ } else { }
優點:
固定的流出速率,很好的流量整形效果
可以平滑地處理突發流量
具備削峰填谷的能力
保護下游系統的穩定性
缺點:
不支援突發流量
實現相對複雜
需要額外的隊列空間
可能造成請求的額外延遲
適用場景:
需要固定處理速率的場景
需要保護下游系統的場景
視頻流處理
消息佇列處理
數據庫寫入限流
實現注意事項:
時間計算精度
併發安全處理
記憶體使用效率
過期請求清理策略
分佈式實現建議:
Redis 實現方案:
List 作為請求隊列
Sorted Set 存儲時間戳
Lua 腳本保證原子性
使用消息隊列系統
分佈式鎖確保一致性
效能優化建議:
批次處理漏出的請求
使用更高效的數據結構
實現請求優先級
動態調整漏出速率
監控指標:
當前桶內請求數
請求被拒絕率
平均處理延遲
漏出速率穩定性
與其他限流演算法比較:
比固定窗口更穩定
比滑動窗口實現複雜
比令牌桶更嚴格
.NET Core 實作方案 使用 AspNetCoreRateLimit 套件 最簡單的方式是使用現成的 AspNetCoreRateLimit 套件:
首先安裝套件:
1 dotnet add package AspNetCoreRateLimit
在 Program.cs 中設定服務:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var builder = WebApplication.CreateBuilder(args );builder.Services.AddMemoryCache(); builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting" )); builder.Services.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies" )); builder.Services.AddInMemoryRateLimiting(); builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>(); var app = builder.Build();app.UseIpRateLimiting();
在 appsettings.json 中設定限流規則:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 { "IpRateLimiting" : { "EnableEndpointRateLimiting" : true , "StackBlockedRequests" : false , "RealIpHeader" : "X-Real-IP" , "ClientIdHeader" : "X-ClientId" , "HttpStatusCode" : 429 , "IpWhitelist" : [ "127.0.0.1" , "192.168.0.0/24" ] , "ClientWhitelist" : [ "dev-id" , "trusted-app" ] , "GeneralRules" : [ { "Endpoint" : "*" , "Period" : "1s" , "Limit" : 10 } , { "Endpoint" : "*" , "Period" : "1m" , "Limit" : 100 } ] } }
自定義中介軟體實作 如果需要更靈活的控制,可以實作自己的限流中介軟體:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class RateLimitMiddleware { private readonly RequestDelegate _next; private readonly IMemoryCache _cache; private readonly string _cacheKey = "RateLimit" ; public RateLimitMiddleware (RequestDelegate next, IMemoryCache cache ) { _next = next; _cache = cache; } public async Task InvokeAsync (HttpContext context ) { var ipAddress = context.Connection.RemoteIpAddress?.ToString(); var cacheKey = $"{_cacheKey} _{ipAddress} " ; var rateLimiter = _cache.GetOrCreate(cacheKey, entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1 ); return new FixedWindowRateLimiter(100 , TimeSpan.FromMinutes(1 )); }); if (!rateLimiter.ShouldAllowRequest()) { context.Response.StatusCode = StatusCodes.Status429TooManyRequests; context.Response.Headers.Add("Retry-After" , "60" ); context.Response.Headers.Add("X-RateLimit-Limit" , "100" ); context.Response.Headers.Add("X-RateLimit-Remaining" , "0" ); context.Response.Headers.Add("X-RateLimit-Reset" , DateTimeOffset.UtcNow.AddMinutes(1 ).ToUnixTimeSeconds().ToString()); await context.Response.WriteAsync("Too many requests. Please try again later." ); return ; } await _next(context); } }
分散式限流方案 在分散式系統中,建議使用 Redis 來實作限流機制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class RedisRateLimiter { private readonly IConnectionMultiplexer _redis; private readonly string _keyPrefix; public RedisRateLimiter (IConnectionMultiplexer redis, string keyPrefix ) { _redis = redis; _keyPrefix = keyPrefix; } public async Task<bool > ShouldAllowRequestAsync (string clientId, int limit, TimeSpan window ) { var db = _redis.GetDatabase(); var key = $"{_keyPrefix} :{clientId} " ; var tran = db.CreateTransaction(); var countTask = db.StringGetAsync(key); var count = int .Parse((await countTask).ToString() ?? "0" ); if (count >= limit) return false ; await db.StringIncrementAsync(key); await db.KeyExpireAsync(key, window); return true ; } }
使用範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class DistributedRateLimitMiddleware { private readonly RequestDelegate _next; private readonly RedisRateLimiter _rateLimiter; public DistributedRateLimitMiddleware (RequestDelegate next, IConnectionMultiplexer redis ) { _next = next; _rateLimiter = new RedisRateLimiter(redis, "ratelimit" ); } public async Task InvokeAsync (HttpContext context ) { var clientId = context.Connection.RemoteIpAddress?.ToString() ?? "unknown" ; if (!await _rateLimiter.ShouldAllowRequestAsync(clientId, 100 , TimeSpan.FromMinutes(1 ))) { context.Response.StatusCode = StatusCodes.Status429TooManyRequests; await context.Response.WriteAsync("Rate limit exceeded" ); return ; } await _next(context); } }
最佳實踐建議
適當的限流閾值
根據系統資源和業務需求設定合理的限流閾值
可以為不同的 API 端點設定不同的限制
考慮系統的實際承載能力進行設定
分層限流
IP 位址限流
使用者身份限流
API 端點限流
全域限流
監控和警報
設置監控系統追蹤限流情況
當限流次數異常時發出警報
記錄限流事件以便分析
優雅的處理
返回適當的 HTTP 429 狀態碼
在響應標頭中提供重試時間建議
提供清晰的錯誤訊息
實作退避策略
快取考量
使用分散式快取來支援水平擴展
定期清理過期的限流記錄
設置適當的快取過期時間
環境配置建議 開發環境 1 2 3 4 5 6 7 8 { "RateLimiting" : { "Enabled" : false , "WhitelistEnabled" : true , "DefaultLimit" : 1000 , "Period" : "1m" } }
測試環境 1 2 3 4 5 6 7 8 9 { "RateLimiting" : { "Enabled" : true , "WhitelistEnabled" : true , "DefaultLimit" : 100 , "Period" : "1m" , "MonitoringEnabled" : true } }
生產環境 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 { "RateLimiting" : { "Enabled" : true , "WhitelistEnabled" : true , "DefaultLimit" : 60 , "Period" : "1m" , "MonitoringEnabled" : true , "AlertingEnabled" : true , "Rules" : [ { "Endpoint" : "/api/public/*" , "Limit" : 30 , "Period" : "1m" } , { "Endpoint" : "/api/authenticated/*" , "Limit" : 100 , "Period" : "1m" } ] , "IpWhitelist" : [ ] , "ClientWhitelist" : [ "internal-service" , "monitoring-service" ] , "AlertThresholds" : { "RejectionRate" : 0.1 , "RequestCount" : 1000 } } }
生產環境配置重點:
較嚴格的限流規則
針對不同 API 路徑設定不同限制
啟用監控和警報機制
最小化白名單範圍
設定警報閾值
效能測試方法 使用 Apache JMeter 進行測試
基本測試計劃
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" ?> <jmeterTestPlan version ="1.2" properties ="5.0" > <ThreadGroup guiclass ="ThreadGroupGui" testname ="API Rate Limit Test" > <elementProp name ="ThreadGroup.main_controller" > <stringProp name ="LoopController.loops" > 100</stringProp > <stringProp name ="ThreadGroup.num_threads" > 50</stringProp > <stringProp name ="ThreadGroup.ramp_time" > 10</stringProp > </elementProp > </ThreadGroup > </jmeterTestPlan >
測試場景
正常負載測試:模擬正常使用情況
突發負載測試:短時間內大量請求
持續高負載測試:長時間維持高請求量
限流觸發測試:確認限流機制生效
監控指標
響應時間
請求成功率
限流觸發率
系統資源使用情況
使用 K6 進行效能測試 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import http from 'k6/http' ;import { check, sleep } from 'k6' ;export const options = { stages : [ { duration : '30s' , target : 20 }, { duration : '1m' , target : 100 }, { duration : '30s' , target : 100 }, { duration : '30s' , target : 0 }, ], }; export default function ( ) { const res = http.get ('http://api.example.com/test' ); check (res, { 'is status 200' : (r ) => r.status === 200 , 'is rate limited' : (r ) => r.status !== 429 , }); sleep (1 ); }
效能測試重點
基準測試
在無限流情況下測試系統基準性能
記錄關鍵指標作為參考值
限流閾值驗證
驗證限流規則是否如預期運作
測試不同限流策略的效果
系統穩定性測試
長時間運行測試
監控系統資源使用情況
評估記憶體洩漏風險
擴展性測試
結論 API 限流是確保系統穩定性和可用性的關鍵機制。通過本文介紹的各種限流策略和實作方式,開發者可以根據實際需求選擇合適的解決方案。重點建議:
選擇適當的限流策略
固定窗口:簡單直接,適合基本場景
滑動窗口:更精確的控制,避免邊界問題
令牌桶:適合處理突發流量
漏桶:適合需要穩定出口速率的場景
分散式架構考量
使用 Redis 等分散式解決方案
確保限流規則在叢集中同步
考慮資料一致性問題
監控和維護
即時監控限流情況
定期評估和調整限流規則
建立完善的警報機制
持續優化
根據監控數據調整限流策略
定期進行效能測試
及時應對新的挑戰
最後,記住限流不僅是一個技術問題,也是一個業務問題。在實作限流時,需要根據具體的業務需求和系統資源來制定合適的限流策略。通過合理的限流機制,我們可以在保護系統的同時,為使用者提供穩定且可靠的服務。
參考資料