.NET多租户系统中如何实现分别限流

限流是后端服务开发中经常要集成的一个功能,对于防范系统因压力过大导致崩溃特别有用。在多租户系统中,限流对于限制单个租户使用的资源量也特别有用,这篇文章就来一探究竟。.

问题

对于一个多租户系统,某些租户如果使用了过多的资源,很可能会对其它租户造成影响。比如对于某个资源的查询,系统的最高容量是300qps,假设正常情况下租户的查询水位都在10qps,此时可以同时为30个租户服务;突然某个租户的查询水位上升到100qps,如果系统没有限流,系统会变慢甚至可能崩溃,如果系统有限流,所有的租户都可能受到限流的影响。

租户的查询水位突然暴涨可能是正常业务,也可能是恶意攻击,不过无论哪种情况对其它租户来说都是不公平的。这时候就可以对不同的租户分别限流,下面就来看一下如何解决。

原理

下面这张图演示了对多租户系统进行限流的原理。

.NET多租户系统中如何实现分别限流

租户A、B、C分别发起服务请求,服务中首先对用户身份进行鉴别,身份验证通过的进入限流检查环节,对每个租户分别进行限流检查,如果未达到限流阈值,则可以通过进入下一步,如果达到了限流阈值,则终止处理并返回错误给租户。

实现

如果你已经看懂了原理,相信你完全有能力写出对应的限流控制逻辑。不过为了研发效率,还是应该尽可能重用现有的组件,这里使用 FireflySoft.RateLimit 来实现这一逻辑,演示代码是以ASP.NET Core为基础的,你也可以在任何其它.NET网络服务框架中使用,甚至自己手写的专用处理程序。

安装Nuget包

使用包管理器控制台:

Install-Package FireflySoft.RateLimit.AspNetCore

或者使用 .NET CLI:

dotnet add package FireflySoft.RateLimit.AspNetCore

或者直接添加到项目文件中:

<ItemGroup>
<PackageReference Include="FireflySoft.RateLimit.AspNetCore" Version="2.*" />
</ItemGroup>

使用中间件

在Startup.cs中注册限流服务并使用限流中间件。

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddRateLimit(new InProcessFixedWindowAlgorithm(
        new[] {
            new FixedWindowRule()
            {
                Id = "1",
                ExtractTarget = context =>
                {
                    // 这里假设限流的目标是租户的用户Id,它是从HTTP Header中传递过来的
                    return (context as HttpContext).Request.GetTypedHeaders().Get<string>("userId");
                },
                CheckRuleMatching = context =>
                {
                    // 假设只对 /Weather/GetToday 这个接口请求进行限流
                    var path = (context as HttpContext).Request.Path.Value;
                    if(path == "/Weather/GetToday"){
                        return true;
                    }
                    return false;
                },
                Name="多租户限流",
                LimitNumber=10, // 限流阈值
                StatWindow=TimeSpan.FromSeconds(1), //限流的时间窗口,这里是1秒
                StartTimeType=StartTimeType.FromNaturalPeriodBeign
            }
        })
    );

    ...
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseRateLimit();

    ...
}

搞定这两步,多租户分别限流功能就可以正常运转了。

这里对所有租户都使用了相同的限流阈值,如果租户有特权租户和普通租户的区别,则可以在上边的限流规则中分别为特权租户和普通租户定义不同的规则就可以了,具体可以参考这篇文章:ASP.NET Core中如何对不同类型的用户进行区别限流。

其它技术问题

注意这里使用的限流算法是进程内固定窗口限流,如果你需要在分布式环境下统一计数限流,则可以使用 FireflySoft.RateLimit 自带的Redis固定窗口限流算法,不过你需要先有一个Redis服务。

固定窗口算法比较刚性,实际情况下,请求很可能是不均匀的,一会多一会少,刚性算法很难设置一个合理的限流阈值。如果你想要允许一定的突发流量,则可以使用滑动窗口、漏桶、令牌桶等算法,FireflySoft.RateLimit 中已经集成了这些算法,直接使用即可。

如果系统的服务能力增强了,现在允许一个租户每秒发起20次请求,则需要调整规则,FireflySoft.RateLimit底层是支持动态调整规则的,具体可以看这篇文章:.NET6运行时动态更新限流阈值 。

好了,以上就是本文的主要内容了。欢迎访问这个仓库:https://github.com/bosima/FireflySoft.RateLimit