昨天博客园撑不住流量又崩溃了,很巧正在编写这篇文章,于是产生一个假想:如果博客园用上我这个限流组件会怎么样呢?

  • 用户会收到几个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运行时动态更新限流阈值的更多相关文章

  1. C# 在运行时动态创建类型

    C# 在运行时动态的创建类型,这里是通过动态生成C#源代码,然后通过编译器编译成程序集的方式实现动态创建类型 public static Assembly NewAssembly() { //创建编译 ...

  2. LINQ to SQL 运行时动态构建查询条件

    在进行数据查询时,经常碰到需要动态构建查询条件.使用LINQ实现这个需求可能会比以前拼接SQL语句更麻烦一些.本文介绍了3种运行时动态构建查询条件的方法.本文中的例子最终实现的都是同一个功能,从Nor ...

  3. 使用javassist运行时动态重新加载java类及其他替换选择

    在不少的情况下,我们需要对生产中的系统进行问题排查,但是又不能重启应用,java应用不同于数据库的存储过程,至少到目前为止,还不能原生的支持随时进行编译替换,从这种角度来说,数据库比java的动态性要 ...

  4. 运行时动态库:not found 及介绍-linux的-Wl,-rpath命令

    ---此文章同步自我的CSDN博客--- 一.运行时动态库:not found   今天在使用linux编写c/c++程序时,需要用到第三方的动态库文件.刚开始编译完后,运行提示找不到动态库文件.我就 ...

  5. C++高效安全的运行时动态类型转换

    关键字:static_cast,dynamic_cast,fast_dynamic_cast,VS 2015. OS:Window 10. C++类之间类型转换有:static_cast.dynami ...

  6. 转: gcc 指定运行时动态库路径

    gcc 指定运行时动态库路径 Leave a reply 由于种种原因,Linux 下写 c 代码时要用到一些外部库(不属于标准C的库),可是由于没有权限,无法将这写库安装到系统目录,只好安装用户目录 ...

  7. [转] Java运行时动态生成class的方法

    [From] http://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000 廖雪峰 / 编 ...

  8. 运行时动态伪造vsprintf的va_list

    运行时动态伪造vsprintf的va_list #include <stdio.h> int main() { char* m = (char*) malloc(sizeof(int)*2 ...

  9. SpringBoot运行时动态添加数据源

    此方案适用于解决springboot项目运行时动态添加数据源,非静态切换多数据源!!! 一.多数据源应用场景: 1.配置文件配置多数据源,如默认数据源:master,数据源1:salve1...,运行 ...

随机推荐

  1. Ubuntu开发相关环境搭建

    一.Ubuntu系统语言环境切换修改 安装时,选择的中文版,但实际使用起来,很不爽,果断切换为英文 1.1 打开终端: vim /etc/default/locale 1.2 修改配置 LANG=&q ...

  2. SDOI2015 排序

    SDOI2015 排序 今天看到这道题,没有一点思路,暴力都没的打...还是理解错题意了,操作不同位置不是说改不同的区间,而是不同操作的顺序...考场上如果知道这个的话最少暴力拿一半啊,因为正解本来就 ...

  3. (STAR-CCM+教程)001 软件安装以及界面介绍

    STAR-CCM+是西门子公司旗下产出的一款CFD软件,因其强大的多面体网格划分功能.简易的操作流程被广泛应用于工程计算以及科研工作中. 学习资源 个人在使用STAR-CCM+过程中,主要参考资料来源 ...

  4. Serverless 工程实践 | Serverless 应用优化与调试秘诀

    作者|刘宇   前言:本文将以阿里云函数计算为例,提供了在线调试.本地调试等多种应用优化与调试方案. Serverless 应用调试秘诀 在应用开发过程中,或者应用开发完成,所执行结果不符合预期时,我 ...

  5. 数据库已经存在表, django使用inspectdb反向生成model实体类

    1.通过inspectdb处理类,可以将现有数据库里的一个或者多个.全部数据库表生成Django model实体类 python manage.py inspectdb --database defa ...

  6. python和shell 取日期为今天的行

    按条件取行 todolist.txt是存储所有数据的地方,每次查看数据库显得麻烦. 在执行命令后,要在终端显示今日应作事项. 首先用linux 的shell脚本来实现该功能. grep指令可以在文件中 ...

  7. docker内服务访问宿主机服务

    目录 1. 场景 2. 解决 4. 参考 1. 场景 使用windows, wsl2 进行日常开发测试工作. 但是wsl2经常会遇到网络问题.比如今天在测试一个项目,核心功能是将postgres 的数 ...

  8. 【Java虚拟机7】ClassLoader源码文档翻译

    前言 学习JVM类加载器,ClassLoader这个类加载器的核心类是必须要重视的. Notes:下方蓝色文字是自己的翻译(如果有问题请指正).黑色文字是源文档.红色文字是自己的备注. ClassLo ...

  9. Java:重载和重写

    Java:重载和重写 对 Java 中的 重载和重写 这个概念,做一个微不足道的小小小小结 重载 重载:编译时多态,同一个类中的同名的方法,参数列表不同,与返回值无关. 有以下几点: 方法名必须相同: ...

  10. 面试官问:说说你对Java函数式编程的理解

    常见的面试问题 总结一下,在Java程序员的面试中,经常会被问到类似这样的问题: Java中的函数式接口是什么意思? 注解 @FunctionalInterface 的作用是什么? 实现一个函数式接口 ...