美文网首页
.NET6运行时动态更新限流阈值

.NET6运行时动态更新限流阈值

作者: 萤火架构 | 来源:发表于2021-11-26 20:57 被阅读0次

    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,甚至配置文件中。

    处理架构

    为了描述的更清晰,我这里提供一张图:

    image

    如上图所示,业务服务集成了限流功能,核心模块有两个:

    • 限流处理:这个直接集成FireflySoft.RateLimit.AspNetCore即可实现。
    • 监控配置变更:这是单独扩展的部分,主要逻辑是:读取数据库中的限流规则配置,如果有变化,则调用FireflySoft.RateLimit的限流规则更新接口。

    其它模块:

    • 构造错误:这个也是FireflySoft.RateLimit.AspNetCore自带的功能,可以自定义错误码和错误消息内容。
    • 限流配置更改程序:这里没有实现。功能就是更改数据库中的限流规则配置,我们测试直接改数据库就行了。

    编写代码

    这里写了一个基于.Net6 的 WebAPI demo,项目结构如下图,你也可以直接点开查看:samples/aspnetcore6 (github.com)

    image

    为了方便集成到自己的项目中,这里也写一下具体的使用步骤:

    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次,看看实际效果。

    image

    关于.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方法作为程序的入口点。

    相关文章

      网友评论

          本文标题:.NET6运行时动态更新限流阈值

          本文链接:https://www.haomeiwen.com/subject/ynvxxrtx.html