温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

.Net Core如何实现限流

发布时间:2021-07-05 14:32:45 来源:亿速云 阅读:221 作者:小新 栏目:开发技术

小编给大家分享一下.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"等。可以配合RealIpHeader参数使用,也单独使用,请求的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中看到啦

    .Net Core如何实现限流

     五、修改规则

    规则只能修改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如何实现限流”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI