NetCoreWeb如何实现API限流

1,463 阅读5分钟

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

引言

限流是对外Api服务在使用过程上经常会碰到的需求。

对客户端的访问频率进行限制可以有效防止因为客户端使用脚本或其他破坏性的方式对服务正常运行造成影响的风险。

限流有多种解决方式,最简单的方式莫过于针对Ip进行限制:只允许某一个Ip在规定的时间内访问多次,ip访问记录可以保存在内存或者其他高速数据存储服务中。

当然我们在开发过种中肯定不会什么轮子都自己造,既浪费时间,出错概率大,考虑不全面。所以选择一个合适的轮子是非常重要的,今天在这里向大家推荐一个ASP.NET Core速率限制的解决方案**AspNetCoreRateLimit **

AspNetCoreRateLimit 介绍

AspNetCoreRateLimit是一个ASP.NET Core速率限制的解决方案,旨在控制客户端根据IP地址或客户端ID向Web API或MVC应用发出的请求的速率。AspNetCoreRateLimit包含一个IpRateLimitMiddlewareClientRateLimitMiddleware,每个中间件可以根据不同的场景配置限制允许IP或客户端,自定义这些限制策略,也可以将限制策略应用在每个API URL或具体的HTTP Method上。

Nuget包

Nuget包引用,截止文章使用时 AspNetCoreRateLimit版本号3.2.2。

Install-Package AspNetCoreRateLimit 

配置说明

通用Ip配置规则

IpRateLimiting

名称类型说明
EnableEndpointRateLimitingboolfalse 则全局将应用限制,并且仅应用具有作为端点的规则*。例如,如果您设置每秒5次调用的限制,则对任何端点的任何HTTP调用都将计入该限制
true 则限制将应用于每个端点,如{HTTP_Verb}{PATH}。例如,如果您为 *:/api/values客户端设置每秒5个呼叫的限制
StackBlockedRequestsbooltrue 如果希望被拒绝的API调用计入其他时间的显示(分钟,小时等)
false 拒绝的API调用不会添加到调用次数计数器上;如客户端每秒发出3个请求并且您设置了每秒一个调用的限制,则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用
RealIpHeaderstring服务器背后是一个反向代理,如果你的代理服务器使用不同的页眉然后提取客户端IP X-Real-IP使用此选项来设置
ClientIdHeaderstring取白名单的客户端ID。如果此标头中存在客户端ID并且与ClientWhitelist中指定的值匹配,则不应用速率限制。
HttpStatusCodestring限制状态码
IpWhiteliststringIP白名单:支持Ip v4和v6
EndpointWhiteliststring端点白名单
ClientWhiteliststring白客户端白名单
GeneralRulesjson通用规则
QuotaExceededResponsejson自定义返回的内容
"QuotaExceededResponse": {
"Content": "{{"code":429,"msg":"访问过于频繁,请稍后重试","data":null}}",
"ContentType": "application/json",
"StatusCode": 200
}

参考内容,下面的配置文件代表的含义是:IP限制适应于所有全局,规则为每5秒访问3次

{
  "IpRateLimiting": {
    "EnableEndpointRateLimiting": false,
    "StackBlockedRequests": false,
    "RealIpHeader": "X-Real-IP",
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,
    ////IP白名单:支持Ip v4和v6 
    "IpWhitelist": [ "127.0.0.1" ],
    "GeneralRules": [
      {
        "Endpoint": "*",
        "Period": "5s",
        "Limit": 3
      }
    ]
  }
}

GeneralRules

名称类型说明
Endpointstring端点路径
Periodstring时间段,格式:{数字}{单位};可使用单位:s, m, h, d
Limitstring调用限制

EndPoint

端点格式:{HTTP_Verb}:{PATH},您可以使用asterix符号来定位任何HTTP谓词。

Period

期间格式:{INT}{PERIOD_TYPE},您可以使用以下期间类型之一:s, m, h, d

Limit

限制格式:{LONG}

配置规则参考

"GeneralRules": [
  //1秒钟只能访问2次
  {
    "Endpoint": "*",
    "Period": "1s",
    //限制
    "Limit": 2
  },
  //15分钟只能调用100次
  {
    "Endpoint": "*",
    "Period": "15m",
    "Limit": 100
  },
  //12H只能调用1000
  {
    "Endpoint": "*",
    "Period": "12h",
    "Limit": 1000
  },
  //7天只能调用10000次
  {
    "Endpoint": "*",
    "Period": "7d",
    "Limit": 10000
  },
  //对api/rateLimit/GetDateTime接口的规则为5秒钟最多访问3次
  {
    "Endpoint": "*:/api/rateLimit/GetDateTime",
    "Period": "5s",
    "Limit": 3
  }
]

特殊Ip限制规则

"IpRateLimitPolicies": {
  //ip规则
  "IpRules": [
    {
      //IP
      "Ip": "84.247.85.224",
      //规则内容
      "Rules": [
        //1s请求10次
        {"Endpoint": "*","Period": "1s","Limit": 10},
        //15分钟请求200次
        {"Endpoint": "*","Period": "15m","Limit": 200}
      ]
    },
    {
      //ip支持设置多个
      "Ip": "192.168.3.22/25",
      "Rules": [
        //1秒请求5次
        {"Endpoint": "*","Period": "1s","Limit": 5},
        //15分钟请求150次
        {"Endpoint": "*","Period": "15m","Limit": 150},
        //12小时请求500次
        {"Endpoint": "*","Period": "12h","Limit": 500}
      ]
    }
  ]
}

相信看完通用Ip配置规则的小伙伴看这一段配置应该没有什么压力了,我在此也就不多做解释了。

使用特殊Ip限制规则需要在ConfigureService中启用这一段代码

//加载ip限制定制策略,即针对某一个ip的特殊限制,可选择性注释
//services.Configure<IpRateLimitPolicies>(configuration.GetSection("IpRateLimitPolicies"));

编码使用

了解了基本规则后,我们开始进行编码工作

1、注入服务

因为涉及的注入内容比较多,我们使用一个扩展方法标识Ip限制服务需要注入的内容。

public static class RateLimitedConfigureService
{
    public static void RateLimitedConfig(this IServiceCollection services, IConfiguration configuration)
    {
        //Option模式配置
        services.AddOptions();
        //存储速率限制计算器和ip规则
        services.AddMemoryCache();
        //加载ip限制通用策略
        services.Configure<IpRateLimitOptions>(configuration.GetSection("IpRateLimiting"));
        //加载ip限制定制策略,即针对某一个ip的特殊限制,可选择性注释
        //services.Configure<IpRateLimitPolicies>(configuration.GetSection("IpRateLimitPolicies"));

        //注入计数器和规则存储
        services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
        services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();

        //httpContext使用需要注入的服务
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        //配置(解析器、计数器密钥生成器)
        services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
    }
}

1.1 配置服务

public void ConfigureServices(IServiceCollection services)
{
    //Ip限制配置
    services.RateLimitedConfig(_configuration);

    services.AddControllers();
}

1.2 启用管道

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
    app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    //管道事件启用客户端IP限制速率
    app.UseIpRateLimiting();
}

2、编写Api

/// <summary>
/// 流量限制使用说明
/// </summary>
[Route("api/rateLimit")]
[ApiController]
public class RateLimitController: Controller
{
    [HttpGet("GetDateTime")]
    public string GetDateTime()
    {
        return DateTime.Now.ToString("d");
    }
}

3、测试

正常请求

调用 http://localhost:5000/api/ratelimit/getdatetime请求接口,返回当前日期。

通过F12查看ResponseHeaders我们可以发现多了三个参数,分别代表的含义为

X-Rate-Limit-Limit 限制时间区间

X-Rate-Limit-Remaining 剩余请求次数

X-Rate-Limit-Reset 请求重置时间

image-20210512155552048

每调用一次X-Rate-Limit-Remaining减去1,当没有请求次数可以调用时返回错误。

API calls quota exceeded! maximum admitted 3 per 5s.

错误请求

当请求数在时间界限外时页面返回错误信息。返回内容也可以自定义,请参考配置规则

参考

Asp.NET Core 限流控制-AspNetCoreRateLimit

.Net Core结合AspNetCoreRateLimit实现限流