前言
同事问我Asp.netCore的RateLimiting是怎么使用的,我回答说很简单的,你只要按照如下步骤来:
- 在RateLimiterOptions上注册policy,记住policy对应的policyName
- 在Web应用添加UseRateLimiter()中间件
- 在api对应的Action上标注[EnableRateLimiting(policyName)]
半小时后,同事说要对我告知他的RateLimiter功能差评,因为他得写几十种policy工作量很大,就连policyName的取名都让他头大。
让RateLimiting更简单
我在基于Yarp的网关上做过RateLimiting,将每个服务的所有Endpoint描述导出放到网关做网关的子级路由,然后基于Endpoint做可配置化的限流,代码实现上很复杂但使用灵活度非常可观。
但今天我们的问题范围是在单体WebApplication中,如何减少我同事的限流工作量。
限流器Attribute化
假如我们造了如下的Attribute:
- [RateLimiter.Concurrency(permitLimit: 10)]
- [RateLimiter.FixedWindow(permitLimit: 10, windowSeconds: 60)]
- [RateLimiter.SlidingWindow(permitLimit: 10, windowSeconds: 60, segmentsPerWindow: 5)]
让我的同事在Action上标记其中一个Attribute,他的开发时间短、代码可读性高,免去了他手写policy实现的巨量代码。
限流单元来源Attribute化
同事说他的众多接口中,需要整体限流的很少,几乎都是需要颗粒度更细的限流:
- 登录接口,需要对请求体的json对象的userName值做限流单元
- 找回密码接口,需要对请求路由里的userName值做限流单元
- 其它已经做了用户身份认证的接口,需要用userId值做限流单元
- xx特殊接口,需要用请求者IP值做限流单元
- yy接口需要从Form中取出yy值做限流单元
- zz接口需要从Header中取出zz值做限流单元
- 想自定义从HttpContext里获取限流单元
于是我们又造了一些Attribute,来处理上面的需求
- [RateLimiterUnit.FromBody(unitName: "$.userName")]
- [RateLimiterUnit.FromRoute(unitName: "userName")]
- [RateLimiterUnit.FromUser(unitName: ClaimTypes.NameIdentifier)]
- [RateLimiterUnit.FromRemoteIPAddress]
- [RateLimiterUnit.FromForm(unitName: "yy")]
- [RateLimiterUnit.FromHeader(unitName: "zz")]
再提供一个IRateLimiterUnitMetadata接口,让他实现自定义逻辑
public class YourRateLimiterUnitAttribute : Attribute, IRateLimiterUnitMetadata { public ValueTask<string?> GetUnitAsync(HttpContext context) { throw new NotImplementedException(); } }
让RateLimiting能运行
上面提到的那些Attribute,目前只存在于我们理想中,我们需要把它变成实际。
我们需要实现实现一个policy,在policy里获取当前请求的Endpoint,从Endpoint的metadata里提取出我们上面定义的Attribute,根据Attribute的描述生成RateLimitPartition。
把这个唯一的policy注册到RatelimiterOptions中,再把它的policyName使用EnableRateLimitingAttribute添加到Endpoint的metadata中。
最后,使用标准的Asp.netCore的UseRateLimiter()中间件,就能把我们的限流器运行起来了。
最后
此项目是开源的,不管你在工作中有没有用到,但里面有意思的想法你可以来一起品。