ASP.NET Core中间件实现限流的代码

 更新时间:2022年03月01日 14:10:58   作者:愚公搬代码  
这篇文章主要介绍了ASP.NET Core中间件实现限流的方法,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、限流算法

在高并发系统中,有三把利器用来保护系统:缓存、降级和限流。

本文主要是介绍限流,限流算法主要有以下三种:

1.计数器算法

  • 固定窗口
  • 滑动窗口

2.令牌桶算法

3.漏桶算法

1.计数器算法

1.1 固定窗口算法

计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter。

java中的具体实现如下:

public class CounterTest {
    public long timeStamp = getNowTime();
    public int reqCount = 0;
    public final int limit = 100; // 时间窗口内最大请求数
    public final long interval = 1000; // 时间窗口ms

    public boolean grant() {
        long now = getNowTime();
        if (now < timeStamp + interval) {
            // 在时间窗口内
            reqCount++;
            // 判断当前时间窗口内是否超过最大请求控制数
            return reqCount <= limit;
        } else {
            timeStamp = now;
            // 超时后重置
            reqCount = 1;
            return true;
        }
    }

    public long getNowTime() {
        return System.currentTimeMillis();
    }
}

.NET Core中的具体实现如下:

AspNetCoreRateLimit是目前ASP.NET Core下最常用的限流解决方案,AspNetCoreRateLimit的源码实现是固定窗口算法如下:

var entry = await _counterStore.GetAsync(counterId, cancellationToken);

if (entry.HasValue)
{
    // entry has not expired
    if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow)
    {
        // increment request count
        var totalCount = entry.Value.Count + _config.RateIncrementer?.Invoke() ?? 1;
        // deep copy
        counter = new RateLimitCounter
        {
            Timestamp = entry.Value.Timestamp,
            Count = totalCount
        };
    }
}

固定窗口算法缺点

在这里插入图片描述

从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

1.2 滑动窗口算法

滑动窗口类似于固定窗口算法,但它通过将前一个窗口中的加权计数添加到当前窗口中的计数来计算估计数,如果估计数超过计数限制,则请求将被阻止。

具体公式如下:

估计数 = 前一窗口计数 * (1 - 当前窗口经过时间 / 单位时间) + 当前窗口计数

在这里插入图片描述

窗口[00:00, 00:01)中有9个请求,窗口[00:01, 00:02)中有5个请求。对于01:15到达的请求,即窗口[00:01, 00:02)的25%位置,通过公式计算请求计数:9 x (1 - 25%) + 5 = 11.75 > 10. 因此我们拒绝此请求。

即使两个窗口都没有超过限制,请求也会被拒绝,因为前一个和当前窗口的加权和确实超过了限制。

2.令牌桶算法

令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)根据限流大小,设置按照一定的速率往桶里添加令牌;
3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;

在这里插入图片描述

3.漏桶算法

漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

在这里插入图片描述

二、ASP.NET Core中间件实现限流

1.中间件代码

public class SlidingWindow
{
    private readonly object _syncObject = new object();

    private readonly int _requestIntervalSeconds;
    private readonly int _requestLimit;
    private DateTime _windowStartTime;
    private int _prevRequestCount;
    private int _requestCount;
    public SlidingWindow(int requestLimit, int requestIntervalSeconds)
    {
        _windowStartTime = DateTime.Now;
        _requestLimit = requestLimit;
        _requestIntervalSeconds = requestIntervalSeconds;
    }
    public bool PassRequest()
        lock (_syncObject)
        {
            var currentTime = DateTime.Now;
            var elapsedSeconds = (currentTime - _windowStartTime).TotalSeconds;
            if (elapsedSeconds >= _requestIntervalSeconds * 2)
            {
                _windowStartTime = currentTime;
                _prevRequestCount = 0;
                _requestCount = 0;
                elapsedSeconds = 0;
            }
            else if (elapsedSeconds >= _requestIntervalSeconds)
                _windowStartTime = _windowStartTime.AddSeconds(_requestIntervalSeconds);
                _prevRequestCount = _requestCount;
                elapsedSeconds = (currentTime - _windowStartTime).TotalSeconds;
            } 
            var requestCount = _prevRequestCount * (1 - elapsedSeconds / _requestIntervalSeconds) + _requestCount + 1;
            if (requestCount <= _requestLimit)
                _requestCount++;
                return true;
        }
        return false;
}

如果最近的2次请求相距2个窗口时间,则可以认为前一窗口计数为0,重新开始计数。

public class RateLimitMiddleware : IMiddleware
{
    private readonly SlidingWindow _window;

    public RateLimitMiddleware()
    {
        _window = new SlidingWindow(10, 60);
    }
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (!_window.PassRequest())
        {
            context.SetEndpoint(new Endpoint((context) =>
            {
                context.Response.StatusCode = StatusCodes.Status403Forbidden;
                return Task.CompletedTask;
            },
                        EndpointMetadataCollection.Empty,
                        "限流"));
        }

        await next(context);
    }
}

2.在管道中的使用

需要注意的是,我们注册Middleware时,必须使用单例模式,保证所有请求通过同一SlidingWindow计数:

services.AddSingleton<RateLimitMiddleware>();

到此这篇关于ASP.NET Core中间件-限流的文章就介绍到这了,更多相关ASP.NET Core中间件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • ASP.NET将文件写到另一服务器(图文教程)及注意事项

    ASP.NET将文件写到另一服务器(图文教程)及注意事项

    有时我们需要将来自于客户端的文件上传到WEB服务器端,并在服务端将文件存储到第三方文件服务器中存储,既然有需求,那就有实现了,感兴趣的你可以了解此文,或许对你学习asp.net 起到很好的作用哦
    2013-01-01
  • 异步 HttpContext.Current实现取值的方法(解决异步Application,Session,Cache...等失效的问题)

    异步 HttpContext.Current实现取值的方法(解决异步Application,Session,Cache.

    在一个项目中,为了系统执行效率更快,把一个经常用到的数据库表通过dataset放到Application中,发现在异步实现中每一次都会出现HttpContext.Current为null的异常,后来在网上查了好多资料,发现问这个问题的人多,回答的少
    2009-07-07
  • Asp.net自定义控件之加载层

    Asp.net自定义控件之加载层

    这篇文章主要介绍了Asp.net自定义控件之加载层,点击按钮自动显示信息加载效果,具有一定的实用性,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • asp.net 无刷新翻页就是这么简单

    asp.net 无刷新翻页就是这么简单

    前两天看了一个自定义分页控件,和AspNetPager一样是实现IPostBackEventHandler接口,不过简洁许多,就想能不能实现ICallbackEventHandler接口做到无刷新分页呢?想到了就马上去做,终于,设想变成了现实!!
    2010-03-03
  • ASP.NET Core文件压缩常见使用误区(最佳实践)

    ASP.NET Core文件压缩常见使用误区(最佳实践)

    本文给大家分享ASP.NET Core文件压缩常见的三种误区,就每种误区给大家讲解的非常详细,是项目实践的最佳纪录,对ASP.NET Core文件压缩相关知识感兴趣的朋友一起看看吧
    2021-05-05
  • C# new和override的区别分析

    C# new和override的区别分析

    C# new和override都会覆盖父类中的方法。那它们两者之前有什么区别呢?
    2013-03-03
  • Web API中使用Autofac实现依赖注入

    Web API中使用Autofac实现依赖注入

    本文详细讲解了Web API中使用Autofac实现依赖注入的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • .NET全局静态可访问IServiceProvider的过程详解(支持Blazor)

    .NET全局静态可访问IServiceProvider的过程详解(支持Blazor)

    为解决在静态方法中访问依赖注入(DI)容器的问题,提出了通过DependencyInjection.StaticAccessor包实现静态访问,这一方法特别适用于需要在静态方法中获取范围内(Scoped)服务的场景,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • .NET Core系列之MemoryCache 缓存选项

    .NET Core系列之MemoryCache 缓存选项

    这篇文章主要介绍了.NET Core系列之MemoryCache 缓存选项,详细的介绍一下 MSCache 中的 Options,由此来介绍一些 MSCache 中的内部机制,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • js插件类库组织与管理(基于asp.net管理)

    js插件类库组织与管理(基于asp.net管理)

    在开发一个较大规模的网站,js插件类库应用的是相当多。那么自然在一个页面里就存在不少script和script相关的link标记,这样js组织与管理自然成了一大问题。
    2010-07-07

最新评论