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特殊规则