.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中看到啦

五、修改规则
规则只能修改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项目实战-统一认证平台】第七章 网关篇-自定义客户端限流
[.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何在网关上增加自定义客户端授权功能,从设计到编码实现,一步一步详细讲解,相信大家也掌握了自定义中间件的开发技巧了,本篇我们 ...
- 【Dnc.Api.Throttle】适用于.Net Core WebApi接口限流框架
Dnc.Api.Throttle 适用于Dot Net Core的WebApi接口限流框架 使用Dnc.Api.Throttle可以使您轻松实现WebApi接口的限流管理.Dnc.Api.Thr ...
- .Net Core使用Ocelot网关(一) -负载,限流,熔断,Header转换
1.什么是API网关 API网关是微服务架构中的唯一入口,它提供一个单独且统一的API入口用于访问内部一个或多个API.它可以具有身份验证,监控,负载均衡,缓存,请求分片与管理,静态响应处理等.API ...
- .net core使用ocelot---第四篇 限流熔断
简介 .net core使用ocelot---第一篇 简单使用 .net core使用ocelot---第二篇 身份验证 .net core使用ocelot---第三篇 日志记录 前几篇文章我们陆续介 ...
- .Net Core结合AspNetCoreRateLimit实现限流
前言 相信使用过WebApiThrottle的童鞋对AspNetCoreRateLimit应该不陌生,AspNetCoreRateLimit是一个ASP.NET Core速率限制的解决方案,旨在控制客 ...
- ASP.NET Core中如何对不同类型的用户进行区别限流
老板提出了一个新需求,从某某天起,免费用户每天只能查询100次,收费用户100W次. 这是一个限流问题,聪明的你也一定想到了如何去做:记录用户每一天的查询次数,然后根据当前用户的类型使用不同的数字做比 ...
- ASP.NET Core中使用令牌桶限流
在限流时一般会限制每秒或每分钟的请求数,简单点一般会采用计数器算法,这种算法实现相对简单,也很高效,但是无法应对瞬时的突发流量. 比如限流每秒100次请求,绝大多数的时间里都不会超过这个数,但是偶尔某 ...
- ASP.NET Core中使用固定窗口限流
算法原理 固定窗口算法又称计数器算法,是一种简单的限流算法.在单位时间内设定一个阈值和一个计数值,每收到一个请求则计数值加一,如果计数值超过阈值则触发限流,如果达不到则请求正常处理,进入下一个单位时间 ...
- ASP.NET Core中使用滑动窗口限流
滑动窗口算法用于应对请求在时间周期中分布不均匀的情况,能够更精确的应对流量变化,比较著名的应用场景就是TCP协议的流量控制,不过今天要说的是服务限流场景中的应用. 算法原理 这里假设业务需要每秒钟限流 ...
随机推荐
- 风变编程(Python自学笔记)第10关-工作量计算器
1.%f的意思是格式化字符串为浮点型,%.1f的意思是格式化字符串为浮点型,并保留1位小数. 2.向上取整:ceil() 使用ceil()方法时需要导入math模块,例如 1 >>> ...
- 1.HTML入门
1.1 初识HTML 1.1.1 概述 网络世界已经跟我们息息相关,当我们打开一个网站,首先映入眼帘的就是一个个华丽多彩的网页.这些网页,不仅呈现着基本的内容,还具备优雅的布局和丰富的动态效果,这一切 ...
- [设计模式] 设计模式课程(二十)--命令模式(Command)
概述 "行为变化"模式:组件构建过程中,组件行为的变化经常会导致组件本身剧烈的变化."行为变化"模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现 ...
- CSS变量和浏览器前缀
一.CSS变量 CSS变量是CSS的新特性,大多数浏览器都实现了这个功能,使用CSS变量有利代码复用,而且当我们修改变量值时,所有引用该变量的属性都会发生改变. 定义变量后可以有两种使用方法,第一种时 ...
- 磁盘IO过高时的处理办法 针对系统中磁盘IO负载过高的指导性操作
磁盘IO过高时的处理办法 针对系统中磁盘IO负载过高的指导性操作 主要命令:echo deadline > /sys/block/sda/queue/scheduler 注:以下的内容仅是提供参 ...
- #undef 与 exit(0) 使用
#undef 与 #defined 反,实际使用中较多的是当你需要使用自己定义的标准C里面已经的函数时可以这样操作: exit(0)和exit(1)是系统判断函数是否有正常的退出,一般0表示正常的退 ...
- java学习之旅2——set
var set = Collections.synchronizedSet(new HashSet<Integer>()); 可以这样来获得一个同步的集合. 对于HashSet, for循 ...
- IDEA中怎么创建ini文件
首先博主在这使用的是idea的2019.3.2的版本,不知道的话可以打开help菜单的about查看 第一步: 具体需要在setings安装ini插件 第二步: 在File Types中查看ini,没 ...
- Task类学习教程—组合任务ContinueWith
Task类学习教程-组合任务.ContinueWith 一.简介 通过任务,可以指定在任务完成之后,应开始运行之后另一个特定任务.ContinueWith是Task根据其自身状况,决定后续应该作何操作 ...
- OneFlow 并行特色
OneFlow 并行特色 在 Consistent 与 Mirrored 视角中,已经知道 OneFlow 提供了 mirrored 与 consistent 两种看待分布式系统的视角,并且提前知道了 ...