宝塔山 发表于 2022-8-22 21:17:07

.NET服务治理之限流中间件-FireflySoft.RateLimit

概述

FireflySoft.RateLimit自2021年1月发布第一个版本以来,经历了多次升级迭代,目前已经十分稳定,被很多开发者应用到了生产系统中,最新发布的版本是3.0.0。

[*]Github:https://github.com/bosima/FireflySoft.RateLimit
[*]码云:https://gitee.com/bosima/FireflySoft.RateLimit
它的核心是一个基于 .NET Standard 的限流类库,其内核简单轻巧,能够灵活应对各种需求的限流场景。其主要特点包括:

[*]多种限流算法:内置固定窗口、滑动窗口、漏桶、令牌桶四种算法,方便自定义扩展。
[*]多种计数存储:目前支持内存、Redis(含集群)两种存储方式。
[*]分布式友好:通过Redis存储支持分布式程序统一计数。
[*]限流目标灵活:可以从请求中提取各种数据用于设置限流目标,不仅仅是客户端IP和Id。
[*]支持限流惩罚:可以在客户端触发限流后锁定一段时间不允许其访问。
[*]时间窗口增强:支持到毫秒级别;支持从秒、分钟、小时、日期等时间周期的自然起始点开始。
[*]实时限流跟踪:当前计数周期内已处理的请求数、剩余允许请求数,以及计数周期重置的时间。
[*]动态更改规则:支持程序运行时动态更改限流规则。
[*]自定义错误:可以自定义触发限流后的错误码和错误消息。
[*]普适性:原则上可以满足任何需要限流的场景,可用于各种B/S、C/S程序。
基于这个核心又实现了两个中间件:

[*]FireflySoft.RateLimit.AspNet:应用于传统的.NET Framework的Web应用程序。
[*]FireflySoft.RateLimit.AspNetCore:应用于ASP.NET Core的Web应用程序。
相比使用FireflySoft.RateLimit核心类库,直接使用这两个中间件比较方便一些。如果这两个中间件不能满足你的需求,比如不是应用在官方的Web框架中,甚至不是Web程序,问题不大,可以基于核心类库满足你的限流需求,你要做的只是定义好你要限流的请求,并在触发限流时执行自己的业务逻辑,限流的算法如何实现都不需要关心。
这些类库和中间件都是可以通过Nuget安装的,搜索 FireflySoft.RateLimit 即可找到。
使用示例

这篇文章以一个ASP.NET Core程序为例,说明FireflySoft.RateLimit的使用方法。
程序的业务需求是:对获取天气预报的接口,根据客户端IP和ClientId进行限流,每个IP每秒钟1次,每个ClientId每秒钟3次。ClientId是预先分配给调用方的。根据规则,调用方如果只有1个出口IP,那么每秒钟只能访问1次,如果有多个出口IP,那么每秒钟最多访问3次。
这个示例程序是基于.NET6开发的,当然你用.NET Core 3.1也没有问题,只是.NET6默认把服务和中间件注册都放到了Program.cs 中。(建议升级到.NET6,.NET6相比.NET Core 3.1的性能有明显的提升。)
来看代码吧,只需要注册服务和Middleware就可以了。
using FireflySoft.RateLimit.AspNetCore;
using FireflySoft.RateLimit.Core.InProcessAlgorithm;
using FireflySoft.RateLimit.Core.Rule;

var builder = WebApplication.CreateBuilder(args);

...

builder.Services.AddRateLimit(new InProcessFixedWindowAlgorithm(
    new[] {
      new FixedWindowRule()
      {
            ExtractTarget = context =>
            {
                var httpContext= context as HttpContext;

                // Through CDN
                var ip = httpContext!.Request.Headers["Cdn-Src-Ip"].FirstOrDefault();
                if (!string.IsNullOrEmpty(ip))
                  return ip;

                // Through SLB
                ip = httpContext!.Request.Headers["X-Forwarded-For"].FirstOrDefault();
                if (!string.IsNullOrEmpty(ip))
                  return ip;

                ip = httpContext!.Connection.RemoteIpAddress?.ToString();
                return ip??"Anonymous-IP";
            },
            CheckRuleMatching = context =>
            {
                var requestPath = (context as HttpContext)!.Request.Path.Value;
                if (requestPath == "/WeatherForecast/Future")
                {
                  return true;
                }
                return false;
            },
            Name = "ClientIPRule",
            LimitNumber = 3,
            StatWindow = TimeSpan.FromSeconds(1)
      },
      new FixedWindowRule()
      {
            ExtractTarget = context =>
            {
                var httpContext= context as HttpContext;
                var clientID = httpContext!.Request.Headers["X-ClientId"].FirstOrDefault();

                return clientID??"Anonymous-ClientId";
            },
            CheckRuleMatching = context =>
            {
                var requestPath = (context as HttpContext)!.Request.Path.Value;
                if (requestPath == "/WeatherForecast/Future")
                {
                  return true;
                }
                return false;
            },
            Name = "ClientIdRule",
            LimitNumber = 1,
            StatWindow = TimeSpan.FromSeconds(1)
      }
    })
);

...

app.UseRateLimit();

...粘贴的代码中只保留了此中间件需要的内容,注册服务使用 AddRateLimit,使用中间件通过 UseRateLimit。
算法

AddRateLimit 时需要指定一个限流算法,示例中是基于本地内存的固定窗口算法,可以根据需要更换为其它算法,比如可以应对短时突发流量的令牌桶算法。
对于某种具体的算法,基于本地内存和基于Redis的实现是不同的类,因为为了更好的性能,Redis实现的算法是通过Lua脚本写的,它完全运行在Redis服务端。
为了方便使用,将这些算法的名字列在这里:
基于本地内存(进程内)基于Redis固定窗口算法InProcessFixedWindowAlgorithmRedisFixedWindowAlgorithm滑动窗口算法InProcessSlidingWindowAlgorithmRedisSlidingWindowAlgorithm漏桶算法InProcessFixedWindowAlgorithmRedisFixedWindowAlgorithm令牌桶算法InProcessTokenBucketAlgorithmRedisokenBucketAlgorithm目前一个ASP.NET Core程序中只能使用一种算法,不知道是否有多种算法的需求,如有需要可以对FireflySoft.RateLimit.AspNetCore 进行一些改造:

[*]AddRateLimit时注册IAlgorithm改为注册IAlgorithm的解析器,解析器提供一个方法根据某个Key返回IAlgorithm的具体实现。
[*]RateLimitMiddleware中根据当前请求确定要使用的算法,然后调用解析器的方法获取IAlgorithm的具体实现。
规则

创建算法实例的时候,还需要指定算法的规则,这里根据算法使用的是 FixedWindowRule,对于同一个算法,进程内实现和Redis实现使用相同的规则。
看一下这里使用的规则的几个属性:
ExtractTarget 设置一个函数,用于从HTTP请求中提取要限流的目标,比如这里的客户端IP和客户端ID,还可以是各种可以从请求中提取或关联到的东西,比如Http Header中携带的用户Id,或者根据用户Id查询到的用户年龄。
CheckRuleMatching 设置一个函数,返回当前请求是否能匹配到某个限流规则,如果能匹配到,则返回true。比如只对 /api/req 这个路径限流,那么只要判断请求的路径是它,就返回true,其它路径都返回false。当然也可以是根据各种可以从请求中提取或关联到的东西来进行判断。
Name 限流规则的名字,方便人跟踪的时候进行区分。
StatWindow 限流的时间窗口。比如需求中的每秒钟3次,这里的时间窗口就要设置为1秒。
LimitNumber 限流的次数阈值。比如需求中的每秒钟3次,这里的时间窗口就要设置为3,超过3就会被限流。
规则中还有其它几个属性,不同算法的规则也略有不同,这里就不一一列举了。感兴趣的朋友可以去示例代码和单元测试中认识它们。
一个算法中可以添加多个对应算法的规则,这无疑会比较灵活。
更多使用说明


[*]ASP.NET Core中使用固定窗口限流
[*]ASP.NET Core中使用滑动窗口限流
[*]ASP.NET Core中使用漏桶算法限流
[*]ASP.NET Core中使用令牌桶限流
[*]ASP.NET Core中如何对不同类型的用户进行区别限流
[*]多租户系统中如何实现分别限流
[*].NET6运行时动态更新限流阈值
[*]如何使用数组实现滑动窗口
[*]限流的非常规用途 – 缓解抢购压力
[*]限流的非正式用途 – 解决重复提交问题
[*]服务限流惩罚是怎么一回事
以上就是本文的主要内容了,如有问题欢迎留言交流。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: .NET服务治理之限流中间件-FireflySoft.RateLimit