.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方法作为程序的入口点。
.NET6运行时动态更新限流阈值的更多相关文章
- C# 在运行时动态创建类型
C# 在运行时动态的创建类型,这里是通过动态生成C#源代码,然后通过编译器编译成程序集的方式实现动态创建类型 public static Assembly NewAssembly() { //创建编译 ...
- LINQ to SQL 运行时动态构建查询条件
在进行数据查询时,经常碰到需要动态构建查询条件.使用LINQ实现这个需求可能会比以前拼接SQL语句更麻烦一些.本文介绍了3种运行时动态构建查询条件的方法.本文中的例子最终实现的都是同一个功能,从Nor ...
- 使用javassist运行时动态重新加载java类及其他替换选择
在不少的情况下,我们需要对生产中的系统进行问题排查,但是又不能重启应用,java应用不同于数据库的存储过程,至少到目前为止,还不能原生的支持随时进行编译替换,从这种角度来说,数据库比java的动态性要 ...
- 运行时动态库:not found 及介绍-linux的-Wl,-rpath命令
---此文章同步自我的CSDN博客--- 一.运行时动态库:not found 今天在使用linux编写c/c++程序时,需要用到第三方的动态库文件.刚开始编译完后,运行提示找不到动态库文件.我就 ...
- C++高效安全的运行时动态类型转换
关键字:static_cast,dynamic_cast,fast_dynamic_cast,VS 2015. OS:Window 10. C++类之间类型转换有:static_cast.dynami ...
- 转: gcc 指定运行时动态库路径
gcc 指定运行时动态库路径 Leave a reply 由于种种原因,Linux 下写 c 代码时要用到一些外部库(不属于标准C的库),可是由于没有权限,无法将这写库安装到系统目录,只好安装用户目录 ...
- [转] Java运行时动态生成class的方法
[From] http://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000 廖雪峰 / 编 ...
- 运行时动态伪造vsprintf的va_list
运行时动态伪造vsprintf的va_list #include <stdio.h> int main() { char* m = (char*) malloc(sizeof(int)*2 ...
- SpringBoot运行时动态添加数据源
此方案适用于解决springboot项目运行时动态添加数据源,非静态切换多数据源!!! 一.多数据源应用场景: 1.配置文件配置多数据源,如默认数据源:master,数据源1:salve1...,运行 ...
随机推荐
- Unity——对象池管理
Unity对象池管理 一.Demo展示 二.逻辑 在游戏中会出现大量重复的物体需要频繁的创建和销毁:比如子弹,敌人,成就列表的格子等: 频繁的创建删除物体会造成很大的开销,像这种大量创建重复且非持续性 ...
- T-SQL——透视PIVOT动态获取待扩展元素集
目录 0.背景说明 1.准备测试数据 2.示例1--利用SELECT循环赋值 3.示例2--使用游标 4.示例3--使用FOR XML PATH() 5. 参考 志铭-2021年10月8日 00:57 ...
- 面试官一口气问了MySQL事务、锁和MVCC,我
面试官:你是怎么理解InnoDB引擎中的事务的? 候选者:在我的理解下,事务可以使「一组操作」要么全部成功,要么全部失败 候选者:事务其目的是为了「保证数据最终的一致性」. 候选者:举个例子,我给你发 ...
- 10.13 Nginx 负载均衡
七层负载均衡server { listen 80; server_name localhost; location / { proxy_pass http://name; //调用集群 } } ups ...
- 点击按钮改变div背景色,再次点击恢复 -- 原生JS
如果对您有帮助,记得点个赞哦!
- 关于django配置好静态文件后打开相关图片页显示404的解决方法
在url里设置以上代码即可,即可解决图片显示异常(出现此问题的根本原因是django版本)django3后需要加以上代码)
- java课堂测试2第一阶段:方法运用
package test2; import java.util.*; public class Test2 { public static int generateRandom(int fanwei) ...
- 电脑(windows)端口被占用如何解决
问题: 今天在启动项目的时候,控制台提示"8080端口被占用",此时我并没有启动其他项目.那么8080端口被占用解决方法如下: 1.点击左下角"开始",在搜索框 ...
- 天脉2(ACoreOS653)操作系统学习01
天脉2(ACoreOS653)操作系统学习01 由于我的毕业设计涉及相关嵌入式操作系统,故最近学了学天脉2操作系统. 一.ARINC653标准 1.ARINC653标准是什么? ARINC 653 : ...
- 改善深层神经网络-week1编程题(Initializaion)
Initialization 如何选择初始化方式,不同的初始化会导致不同的结果 好的初始化方式: 加速梯度下降的收敛(Speed up the convergence of gradient desc ...