溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

.Net Core如何實(shí)現(xiàn)限流

發(fā)布時(shí)間:2021-07-05 14:32:45 來(lái)源:億速云 閱讀:216 作者:小新 欄目:開(kāi)發(fā)技術(shù)

小編給大家分享一下.Net Core如何實(shí)現(xiàn)限流,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

    一、環(huán)境

    1.vs2019

    2..Net Core 3.1

    3.引用 AspNetCoreRateLimit 4.0.1

    二、基礎(chǔ)使用

    1.設(shè)置

    在Startup文件中配置如下,把配置項(xiàng)都放在前面:

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

    2.規(guī)則設(shè)置

    規(guī)則的設(shè)置分為兩個(gè)大類(lèi):通過(guò)IP限流和通過(guò)客戶端限流。都通過(guò)配置文件來(lái)配置參數(shù),在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
                }
              ]
            }
          ]
        }
      }

    各配置項(xiàng)的說(shuō)明如下:

     EnableEndpointRateLimiting:設(shè)置為true,則端點(diǎn)規(guī)則為 * 的時(shí)候所有的謂詞如GET、POST等分別享有限制次數(shù)。例如,如果您為*:/api/values客戶端設(shè)置每秒GET /api/values5 次調(diào)用的限制,則每秒可以調(diào)用5 次,但也可以調(diào)用5 次PUT /api/values。

    如果設(shè)置為false,則上述例子中GET、POST等請(qǐng)求共享次數(shù)限制。是否共享限制次數(shù)的設(shè)置。這里有個(gè)注意的地方,就是當(dāng)該參數(shù)設(shè)置為false的時(shí)候,只有端點(diǎn)設(shè)置為星號(hào)*的規(guī)則有效,其他規(guī)則無(wú)效,設(shè)置為true時(shí)所有規(guī)則有效。

    StackBlockedRequests:設(shè)為false的情況下,被拒絕的請(qǐng)求不會(huì)加入到計(jì)數(shù)器中,如一秒內(nèi)有三個(gè)請(qǐng)求,限流規(guī)則分別為一秒一次和一分鐘三次,則被拒絕的兩個(gè)請(qǐng)求是不會(huì)記錄在一分鐘三次的規(guī)則中的,也就是說(shuō)這一分鐘還能調(diào)用兩次該接口。設(shè)置為true的話,則被拒絕的請(qǐng)求也會(huì)加入計(jì)數(shù)器,像上述例子中的情況,一分鐘內(nèi)就不能調(diào)用了,三次全部記錄了。

    RealIpHeader:與配置項(xiàng)IP白名單IpWhitelist組合使用,如果該參數(shù)定義的請(qǐng)求頭名稱存在于一個(gè)請(qǐng)求中,并且該參數(shù)內(nèi)容為IP白名單中的IP,則不受限流規(guī)則限制。

    ClientIdHeader:與配置項(xiàng)客戶端白名單ClientIdHeader組合使用,如果該參數(shù)定義的請(qǐng)求頭名稱存在于一個(gè)請(qǐng)求中,并且該參數(shù)內(nèi)容為客戶端白名單中的名稱,則不受限流規(guī)則限制。

    HttpStatusCode:http請(qǐng)求限流后的返回碼。

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

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

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

    QuotaExceededResponse:限流后的返回值設(shè)置,返回內(nèi)容、狀態(tài)碼等。

    GeneralRules:通用規(guī)則設(shè)置,有三個(gè)參數(shù)為Endpoint、Period和Limit。

    Endpoint端點(diǎn)格式為{HTTP_Verb}:{PATH},可以使用星號(hào)來(lái)定位任何 HTTP 動(dòng)詞,如get:/api/values。

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

    Limit限制格式為{LONG},訪問(wèn)次數(shù)。

    ClientRateLimitPolicies:客戶端限流的特殊配置,規(guī)則和通用規(guī)則一樣設(shè)置,只不過(guò)需要配合ClientIdHeader在請(qǐng)求頭中來(lái)使用,需要使用app.UseClientRateLimiting();啟用,否則無(wú)效。這個(gè)參數(shù)名稱是可以更改的噢。通用規(guī)則和特殊規(guī)則是同優(yōu)先級(jí)的。

    IpRateLimitPolicies:IP限流的特殊配置,規(guī)則和通用規(guī)則一樣設(shè)置,只不過(guò)需要配合RealIpHeader在請(qǐng)求頭中來(lái)使用,需要使用app.UseIpRateLimiting();啟用,否則無(wú)效。這個(gè)參數(shù)名稱是可以更改的噢。通用規(guī)則和特殊規(guī)則是同優(yōu)先級(jí)的。

    3.特殊規(guī)則的啟用

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

    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中讀取配置參數(shù),之后是在Startup文件中的Configure方法選擇app.UseIpRateLimiting()或app.UseClientRateLimiting()啟動(dòng)IP特殊規(guī)則或者客戶端特殊規(guī)則,都存在的情況下,先執(zhí)行的先生效。

     三、請(qǐng)求返回頭

    限流啟動(dòng)后,執(zhí)行限流規(guī)則的返回頭會(huì)有三個(gè)參數(shù)分別為:

    X-Rate-Limit-Limit:現(xiàn)在時(shí)間,如1d。

    X-Rate-Limit-Remaining:剩余可請(qǐng)求次數(shù)。

    X-Rate-Limit-Reset:下次請(qǐng)求次數(shù)重置時(shí)間。

    多個(gè)限制規(guī)則會(huì)采用最長(zhǎng)的周期的規(guī)則顯示。

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

    "Content": "{{\"code\":429,\"msg\":\"訪問(wèn)太頻繁了,每{1}{0}次,請(qǐng)?jiān)趝2}秒后重試\",\"data\":null}}",

    {0}可以替換當(dāng)前阻止規(guī)則規(guī)定的次數(shù),{1}可以替換時(shí)間區(qū)間帶單位s、h等,{2}替換幾秒后嘗試當(dāng)單位為天或者小時(shí)等都會(huì)換算成秒。

    四、使用Redis存儲(chǔ)

    限流規(guī)則等目前都是通過(guò)內(nèi)存存儲(chǔ)的,我們結(jié)合實(shí)際會(huì)使用redis存儲(chǔ)。使用Microsoft.Extensions.Caching.Redis可以達(dá)到這么目的。

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

    我們緩存三類(lèi)數(shù)據(jù)1、訪問(wèn)計(jì)數(shù)2、ip特殊規(guī)則3、客戶端特殊規(guī)則

    1、訪問(wèn)計(jì)數(shù)

    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特殊規(guī)則

    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、客戶端特殊規(guī)則

    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文件中增加對(duì)應(yīng)的注入

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

    之后運(yùn)行就可以在redis中看到啦

    .Net Core如何實(shí)現(xiàn)限流

     五、修改規(guī)則

    規(guī)則只能修改IP和客戶端的特殊規(guī)則,因?yàn)樯弦徊糠忠呀?jīng)注入了改規(guī)則的對(duì)應(yīng)redis增刪查改的功能,所以我們可以利用這些方法重寫(xiě)規(guī)則,如下:

    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如何實(shí)現(xiàn)限流”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI