Asp.NET Core 限流控制-AspNetCoreRateLimit

chaney1992發表於2021-02-28

起因:

 近期專案中,提供了一些呼叫頻率較高的api介面,需要保障伺服器的穩定執行;需要對提供的介面進行限流控制。避免因客戶端頻繁的請求導致伺服器的壓力。

一、AspNetCoreRateLimit 介紹

 AspNetCoreRateLimit是一個ASP.NET Core速率限制的解決方案,旨在控制客戶端根據IP地址或客戶端ID向Web API或MVC應用發出的請求的速率。AspNetCoreRateLimit包含一個IpRateLimitMiddlewareClientRateLimitMiddleware,每個中介軟體可以根據不同的場景配置限制允許IP或客戶端,自定義這些限制策略,也可以將限制策略應用在每​​個API URL或具體的HTTP Method上。

二、AspNetCoreRateLimit使用

 由上面介紹可知AspNetCoreRateLimit支援了兩種方式:基於客戶端IP(IpRateLimitMiddleware)和客戶端ID(ClientRateLimitMiddleware)速率限制 接下來就分別說明使用方式

 新增Nuget包引用:

 Install-Package AspNetCoreRateLimit 
  • 基於客戶端IP速率限制

  1、修改Startup.cs中方法:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }// This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        //需要從載入配置檔案appsettings.json
        services.AddOptions();
        //需要儲存速率限制計算器和ip規則
        services.AddMemoryCache();

        //從appsettings.json中載入常規配置,IpRateLimiting與配置檔案中節點對應
        services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));

        //從appsettings.json中載入Ip規則
        services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));

        //注入計數器和規則儲存
        services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
        services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();

        services.AddControllers();

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        //配置(解析器、計數器金鑰生成器)
        services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

        //Other Code
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        //Other Code

        app.UseRouting();

        app.UseAuthorization();
     //啟用客戶端IP限制速率
        app.UseIpRateLimiting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

  2、在appsettings.json中新增通用配置項節點:(IpRateLimiting節點與Startup中取的節點對應)

"IpRateLimiting": {
  //false,則全域性將應用限制,並且僅應用具有作為端點的規則*。例如,如果您設定每秒5次呼叫的限制,則對任何端點的任何HTTP呼叫都將計入該限制
  //true, 則限制將應用於每個端點,如{HTTP_Verb}{PATH}。例如,如果您為*:/api/values客戶端設定每秒5個呼叫的限制,
  "EnableEndpointRateLimiting": false,
  //false,拒絕的API呼叫不會新增到呼叫次數計數器上;如 客戶端每秒發出3個請求並且您設定了每秒一個呼叫的限制,則每分鐘或每天計數器等其他限制將僅記錄第一個呼叫,即成功的API呼叫。如果您希望被拒絕的API呼叫計入其他時間的顯示(分鐘,小時等)
//,則必須設定StackBlockedRequests為true。
"StackBlockedRequests": false, //Kestrel 伺服器背後是一個反向代理,如果你的代理伺服器使用不同的頁首然後提取客戶端IP X-Real-IP使用此選項來設定 "RealIpHeader": "X-Real-IP", //取白名單的客戶端ID。如果此標頭中存在客戶端ID並且與ClientWhitelist中指定的值匹配,則不應用速率限制。 "ClientIdHeader": "X-ClientId", //限制狀態碼 "HttpStatusCode": 429, ////IP白名單:支援Ip v4和v6 //"IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ], ////端點白名單 //"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ], ////客戶端白名單 //"ClientWhitelist": [ "dev-id-1", "dev-id-2" ], //通用規則 "GeneralRules": [ { //端點路徑 "Endpoint": "*", //時間段,格式:{數字}{單位};可使用單位:s, m, h, d "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} ] }

   配置節點已新增相應註釋資訊。

   規則設定格式:   

端點格式:{HTTP_Verb}:{PATH},您可以使用asterix符號來定位任何HTTP謂詞。

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

限制格式:{LONG}

  3、特點Ip限制規則設定,在appsettings.json中新增 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}
      ]
    }
  ]
}
  • 基於客戶端ID速率限制

  1、修改Startup檔案:

public void ConfigureServices(IServiceCollection services)
{
    //需要從載入配置檔案appsettings.json
    services.AddOptions();

    //需要儲存速率限制計算器和ip規則
    services.AddMemoryCache();

    //從appsettings.json中載入常規配置
    services.Configure<ClientRateLimitOptions>(Configuration.GetSection("IPRateLimiting"));

    //從appsettings.json中載入客戶端規則
    services.Configure<ClientRateLimitPolicies>(Configuration.GetSection("ClientRateLimitPolicies"));

    //注入計數器和規則儲存
    services.AddSingleton<IClientPolicyStore, MemoryCacheClientPolicyStore>();
    services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();

    
    services.AddControllers();

        // https://github.com/aspnet/Hosting/issues/793
        // the IHttpContextAccessor service is not registered by default.
        //注入計數器和規則儲存
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        //配置(解析器、計數器金鑰生成器)
        services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
        //啟用客戶端限制
    app.UseClientRateLimiting();

    app.UseMvc();
}

  2、通用配置採用IP限制相同配置,新增客戶端限制配置:

//客戶端限制設定
"ClientRateLimitPolicies": {
  "ClientRules": [
    {
      //客戶端id
      "ClientId": "client-id-1",
      "Rules": [
        {"Endpoint": "*","Period": "1s","Limit": 10},
        {"Endpoint": "*","Period": "15m","Limit": 200}
      ]
    },
    {
      "ClientId": "client-id-2",
      "Rules": [
        {"Endpoint": "*","Period": "1s","Limit": 5},
        {"Endpoint": "*","Period": "15m","Limit": 150},
        {"Endpoint": "*","Period": "12h","Limit": 500}
      ]
    }
  ]
}

  3、呼叫結果:

    設定規則:1s只能呼叫一次:首次呼叫

    

     呼叫第二次:自定義返回內容

     

三、其他 

  • 執行時更新速率限制

   新增IpRateLimitController控制器:   

/// <summary>
/// IP限制控制器
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class IpRateLimitController : ControllerBase
{

    private readonly IpRateLimitOptions _options;
    private readonly IIpPolicyStore _ipPolicyStore;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="optionsAccessor"></param>
    /// <param name="ipPolicyStore"></param>
    public IpRateLimitController(IOptions<IpRateLimitOptions> optionsAccessor, IIpPolicyStore ipPolicyStore)
    {
        _options = optionsAccessor.Value;
        _ipPolicyStore = ipPolicyStore;
    }

    /// <summary>
    /// 獲取限制規則
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public async Task<IpRateLimitPolicies> Get()
    {
        return await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix);
    }

    /// <summary>
    /// 
    /// </summary>
    [HttpPost]
    public async Task Post(IpRateLimitPolicy ipRate)
    {
        var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix);
        if (ipRate != null)
        {
            pol.IpRules.Add(ipRate);
            await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol);
        }
    }
}
  • 分散式部署時,需要將速率限制計算器和ip規則儲存到分散式快取中如Redis
    • 修改注入物件
// inject counter and rules distributed cache stores
services.AddSingleton<IClientPolicyStore, DistributedCacheClientPolicyStore>();
services.AddSingleton<IRateLimitCounterStore,DistributedCacheRateLimitCounterStore>();
services.AddStackExchangeRedisCache(options =>
{
    options.ConfigurationOptions = new ConfigurationOptions
    {
        //silently retry in the background if the Redis connection is temporarily down
        AbortOnConnectFail = false
    };
    options.Configuration = "localhost:6379";
    options.InstanceName = "AspNetRateLimit";
});
  • 限制時自定義相應結果:
    //請求返回
        "QuotaExceededResponse": {
          "Content": "{{\"code\":429,\"msg\":\"Visit too frequently, please try again later\",\"data\":null}}",
          "ContentType": "application/json;utf-8",
          "StatusCode": 429
        },

     呼叫時返回結果:

其他:

  示例程式碼:https://github.com/cwsheng/WebAPIVersionDemo

 

相關文章