Skip to content

在 .NET 中高级限流应用场景

Published: at 12:00 AM

摘录

限流是指限制对您的应用程序的请求数量。它通常在特定时间窗口内或基于其他一些标准应用。

原文 Advanced Rate Limiting Use Cases In .NET 由 Milan Jovanović 撰写。


限流是指限制对您的应用程序的请求数量。它通常在特定时间窗口内或基于其他标准应用。

它有几个帮助原因:

.NET 7 带来了内置限流器,但您需要知道如何正确实施它。否则您可能会使系统停滞不前 - 我们不希望这样。

在本周的新闻通讯中,我会教您:

所以,让我们深入了解!

.NET 7 中的内置限流

从 .NET 7 开始,我们可以在 Microsoft.AspNetCore.RateLimiting 命名空间中使用内置的限流中间件。API 非常直接,您可以通过几行代码创建一个限流策略。

我们可以使用四种限流算法之一:

以下是通过调用 AddTokenBucketLimiter 方法来定义限流策略的方法:

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    rateLimiterOptions.AddTokenBucketLimiter("token", options =>
    {
        options.TokenLimit = 1000;
        options.ReplenishmentPeriod = TimeSpan.FromHours(1);
        options.TokensPerPeriod = 700;
        options.AutoReplenishment = true;
    });
});

现在您可以在您的端点或控制器上引用 token 限流策略。

您还必须将 RateLimitingMiddleware 添加到请求管道:

app.UseRateLimiter();

您可以在这里了解更多关于 .NET 7 中的限流,所以我不会深入基础原理。

按 IP 地址限流用户

我刚才展示的方法存在一个问题 - 限流策略是全局的并且适用于所有用户

大多数时候,您不希望这样做。限流应该是细粒度的并且适用于单个用户

幸运的是,您可以通过创建 RateLimitPartition 实现这一点。

RateLimitPartition 有两个组件:

以下是如何定义具有固定窗口策略的限流器,并且分区键是用户的IP 地址

builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("fixed-by-ip", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromMinutes(1)
            }));
});

IP 地址限流可以为未经认证的用户提供一个好的安全层。您不知道谁在访问您的系统,并且不能应用更细致的限流。这可以帮助保护系统免受试图进行 DDoS 攻击的恶意用户的伤害。

您还可以使用 CreateChained API 创建链式限流器。它允许您传入多个 PartitionedRateLimiter,这些限流器组合成一个 PartitionedRateLimiter。链式限流器依次(一个接一个)运行所有输入限流器。

如果您的应用程序运行在反向代理后面,您需要确保不要限制代理的 IP 地址。反向代理通常使用 X-Forwarded-For转发原始 IP 地址。因此,您可以将其用作分区键

builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("fixed-by-ip", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            httpContext.Request.Headers["X-Forwarded-For"].ToString(),
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromMinutes(1)
            }));
});

按身份限流用户

如果您要求用户认证以访问您的 API,您可以确定当前用户是谁。然后您可以使用用户的身份作为 RateLimitPartition分区键

以下是您如何创建这样的限流策略:

builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("fixed-by-user", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.Identity?.Name?.ToString(),
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromMinutes(1)
            }));
});

我使用 HttpContext 上的 User.Identity 值获取当前用户的 Name 声明。这通常对应于 JWT 内部的 sub 声明 - 即用户标识符。

在反向代理上应用限流

在健壮的实现中,你想在请求触及你的 API 之前在反向代理级别上限流。如果您有一个分布式系统,这是一个要求。否则,您的系统将无法正确工作。

有许多反向代理实现可供选择。

YARP 是一个与 .NET 集成良好的反向代理。这不足为奇,因为它是用C#写的。您可以在这里了解更多关于使用 YARP 构建 API 网关 的信息。

要在 YARP 上实施反向代理的限流,您需要:

"products-route": {
  "ClusterId": "products-cluster",
  "RateLimiterPolicy": "sixty-per-minute-fixed",
  "Match": {
    "Path": "/products/{**catch-all}"
  },
  "Transforms": [
    { "PathPattern": "{**catch-all}" }
  ]
}

内置限流中间件使用内存中的存储来跟踪请求数量。如果你想在高可用性设置中运行你的反向代理,你将需要使用分布式缓存。值得研究的一个好选择是使用 Redis 后端进行限流。

结束思考

使用 PartitionedRateLimiter,您可以轻松创建细粒度的限流策略。

两种常见的方法是:

看到 .NET 团队推出限流功能,我真的很兴奋。但是,当前实施方式有其缺点。主要问题是它只在内存中工作。对于分布式解决方案,您需要自己实现一些东西或使用外部库。

您可以使用 YARP 反向代理来构建健壮且可扩展的分布式系统。并且只需几行代码即可在反向代理级别添加限流。我在我的系统中广泛使用它。