.NET6運(yùn)行時(shí)動(dòng)態(tài)更新限流閾值

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

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

限流需求

這里假設(shè)需求是這樣的:

  • 有一個(gè)天氣服務(wù),包含兩個(gè)接口:GetToday(獲取今天的天氣)、GetTomorrow(獲取明天的天氣)。
  • 對(duì)每個(gè)訪問者分別單獨(dú)限流,具體限流閾值:GetToday 20次/秒、GetTomorrow 10次/秒,所有接口總計(jì) 25次/秒。
  • 每秒的訪問次數(shù)并不均勻,有一定的突發(fā)請(qǐng)求。大部分情況下低于限流閾值,極少數(shù)時(shí)可能會(huì)超出限流閾值30%。

限流配置

FireflySoft.RateLimit中不同的限流算法有不同的限流規(guī)則定義,因?yàn)橛型话l(fā)情況,所以這里采用令牌桶算法。根據(jù)限流需求,這里定義了一個(gè)限流配置,它是應(yīng)用到每一個(gè)用戶的。

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 指定應(yīng)用到的接口類型:?jiǎn)蝹€(gè)接口還是所有接口
  • TokenCapacity 是令牌桶容量
  • TokenSpeed 是令牌放入速度,這里固定單位是:個(gè)/秒,F(xiàn)ireflySoft.RateLimit支持更小的時(shí)間單位。

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

處理架構(gòu)

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

image

如上圖所示,業(yè)務(wù)服務(wù)集成了限流功能,核心模塊有兩個(gè):

  • 限流處理:這個(gè)直接集成FireflySoft.RateLimit.AspNetCore即可實(shí)現(xiàn)。
  • 監(jiān)控配置變更:這是單獨(dú)擴(kuò)展的部分,主要邏輯是:讀取數(shù)據(jù)庫(kù)中的限流規(guī)則配置,如果有變化,則調(diào)用FireflySoft.RateLimit的限流規(guī)則更新接口。

其它模塊:

  • 構(gòu)造錯(cuò)誤:這個(gè)也是FireflySoft.RateLimit.AspNetCore自帶的功能,可以自定義錯(cuò)誤碼和錯(cuò)誤消息內(nèi)容。
  • 限流配置更改程序:這里沒有實(shí)現(xiàn)。功能就是更改數(shù)據(jù)庫(kù)中的限流規(guī)則配置,我們測(cè)試直接改數(shù)據(jù)庫(kù)就行了。

編寫代碼

這里寫了一個(gè)基于.Net6 的 WebAPI demo,項(xiàng)目結(jié)構(gòu)如下圖,你也可以直接點(diǎn)開查看:samples/aspnetcore6 (github.com)

image

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

1、創(chuàng)建或打開你的項(xiàng)目

打開項(xiàng)目,你可以用Visual Studio,也可以用Visual Studio Code。

如果項(xiàng)目是.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、配置數(shù)據(jù)庫(kù)表

你需要有一個(gè)MySQL,我建議是5.7及以上,下邊是創(chuàng)建表和測(cè)試配置的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');

打開項(xiàng)目中的appsettings.json,添加一個(gè)DbConn的配置項(xiàng):

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

這里邊的數(shù)據(jù)庫(kù)地址、數(shù)據(jù)庫(kù)名稱、帳號(hào)密碼、字符集都需要改成自己的。

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

4、編寫”監(jiān)控配置變更“

在上邊的架構(gòu)圖中,提到一個(gè)”監(jiān)控配置變更“的部分,這個(gè)是這篇文章的重頭戲。FireflySoft.RateLimit自身沒有提供這部分,需要根據(jù)需求自己實(shí)現(xiàn)。我這里提供一個(gè)實(shí)現(xiàn)方案,僅供參考。

這個(gè)部分我寫了5個(gè)文件:

  • RateLimitRuleDAO.cs:實(shí)現(xiàn)從數(shù)據(jù)庫(kù)查詢出限流規(guī)則配置。
  • RateLimitConfigurationManager.cs:實(shí)現(xiàn)跟蹤數(shù)據(jù)庫(kù)中的限流配置變更,如果有變更則觸發(fā)一個(gè)事件。
  • NonCapturingTimer.cs:用于定時(shí)查詢數(shù)據(jù)庫(kù)中的限流配置。不捕捉上下文的Timer,用習(xí)慣了而已。
  • AutoUpdateAlgorithmManager.cs:注冊(cè)事件到RateLimitConfigurationManager中,事件發(fā)生時(shí)更新到限流算法中。
  • AutoUpdateAlgorithmService.cs:方便注冊(cè)服務(wù):向ASP.NET Core中注冊(cè)上邊這幾個(gè)服務(wù)。

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

5、注冊(cè)服務(wù)和使用中間件

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

  • builder.Services.AddAutoUpdateRateLimitAlgorithm 這個(gè)在AutoUpdateAlgorithmService.cs中定義的。
  • builder.Services.AddRateLimit 這個(gè)是FireflySoft.RateLimit.AspNetCore定義的。
  • app.UseRateLimit() 這個(gè)是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、啟動(dòng)服務(wù)并測(cè)試

可以使用Postman來運(yùn)行一個(gè)Runner,執(zhí)行100次,看看實(shí)際效果。

image

關(guān)于.NET6

雖然標(biāo)題中提到了.NET6,不過到目前為止還沒看到什么關(guān)于.NET6的特別內(nèi)容,所以這里特別準(zhǔn)備了一點(diǎn)關(guān)于.NET6的內(nèi)容,否則就太標(biāo)題黨了。

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

Namespace

現(xiàn)在namespace可以直接聲明應(yīng)用到整個(gè)文件,不需要再加大括號(hào),被括號(hào)層級(jí)折磨的人輕松了。

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的內(nèi)容合并到Program.cs中了,并且不需要顯式編寫main方法,直接一行行的寫就行了。這樣確實(shí)又簡(jiǎn)便了一些。主要內(nèi)容還是那兩部分:構(gòu)建Web應(yīng)用(注冊(cè)服務(wù)、使用中間件)、運(yùn)行Web應(yīng)用。

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

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

app.Run();

不過一個(gè)應(yīng)用中只能有一個(gè)這樣的文件,你也不能再寫其它main方法作為程序的入口點(diǎn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容