.NET6運行時動態更新限流閾值


昨天博客園撐不住流量又崩潰了,很巧正在編寫這篇文章,於是產生一個假想:如果博客園用上我這個限流組件會怎么樣呢?

  • 用戶會收到幾個429錯誤,並且多刷新幾次就看到了內容,不會出現完全不可用。
  • 還可以降低查詢接口的限流閾值,提升保存接口的限流閾值,這樣寫操作影響比較小,創作不易,丟了就麻煩了。
  • 然后后端服務不會崩潰,可以從容的增加服務器容量,然后再增大限流閾值。
  • 如果能識別出來非正常的用戶請求,比如IP、Cookie、Url等請求攜帶的特定信息,那專門對它們限流的效果會很好。
  • 如果是數據庫先撐不住,那這個限流就形同虛設了,說明限流閾值可能太大,需要做下壓測,看看多少合適。

當然還可能有一種結果,我這個限流組件撐不住,這可能嗎?如果基於內存的限流計數,每個節點每秒可支撐百萬級別,如果基於Redis限流計數,這個就得看網絡IO和Redis IO了,也不應該是組件的問題。


這里開始正文。

FireflySoft.RateLimit發布以來,幫助了不少需要在.net中進行限流處理的用戶。前段時間有個開發者發了一個pull request,大意是Redis重啟的時候Lua script會丟失,但是程序中還認為它存在,所以就會一直拋出異常,那位同學通過捕捉一個特定異常再reload Lua script的方式解決了這個問題。經過一段時間的測,試運行良好,因為這個問題還是相對常見的,所以就發布了一個版本 2.0.2,建議通過nuget盡快升級。

之前還有用戶問怎么在程序執行過程中動態更改限流的閾值,比如原來限流100/s,現在服務性能更好了,要改成限流300/s。FireflySoft.RateLimit底層是支持的,通過IAlgorithm.UpdateRules或者UpdateRulesAsync即可實現。不過這只是開放了一個基礎能力,實際還需要開發者自己去做更多的工作,比如定義限流閾值的數據格式、從其它配置系統中定時獲取最新的限流閾值等。為了更方便開發者使用這個類庫,同時恰逢.NET 6正式發布,所以這里用.NET6編寫一個Demo程序,可以實現程序運行時動態更新限流閾值。

限流需求

這里假設需求是這樣的:

  • 有一個天氣服務,包含兩個接口:GetToday(獲取今天的天氣)、GetTomorrow(獲取明天的天氣)。
  • 對每個訪問者分別單獨限流,具體限流閾值:GetToday 20次/秒、GetTomorrow 10次/秒,所有接口總計 25次/秒。
  • 每秒的訪問次數並不均勻,有一定的突發請求。大部分情況下低於限流閾值,極少數時可能會超出限流閾值30%。

限流配置

FireflySoft.RateLimit中不同的限流算法有不同的限流規則定義,因為有突發情況,所以這里采用令牌桶算法。根據限流需求,這里定義了一個限流配置,它是應用到每一個用戶的。

public class RateLimitConfiguration
{
    public string? Path { get; set; }
    public LimitPathType PathType { get; set; }
    public int TokenCapacity { get; set; }
    public int TokenSpeed { get; set; }
}

其中:

  • Path 用來定義接口路徑,形如:/WeatherForecast/GetToday
  • PathType 指定應用到的接口類型:單個接口還是所有接口
  • TokenCapacity 是令牌桶容量
  • TokenSpeed 是令牌放入速度,這里固定單位是:個/秒,FireflySoft.RateLimit支持更小的時間單位。

同時為了方便限流規則的更新,它可以用來傳輸或者持久化到各種存儲中。我把配置保存在MySQL中,更改限流閾值時更新數據庫內容,應用限流閾值時從數據庫中查詢限流閾值。你也可以把這個配置放到任何其它地方,比如Consul、Redis,甚至配置文件中。

處理架構

為了描述的更清晰,我這里提供一張圖:

 

如上圖所示,業務服務集成了限流功能,核心模塊有兩個:

  • 限流處理:這個直接集成FireflySoft.RateLimit.AspNetCore即可實現。
  • 監控配置變更:這是單獨擴展的部分,主要邏輯是:讀取數據庫中的限流規則配置,如果有變化,則調用FireflySoft.RateLimit的限流規則更新接口。

其它模塊:

  • 構造錯誤:這個也是FireflySoft.RateLimit.AspNetCore自帶的功能,可以自定義錯誤碼和錯誤消息內容。
  • 限流配置更改程序:這里沒有實現。功能就是更改數據庫中的限流規則配置,我們測試直接改數據庫就行了。

編寫代碼

這里寫了一個基於.Net6 的 WebAPI demo,項目結構如下圖,你也可以直接點開查看:samples/aspnetcore6 (github.com)

 

為了方便集成到自己的項目中,這里也寫一下具體的使用步驟:

1、創建或打開你的項目

打開項目,你可以用Visual Studio,也可以用Visual Studio Code。

如果項目是.NET Framework,必須是4.6.1及以上。如果是.NET Core,必須是2.0及以上。這里是.NET6。

 

2、安裝Nuget包

你可以使用Package Manager:

Install-Package FireflySoft.RateLimit.AspNetCore -Version 2.0.2-rc1

也可使用.NET CLI:

dotnet add package FireflySoft.RateLimit.AspNetCore --version 2.0.2-rc1

3、配置數據庫表

你需要有一個MySQL,我建議是5.7及以上,下邊是創建表和測試配置的SQL腳本:

CREATE TABLE `rate_limit_rule` (
  `Id` varchar(40) NOT NULL,
  `Path` varchar(100) NOT NULL,
  `PathType` int(11) NOT NULL,
  `TokenCapacity` int(11) NOT NULL,
  `TokenSpeed` int(11) NOT NULL,
  `AddTime` datetime NOT NULL,
  `UpdateTime` datetime NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

INSERT INTO rate_limit_rule (Id,`Path`,PathType,TokenCapacity,TokenSpeed,AddTime,UpdateTime) VALUES
     ('1','/WeatherForecast/GetToday',1,26,20,'2021-11-16 00:00:00.0','2021-11-16 00:00:00.0'),
     ('2','/WeatherForecast/GetTomorrow',1,13,10,'2021-11-16 00:00:00.0','2021-11-16 00:00:00.0'),
     ('3','All',2,29,25,'2021-11-16 00:00:00.0','2021-11-16 00:00:00.0');

打開項目中的appsettings.json,添加一個DbConn的配置項:

{
  "DbConn":"Server=127.0.0.1;User ID=root;Password=l123456;port=3306;Database=ratelimit;CharSet=utf8mb4;",
  ...
}

這里邊的數據庫地址、數據庫名稱、帳號密碼、字符集都需要改成自己的。

你也可以使用其它的數據庫連接配置方式,比如放到Consul中,或者寫到自己的配置中心,甚至寫死在代碼中。

4、編寫”監控配置變更“

在上邊的架構圖中,提到一個”監控配置變更“的部分,這個是這篇文章的重頭戲。FireflySoft.RateLimit自身沒有提供這部分,需要根據需求自己實現。我這里提供一個實現方案,僅供參考。

這個部分我寫了5個文件:

  • RateLimitRuleDAO.cs:實現從數據庫查詢出限流規則配置。
  • RateLimitConfigurationManager.cs:實現跟蹤數據庫中的限流配置變更,如果有變更則觸發一個事件。
  • NonCapturingTimer.cs:用於定時查詢數據庫中的限流配置。不捕捉上下文的Timer,用習慣了而已。
  • AutoUpdateAlgorithmManager.cs:注冊事件到RateLimitConfigurationManager中,事件發生時更新到限流算法中。
  • AutoUpdateAlgorithmService.cs:方便注冊服務:向ASP.NET Core中注冊上邊這幾個服務。

代碼量比較大,這里就不貼了,可以到Github上查看詳細。

5、注冊服務和使用中間件

.NET6中這部分要寫到Program.cs中,限於篇幅,這里省略了很多代碼,只需要關注如下幾行:

  • builder.Services.AddAutoUpdateRateLimitAlgorithm 這個在AutoUpdateAlgorithmService.cs中定義的。
  • builder.Services.AddRateLimit 這個是FireflySoft.RateLimit.AspNetCore定義的。
  • app.UseRateLimit() 這個是FireflySoft.RateLimit.AspNetCore定義的。
using aspnetcore6.RateLimit;
using FireflySoft.RateLimit.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

...

// Add firefly soft rate limit service
builder.Services.AddAutoUpdateRateLimitAlgorithm();
builder.Services.AddRateLimit(serviceProvider =>
{
    var algorithmManager = serviceProvider.GetService<AutoUpdateAlgorithmManager>();
    if (algorithmManager != null)
    {
        return algorithmManager.GetAlgorithmInstance();
    }

    return null;
});

var app = builder.Build();

...

// Use firefly soft rate limit middleware
app.UseRateLimit();

app.MapControllers();

app.Run();

6、啟動服務並測試

可以使用Postman來運行一個Runner,執行100次,看看實際效果。

 

關於.NET6

雖然標題中提到了.NET6,不過到目前為止還沒看到什么關於.NET6的特別內容,所以這里特別准備了一點關於.NET6的內容,否則就太標題黨了。

如果你使用過.NET Core,其實.NET6用起來也沒有太多變化,很多.net core、.net standard的庫也都兼容,這里列舉兩點我感覺變化比較大的地方:

Namespace

現在namespace可以直接聲明應用到整個文件,不需要再加大括號,被括號層級折磨的人輕松了。

using System.Collections.ObjectModel;

namespace aspnetcore6.RateLimit;

public class RateLimitConfiguration
{
    public string? Path { get; set; }
    public LimitPathType PathType { get; set; }
    public int TokenCapacity { get; set; }
    public int TokenSpeed { get; set; }
}

Top-level statements

Program.cs和Startup.cs的內容合並到Program.cs中了,並且不需要顯式編寫main方法,直接一行行的寫就行了。這樣確實又簡便了一些。主要內容還是那兩部分:構建Web應用(注冊服務、使用中間件)、運行Web應用。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
...

var app = builder.Build();
...
app.MapControllers();

app.Run();

不過一個應用中只能有一個這樣的文件,你也不能再寫其它main方法作為程序的入口點。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM