前言

同事问我Asp.netCore的RateLimiting是怎么使用的,我回答说很简单的,你只要按照如下步骤来:

  1. 在RateLimiterOptions上注册policy,记住policy对应的policyName
  2. 在Web应用添加UseRateLimiter()中间件
  3. 在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化

同事说他的众多接口中,需要整体限流的很少,几乎都是需要颗粒度更细的限流:

  1. 登录接口,需要对请求体的json对象的userName值做限流单元
  2. 找回密码接口,需要对请求路由里的userName值做限流单元
  3. 其它已经做了用户身份认证的接口,需要用userId值做限流单元
  4. xx特殊接口,需要用请求者IP值做限流单元
  5. yy接口需要从Form中取出yy值做限流单元
  6. zz接口需要从Header中取出zz值做限流单元
  7. 想自定义从HttpContext里获取限流单元

于是我们又造了一些Attribute,来处理上面的需求

  1. [RateLimiterUnit.FromBody(unitName: "$.userName")]
  2. [RateLimiterUnit.FromRoute(unitName: "userName")]
  3. [RateLimiterUnit.FromUser(unitName: ClaimTypes.NameIdentifier)]
  4. [RateLimiterUnit.FromRemoteIPAddress]
  5. [RateLimiterUnit.FromForm(unitName: "yy")]
  6. [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()中间件,就能把我们的限流器运行起来了。

最后

此项目是开源
的,不管你在工作中有没有用到,但里面有意思的想法你可以来一起品。

标签: none

添加新评论