限流是后端服務開發中經常要集成的一個功能,對於防范系統因壓力過大導致崩潰特別有用。在多租戶系統中,限流對於限制單個租戶使用的資源量也特別有用,這篇文章就來一探究竟。
問題
對於一個多租戶系統,某些租戶如果使用了過多的資源,很可能會對其它租戶造成影響。比如對於某個資源的查詢,系統的最高容量是300qps,假設正常情況下租戶的查詢水位都在10qps,此時可以同時為30個租戶服務;突然某個租戶的查詢水位上升到100qps,如果系統沒有限流,系統會變慢甚至可能崩潰,如果系統有限流,所有的租戶都可能受到限流的影響。
租戶的查詢水位突然暴漲可能是正常業務,也可能是惡意攻擊,不過無論哪種情況對其它租戶來說都是不公平的。這時候就可以對不同的租戶分別限流,下面就來看一下如何解決。
原理
下面這張圖演示了對多租戶系統進行限流的原理:
租戶A、B、C分別發起服務請求,服務中首先對用戶身份進行鑒別,身份驗證通過的進入限流檢查環節,對每個租戶分別進行限流檢查,如果未達到限流閾值,則可以通過進入下一步,如果達到了限流閾值,則終止處理並返回錯誤給租戶。
實現
如果你已經看懂了原理,相信你完全有能力寫出對應的限流控制邏輯。不過為了研發效率,還是應該盡可能重用現有的組件,這里使用 FireflySoft.RateLimit 來實現這一邏輯,演示代碼是以ASP.NET Core為基礎的,你也可以在任何其它.NET網絡服務框架中使用,甚至自己手寫的專用處理程序。
安裝Nuget包
使用包管理器控制台:
Install-Package FireflySoft.RateLimit.AspNetCore
或者使用 .NET CLI:
dotnet add package FireflySoft.RateLimit.AspNetCore
或者直接添加到項目文件中:
<ItemGroup> <PackageReference Include="FireflySoft.RateLimit.AspNetCore" Version="2.*" /> </ItemGroup>
使用中間件
在Startup.cs中注冊限流服務並使用限流中間件。
public void ConfigureServices(IServiceCollection services) { ... services.AddRateLimit(new InProcessFixedWindowAlgorithm( new[] { new FixedWindowRule() { Id = "1", ExtractTarget = context => { // 這里假設限流的目標是租戶的用戶Id,它是從HTTP Header中傳遞過來的 return (context as HttpContext).Request.GetTypedHeaders().Get<string>("userId"); }, CheckRuleMatching = context => { // 假設只對 /Weather/GetToday 這個接口請求進行限流 var path = (context as HttpContext).Request.Path.Value; if(path == "/Weather/GetToday"){ return true; } return false; }, Name="多租戶限流", LimitNumber=10, // 限流閾值 StatWindow=TimeSpan.FromSeconds(1), //限流的時間窗口,這里是1秒 StartTimeType=StartTimeType.FromNaturalPeriodBeign } }) ); ... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... app.UseRateLimit(); ... }
搞定這兩步,多租戶分別限流功能就可以正常運轉了。
這里對所有租戶都使用了相同的限流閾值,如果租戶有特權租戶和普通租戶的區別,則可以在上邊的限流規則中分別為特權租戶和普通租戶定義不同的規則就可以了,具體可以參考這篇文章:ASP.NET Core中如何對不同類型的用戶進行區別限流。
其它技術問題
注意這里使用的限流算法是進程內固定窗口限流,如果你需要在分布式環境下統一計數限流,則可以使用 FireflySoft.RateLimit 自帶的Redis固定窗口限流算法,不過你需要先有一個Redis服務。
固定窗口算法比較剛性,實際情況下,請求很可能是不均勻的,一會多一會少,剛性算法很難設置一個合理的限流閾值。如果你想要允許一定的突發流量,則可以使用滑動窗口、漏桶、令牌桶等算法,FireflySoft.RateLimit 中已經集成了這些算法,直接使用即可。
如果系統的服務能力增強了,現在允許一個租戶每秒發起20次請求,則需要調整規則,FireflySoft.RateLimit底層是支持動態調整規則的,具體可以看這篇文章:.NET6運行時動態更新限流 。
好了,以上就是本文的主要內容了。如有興趣,歡迎訪問開源倉庫:https://github.com/bosima/FireflySoft.RateLimit