概述
FireflySoft.RateLimit自2021年1月发布第一个版本以来,经历了多次升级迭代,目前已经十分稳定,被很多开发者应用到了生产系统中,最新发布的版本是3.0.0。
它的核心是一个基于 .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服务端。
为了方便使用,将这些算法的名字列在这里:
目前一个ASP.NET Core程序中只能使用一种算法,不知道是否有多种算法的需求,如有需要可以对FireflySoft.RateLimit.AspNetCore 进行一些改造:
- AddRateLimit时注册IAlgorithm改为注册IAlgorithm的解析器,解析器提供一个方法根据某个Key返回IAlgorithm的具体实现。
- RateLimitMiddleware中根据当前请求确定要使用的算法,然后调用解析器的方法获取IAlgorithm的具体实现。