### net7 + 出现了 自带的 限流中间件 固定窗口、滑动窗口 并发 令牌桶 全局限流器
资料
限流的方法
速率限制算法
固定窗口算法 是最简单的算法之一。它将请求限制为一个固定的时间窗口,该窗口在任何时间点都只允许固定数量的请求。
滑动窗口算法 是固定窗口算法的改进版本,它将请求限制为一个可变的窗口,该窗口在任何时间点都只允许固定数量的请求。
令牌桶算法 使用固定大小的令牌桶来限制请求的速率。令牌桶最初被填满了指定数量的令牌。每次请求都会消耗一个令牌,如果令牌桶中没有令牌,则该请求会被拒绝。
并发算法 是一种非常简单的算法,它只允许固定数量的并发请求,但是不限制一段时间内的请求数。
IOC注册内置的限流中间件
//使用限流器
app.UseRateLimiter();
#region 限流中间件 IOC 注册
builder.Services.AddRateLimiter(options => {
//1、固定窗口限流策略
//配置说明:该固定窗口5s时间内,可以最多有5+2=7个请求,5个会被处理,2个会被排队,其他则会在一定时间后拒绝返回
options.AddFixedWindowLimiter(policyName: "fixed", op =>
{
op.PermitLimit = 5; //每个窗口时间范围内,允许100个请求被处理
op.Window = TimeSpan.FromSeconds(5);//窗口大小。即窗口时间长度5s。必须>TimeSpan.Zero
//5s 内只能5个请求--过后继续
//排队请求的处理顺序。这里设置为有限处理先来的请求
op.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
//窗口阈值。即每个窗口时间范围内,最多允许的请求个数。该值必须>0。
//当窗口请求达到最大值,后续请求会进入排队。该值用于设置对垒大小(即允许几个请求在排队队列等待)
op.QueueLimit = 2;
//开启新窗口时是否自动重置请求限制,默认true。如果是false,
//则需要手动调佣 FixedWindowRateLimiter.TryReplenish来重置
op.AutoReplenishment = true;
});
});
#endregion
启用限流中间件
app.UseRateLimiter();//限流中间件
// 这个是所有都增加限流了 不是是特点路由限流了
//app.MapControllers().RequireRateLimiting("fixed");
app.MapControllers();
按需加特性
[EnableRateLimiting("fixed")]
固定窗口限流器
滑动窗口
#region 限流中间件 IOC 注册
builder.Services.AddRateLimiter(options => {
//2、滑动窗口限流则略
//配置说明:窗口时间长度为30s,每个窗口内,最多允许100个请求,窗口段数3,每个段的时间间隔为30/3=10s,即窗口每10s滑动一段。
options.AddSlidingWindowLimiter(policyName:"sliding", slidingOptions =>
{
slidingOptions.PermitLimit = 100;
slidingOptions.Window = TimeSpan.FromSeconds(30);
slidingOptions.QueueLimit = 2;
slidingOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
slidingOptions.AutoReplenishment = true;//开启新窗口时是否自动重置请求限制,默认true
slidingOptions.SegmentsPerWindow = 3;
});
});
#endregion
令牌桶限流
#region 限流中间件 IOC 注册
builder.Services.AddRateLimiter(options => {
//策略说明:桶最多装4个令牌,每10秒发放一次令牌,每次发放2个令牌,所以在一个发放周期,
//最多可以处理4个请求(TokenLimit ),至少可以处理2个请求(TokensPerPeriod 一个周期发两个令牌)。
options.AddTokenBucketLimiter(policyName:"token_bucket", tokenBucketOptions =>
{
tokenBucketOptions.TokenLimit = 4;//桶最多可以装的令牌数,发放的多余令牌会被丢弃
tokenBucketOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(10);//令牌发放周期
tokenBucketOptions.TokensPerPeriod = 2;//每个周期发放令牌数
tokenBucketOptions.QueueLimit = 2;//当桶内的令牌全部被拿完(token=0)时,后续请求会进入排队
tokenBucketOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
tokenBucketOptions.AutoReplenishment = true;//进入新令牌发放周期,是否自动发放令牌。如果设置为false,则需要手动调用 TokenBucketRateLimiter.TryReplenish来发放
});
});
#endregion
并发限流器
并发限流器不是限制一段时间内的最大请求数,而是限制并发数。
【原理】:限制同一时刻并发请求的数量。
【特点】:可以充分利用服务器的性能,当出现突发流量时,服务器负载可能会持续过高。
#region 限流中间件 IOC 注册
builder.Services.AddRateLimiter(options => {
//策略说明:最大并发请求4,超过最大并发请求,则后续最多2个请求进入排队队列。
options.AddConcurrencyLimiter(policyName:"concurrency", concurrencyOptions =>
{
concurrencyOptions.PermitLimit = 4;//最大并发请求数
concurrencyOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
concurrencyOptions.QueueLimit = 2;//当并发请求数达到最大,后续请求进入排队,该参数用于配置队列大小
});
});
#endregion
全局限流器
通过GlobalLimiter,我们可以设置全局限流器,更准确的说法是全局分区限流器,该限流器会应用于所有请求。执行顺序为先执行全局限流器,再执行特定于路由终结点的限流器(如果存在的话)。
需要注意的是,相对于上面注册的限流策略来说,GlobalLimiter已经是一个限流器实例了,所以需要分配给他一个分区限流器实例,通过PartitionedRateLimiter.Create来创建。
builder.Services.AddRateLimiter(limiterOptions =>
{
limiterOptions.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, IPAddress>(context =>
{
IPAddress? remoteIpAddress = context.Connection.RemoteIpAddress;
// 针对非回环地址限流
if (!IPAddress.IsLoopback(remoteIpAddress!))
{
return RateLimitPartition.GetTokenBucketLimiter
(remoteIpAddress!, _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = 4,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
ReplenishmentPeriod = TimeSpan.FromSeconds(10),
TokensPerPeriod = 10,
AutoReplenishment = true
});
}
// 若为回环地址,则不限流
return RateLimitPartition.GetNoLimiter(IPAddress.Loopback);
});
});
链式组合限流器
builder.Services.AddRateLimiter(limiterOptions =>
{
var chainedLimiter = PartitionedRateLimiter.CreateChained(
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetFixedWindowLimiter
(userAgent, _ =>
new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 4,
Window = TimeSpan.FromSeconds(2)
});
}),
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetConcurrencyLimiter
(userAgent, _ =>
new ConcurrencyLimiterOptions
{
PermitLimit = 4,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2
});
})
limiterOptions.GlobalLimiter = chainedLimiter ;
);
注意 注意 注意
比如一个政策限流是 最大Limit=100
这个政策,分别加在两个路由中 [EnableRateLimiting(“xxx”)]
只有这个两个路由 请求 总和到达100,就会现在访问了。
下面我们就借助AddPolicy,分别使用两种方式添加一个自定义策略“my_policy”:一个用户一个分区,匿名用户共享一个分区。
通过委托创建自定义限流策略
builder.Services.AddRateLimiter(limiterOptions =>
{
limiterOptions.AddPolicy(policyName: "my_policy", httpcontext =>
{
var userId = "anonymous user";
if (httpcontext.User.Identity?.IsAuthenticated is true)
{
userId = httpcontext.User.Claims.First(c => c.Type == "id").Value;
}
return RateLimitPartition.GetFixedWindowLimiter(partitionKey: userId, _ => new
FixedWindowRateLimiterOptions
{
PermitLimit = 3,
Window = TimeSpan.FromSeconds(60),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0
});
});
});
通过IRateLimiterPolicy创建自定义限流策略
public interface IRateLimiterPolicy<TPartitionKey>
{
// 若不为空,则执行它(不会执行全局的),如果它为空,则执行全局的
Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; }
// 获取限流分区
RateLimitPartition<TPartitionKey> GetPartition(HttpContext httpContext);
}
public class MyRateLimiterPolicy : IRateLimiterPolicy<string>
{
// 可以通过依赖注入参数
public MyRateLimiterPolicy(ILogger<MyRateLimiterPolicy> logger)
{
// 可以设置自己的限流拒绝回调逻辑,而不使用上面全局设置的 limiterOptions.OnRejected
OnRejected = (ctx, token) =>
{
ctx.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
logger.LogWarning($"Request rejected by {nameof(MyRateLimiterPolicy)}");
return ValueTask.CompletedTask;
};
}
public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; }
public RateLimitPartition<string> GetPartition(HttpContext httpContext)
{
var userId = "anonymous user";
if (httpContext.User.Identity?.IsAuthenticated is true)
{
userId = httpContext.User.Claims.First(c => c.Type == "id").Value;
}
return RateLimitPartition.GetFixedWindowLimiter(partitionKey: userId, _ => new
FixedWindowRateLimiterOptions
{
PermitLimit = 3,
Window = TimeSpan.FromSeconds(60),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0
});
}
}
// 记得注册它
builder.Services.AddRateLimiter(limiterOptions =>
{
limiterOptions.AddPolicy<string, MyRateLimiterPolicy>(policyName: "my_policy");
}
限流应用
EnableRateLimitingAttribute & DisableRateLimitingAttribute