.Net Core限流的實現示例

 更新時間:2021年07月05日 09:18:40   作者:以往清泉  
限流能夠很好的控制住一個客戶端訪問服務器資源地址的請求次數,本文主要介紹了.Net Core限流的實現示例,感興趣的可以了解一下

一、環境

1.vs2019

2..Net Core 3.1

3.引用 AspNetCoreRateLimit 4.0.1

二、基礎使用

1.設置

在Startup文件中配置如下,把配置項都放在前面:

 public void ConfigureServices(IServiceCollection services)
 {
  // 從appsettings.json中加載ip限流配置通用規則
  services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
  // 從appsettings.json中加載ip限流規則
  services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimiting:IpRateLimitPolicies"));
  // 從appsettings.json中加載客戶端限流配置通用規則
  services.Configure<ClientRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
  // 從appsettings.json中加載客戶端限流規則
  services.Configure<ClientRateLimitPolicies>(Configuration.GetSection("IpRateLimiting:ClientRateLimitPolicies"));
  // 注入計數器和規則存儲
  services.AddInMemoryRateLimiting();
  // 配置(解析器、計數器密鑰生成器)
  services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
  //解析clientid和ip的使用有用,如果默認沒有啟用,則此處啟用
  //services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  //調用ip限流方式和客戶端限流方式
  //只能選用一個,后一個調用的生效,也就是說ip規則限流和客戶端限流的特殊規則不能同時使用,但是通用規則不影響
  app.UseIpRateLimiting();
  app.UseClientRateLimiting();
}

2.規則設置

規則的設置分為兩個大類:通過IP限流和通過客戶端限流。都通過配置文件來配置參數,在appsettings.json中配置如下(也可以另起配置文件):

"IpRateLimiting": {
    "EnableEndpointRateLimiting": false,
    "StackBlockedRequests": false,
    "RealIpHeader": "X-Real-IP",
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,
    //"IpWhitelist": [ "198.0.0.1", "::1/10", "192.168.0.13/24" ],
    "EndpointWhitelist": [ "get:/api/license", "*:/api/status" ],
    "ClientWhitelist": [ "dev-id-1", "dev-id-2" ],
    "QuotaExceededResponse": {
      "Content": "{{\"code\":429,\"msg\":\"Visit too frequently, please try again later\",\"data\":null}}",
      "ContentType": "application/json;utf-8",
      "StatusCode": 429
    },
    "GeneralRules": [
      {
        "Endpoint": "*",
        "Period": "1s",
        "Limit": 2
      }
    ],
    "ClientRateLimitPolicies": {
      "ClientRules": [
        {
          "ClientId": "client-id-1",
          "Rules": [
            {
              "Endpoint": "*",
              "Period": "1s",
              "Limit": 10
            },
            {
              "Endpoint": "*",
              "Period": "15m",
              "Limit": 200
            }
          ]
        }
      ]
    },
    "IpRateLimitPolicies": {
      "IpRules": [
        {
          "Ip": "84.247.85.224",
          "Rules": [
            {
              "Endpoint": "*",
              "Period": "1s",
              "Limit": 10
            },
            {
              "Endpoint": "*",
              "Period": "15m",
              "Limit": 200
            }
          ]
        }
      ]
    }
  }

各配置項的說明如下:

 EnableEndpointRateLimiting:設置為true,則端點規則為 * 的時候所有的謂詞如GET、POST等分別享有限制次數。例如,如果您為*:/api/values客戶端設置每秒GET /api/values5 次調用的限制,則每秒可以調用5 次,但也可以調用5 次PUT /api/values。

如果設置為false,則上述例子中GET、POST等請求共享次數限制。是否共享限制次數的設置。這里有個注意的地方,就是當該參數設置為false的時候,只有端點設置為星號*的規則有效,其他規則無效,設置為true時所有規則有效。

StackBlockedRequests:設為false的情況下,被拒絕的請求不會加入到計數器中,如一秒內有三個請求,限流規則分別為一秒一次和一分鐘三次,則被拒絕的兩個請求是不會記錄在一分鐘三次的規則中的,也就是說這一分鐘還能調用兩次該接口。設置為true的話,則被拒絕的請求也會加入計數器,像上述例子中的情況,一分鐘內就不能調用了,三次全部記錄了。

RealIpHeader:與配置項IP白名單IpWhitelist組合使用,如果該參數定義的請求頭名稱存在于一個請求中,并且該參數內容為IP白名單中的IP,則不受限流規則限制。

ClientIdHeader:與配置項客戶端白名單ClientIdHeader組合使用,如果該參數定義的請求頭名稱存在于一個請求中,并且該參數內容為客戶端白名單中的名稱,則不受限流規則限制。

HttpStatusCode:http請求限流后的返回碼。

IpWhitelist:IP白名單,字段支持支持Ip v4和v6如 "198.0.0.1", "::1/10", "192.168.0.13/24"等?梢耘浜蟁ealIpHeader參數使用,也單獨使用,請求的ip符合該白名單規則任意一條,則不受限流規則限制。

EndpointWhitelist:終端白名單,符合該終端規則的請求都將不受限流規則影響,如"get:/api/values"表示GET請求的api/values接口不受影響,*表示所有類型的請求。

ClientWhitelist:客戶端白名單,配合ClientIdHeader參數使用,配置客戶端的名稱。

QuotaExceededResponse:限流后的返回值設置,返回內容、狀態碼等。

GeneralRules:通用規則設置,有三個參數為Endpoint、Period和Limit。

Endpoint端點格式為{HTTP_Verb}:{PATH},可以使用星號來定位任何 HTTP 動詞,如get:/api/values。

Period期間格式為{INT}{PERIOD_TYPE},可以使用以下期間類型之一:s、m、h、d,分別為秒分時天。

Limit限制格式為{LONG},訪問次數。

ClientRateLimitPolicies:客戶端限流的特殊配置,規則和通用規則一樣設置,只不過需要配合ClientIdHeader在請求頭中來使用,需要使用app.UseClientRateLimiting();啟用,否則無效。這個參數名稱是可以更改的噢。通用規則和特殊規則是同優先級的。

IpRateLimitPolicies:IP限流的特殊配置,規則和通用規則一樣設置,只不過需要配合RealIpHeader在請求頭中來使用,需要使用app.UseIpRateLimiting();啟用,否則無效。這個參數名稱是可以更改的噢。通用規則和特殊規則是同優先級的。

3.特殊規則的啟用

IP和客戶端特殊規則的啟用需要改造Program文件中的程序入口如下,分別發送各自的特殊規則:

public static async Task Main(string[] args)
{
  IWebHost webHost = CreateWebHostBuilder(args).Build();
  using (var scope = webHost.Services.CreateScope())
  {
    var clientPolicyStore = scope.ServiceProvider.GetRequiredService<IClientPolicyStore>();
    await clientPolicyStore.SeedAsync();

    var ipPolicyStore = scope.ServiceProvider.GetRequiredService<IIpPolicyStore>();
    await ipPolicyStore.SeedAsync();
  }
  await webHost.RunAsync();
}  

在ConfigureServices中讀取配置參數,之后是在Startup文件中的Configure方法選擇app.UseIpRateLimiting()或app.UseClientRateLimiting()啟動IP特殊規則或者客戶端特殊規則,都存在的情況下,先執行的先生效。

 三、請求返回頭

限流啟動后,執行限流規則的返回頭會有三個參數分別為:

X-Rate-Limit-Limit:現在時間,如1d。

X-Rate-Limit-Remaining:剩余可請求次數。

X-Rate-Limit-Reset:下次請求次數重置時間。

多個限制規則會采用最長的周期的規則顯示。

在配置文件中配置返回信息,除了返回提示信息外,還可以返回限制規則提醒,如下

"Content": "{{\"code\":429,\"msg\":\"訪問太頻繁了,每{1}{0}次,請在{2}秒后重試\",\"data\":null}}",

{0}可以替換當前阻止規則規定的次數,{1}可以替換時間區間帶單位s、h等,{2}替換幾秒后嘗試當單位為天或者小時等都會換算成秒。

四、使用Redis存儲

限流規則等目前都是通過內存存儲的,我們結合實際會使用redis存儲。使用Microsoft.Extensions.Caching.Redis可以達到這么目的。

但是好像會存在性能問題,所以我們自己替換,使用的是用CSRedis封裝的方法,不過這里不做闡述。

我們緩存三類數據1、訪問計數2、ip特殊規則3、客戶端特殊規則

1、訪問計數

public class RedisRateLimitCounterStore : IRateLimitCounterStore
    {
        private readonly ILogger _logger;
        private readonly IRateLimitCounterStore _memoryCacheStore;
        private readonly RedisCache _redisCache;

        public RedisRateLimitCounterStore(
            IMemoryCache memoryCache,
            ILogger<RedisRateLimitCounterStore> logger)
        {
            _logger = logger;
            _memoryCacheStore = new MemoryCacheRateLimitCounterStore(memoryCache);

            _redisCache = new RedisCache();
        }

        public async Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            return await TryRedisCommandAsync(
                () =>
                {
                    return _redisCache.KeyExistsAsync(id, 0);
                },
                () =>
                {
                    return _memoryCacheStore.ExistsAsync(id, cancellationToken);
                });
        }

        public async Task<RateLimitCounter?> GetAsync(string id, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            return await TryRedisCommandAsync(
                async () =>
                {
                    var value = await _redisCache.GetStringAsync(id, 0);

                    if (!string.IsNullOrEmpty(value))
                    {
                        return JsonConvert.DeserializeObject<RateLimitCounter?>(value);
                    }

                    return null;
                },
                () =>
                {
                    return _memoryCacheStore.GetAsync(id, cancellationToken);
                });
        }

        public async Task RemoveAsync(string id, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            _ = await TryRedisCommandAsync(
                async () =>
                {
                    await _redisCache.KeyDeleteAsync(id, 0);

                    return true;
                },
                async () =>
                {
                    await _memoryCacheStore.RemoveAsync(id, cancellationToken);

                    return true;
                });
        }

        public async Task SetAsync(string id, RateLimitCounter? entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            _ = await TryRedisCommandAsync(
                async () =>
                {
                    var exprie = expirationTime.HasValue ? Convert.ToInt32(expirationTime.Value.TotalSeconds) : -1;
                    await _redisCache.SetStringAsync(id, JsonConvert.SerializeObject(entry.Value), exprie);

                    return true;
                },
                async () =>
                {
                    await _memoryCacheStore.SetAsync(id, entry, expirationTime, cancellationToken);

                    return true;
                });
        }

        private async Task<T> TryRedisCommandAsync<T>(Func<Task<T>> command, Func<Task<T>> fallbackCommand)
        {
            if (_redisCache != null)
            {
                try
                {
                    return await command();
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Redis command failed: {ex}");
                }
            }

            return await fallbackCommand();
        }
    }

 2、ip特殊規則

public class RedisIpPolicyStore : IIpPolicyStore
    {
        private readonly IpRateLimitOptions _options;
        private readonly IpRateLimitPolicies _policies;
        private readonly RedisCache _redisCache;
        public RedisIpPolicyStore(
            IOptions<IpRateLimitOptions> options = null,
            IOptions<IpRateLimitPolicies> policies = null)
        {
            _options = options?.Value;
            _policies = policies?.Value;
            _redisCache = new RedisCache();
        }

        public async Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default)
        {
            return await _redisCache.KeyExistsAsync($"{_options.IpPolicyPrefix}", 0);
        }

        public async Task<IpRateLimitPolicies> GetAsync(string id, CancellationToken cancellationToken = default)
        {
            string stored = await _redisCache.GetStringAsync($"{_options.IpPolicyPrefix}", 0);
            if (!string.IsNullOrEmpty(stored))
            {
                return JsonConvert.DeserializeObject<IpRateLimitPolicies>(stored);
            }

            return default;
        }

        public async Task RemoveAsync(string id, CancellationToken cancellationToken = default)
        {
            await _redisCache.DelStringAsync($"{_options.IpPolicyPrefix}", 0);
        }

        public async Task SeedAsync()
        {
            // on startup, save the IP rules defined in appsettings
            if (_options != null && _policies != null)
            {
                await _redisCache.SetStringAsync($"{_options.IpPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0).ConfigureAwait(false);
            }
        }

        public async Task SetAsync(string id, IpRateLimitPolicies entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
        {
            var exprie = expirationTime.HasValue ? Convert.ToInt32(expirationTime.Value.TotalSeconds) : -1;
            await _redisCache.SetStringAsync($"{_options.IpPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0, exprie);
        }
    }

3、客戶端特殊規則

public class RedisClientPolicyStore : IClientPolicyStore
    {
        private readonly ClientRateLimitOptions _options;
        private readonly ClientRateLimitPolicies _policies;
        private readonly RedisCache _redisCache;
        public RedisClientPolicyStore(
            IOptions<ClientRateLimitOptions> options = null,
            IOptions<ClientRateLimitPolicies> policies = null)
        {
            _options = options?.Value;
            _policies = policies?.Value;
            _redisCache = new RedisCache();
        }

        public async Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default)
        {
            return await _redisCache.KeyExistsAsync($"{_options.ClientPolicyPrefix}", 0);
        }

        public async Task<ClientRateLimitPolicy> GetAsync(string id, CancellationToken cancellationToken = default)
        {
            string stored = await _redisCache.GetStringAsync($"{_options.ClientPolicyPrefix}", 0);
            if (!string.IsNullOrEmpty(stored))
            {
                return JsonConvert.DeserializeObject<ClientRateLimitPolicy>(stored);
            }

            return default;
        }

        public async Task RemoveAsync(string id, CancellationToken cancellationToken = default)
        {
            await _redisCache.DelStringAsync($"{_options.ClientPolicyPrefix}", 0);
        }

        public async Task SeedAsync()
        {
            // on startup, save the IP rules defined in appsettings
            if (_options != null && _policies != null)
            {
                await _redisCache.SetStringAsync($"{_options.ClientPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0).ConfigureAwait(false);
            }
        }

        public async Task SetAsync(string id, ClientRateLimitPolicy entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
        {
            var exprie = expirationTime.HasValue ? Convert.ToInt32(expirationTime.Value.TotalSeconds) : -1;
            await _redisCache.SetStringAsync($"{_options.ClientPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0, exprie);
        }
    }

之后在Startup文件中增加對應的注入

services.AddSingleton<IRateLimitCounterStore, RedisRateLimitCounterStore>();
services.AddSingleton<IIpPolicyStore, RedisIpPolicyStore>();
services.AddSingleton<IClientPolicyStore, RedisClientPolicyStore>();


之后運行就可以在redis中看到啦

 五、修改規則

規則只能修改IP和客戶端的特殊規則,因為上一部分已經注入了改規則的對應redis增刪查改的功能,所以我們可以利用這些方法重寫規則,如下:

public class ClientRateLimitController : Controller
{
    private readonly ClientRateLimitOptions _options;
    private readonly IClientPolicyStore _clientPolicyStore;

    public ClientRateLimitController(IOptions<ClientRateLimitOptions> optionsAccessor, IClientPolicyStore clientPolicyStore)
    {
        _options = optionsAccessor.Value;
        _clientPolicyStore = clientPolicyStore;
    }

    [HttpGet]
    public ClientRateLimitPolicy Get()
    {
        return _clientPolicyStore.Get($"{_options.ClientPolicyPrefix}_cl-key-1");
    }

    [HttpPost]
    public void Post()
    {
        var id = $"{_options.ClientPolicyPrefix}_cl-key-1";
        var clPolicy = _clientPolicyStore.Get(id);
        clPolicy.Rules.Add(new RateLimitRule
        {
            Endpoint = "*/api/testpolicyupdate",
            Period = "1h",
            Limit = 100
        });
        _clientPolicyStore.Set(id, clPolicy);
    }
}

到此這篇關于.Net Core限流的實現示例的文章就介紹到這了,更多相關.Net Core限流內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論

精品国内自产拍在线观看