首頁 > 軟體

ASP.NET Core基於滑動視窗實現限流控制

2022-03-02 19:02:11

前言:

在實際專案中,為了保障伺服器的穩定執行,需要對介面的可存取頻次進行限流控制,避免因使用者端頻繁請求導致伺服器壓力過大。

而​AspNetCoreRateLimit是目前ASP.NET Core下最常用的限流解決方案。

檢視它的實現程式碼,我發現它使用的固定視窗演演算法。

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
        };
    }
}

二、固定視窗演演算法

固定視窗演演算法是將時間線劃分為固定大小的視窗,併為每個視窗分配一個計數器。每個請求,根據其到達時間,被對映到一個視窗。如果視窗中的計數器已達到限制,則拒絕落在此視窗中的請求。

例如,如果我們將視窗大小設定為one分鐘,每分鐘允許ten個請求:

ASP.NET Core基於滑動視窗演演算法實現限流控制 #yyds乾貨盤點#_滑動視窗

59秒的請求將被阻止,因為這時已經接受了10個請求。1分鐘時計數器歸零,所以1分01秒的請求可以接受。

​​固定視窗演演算法的問題主要在於,如果在視窗邊緣發生大量請求,會導致限流策略失效。​​

比如,在59秒接收了9個請求,在1分01秒又可以再接收10個請求,相當於每分鐘允許了20個請求。

三、滑動視窗演演算法

滑動視窗類似於固定視窗演演算法,但它通過將前一個視窗中的加權計數新增到當前視窗中的計數來計算估計數,如果估計數超過計數限制,則請求將被阻止。

具體公式如下:

估計數 = 前一視窗計數 * (1 - 當前視窗經過時間 / 單位時間) + 當前視窗計數

例如,假設限制為每分鐘10個:

視窗[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. 因此我們拒絕此請求。

​​即使兩個視窗都沒有超過限制,請求也會被拒絕,因為前一個和當前視窗的加權和確實超過了限制。​​

四、實現

根據上面的公式,實現滑動視窗演演算法程式碼如下:

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;
                _requestCount = 0;

                elapsedSeconds = (currentTime - _windowStartTime).TotalSeconds;
            } 

            var requestCount = _prevRequestCount * (1 - elapsedSeconds / _requestIntervalSeconds) + _requestCount + 1;
            if (requestCount <= _requestLimit)
            {
                _requestCount++;
                return true;
            }
        }

        return false;
    }
}

如果最近的2次請求相距2個視窗時間,則可以認為前一視窗計數為0,重新開始計數。

六、使用

新建Middleware,使用滑動視窗演演算法進行限流:

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);
    }
}

​​需要注意的是,我們註冊Middleware時,必須使用單例模式,保證所有請求通過同一SlidingWindow計數:​​

services.AddSingleton<RateLimitMiddleware>();

結論:

使用滑動視窗演演算法,可以有效避免固定視窗演演算法存在的視窗邊緣大量請求無法限制的問題。

到此這篇關於ASP.NET Core基於滑動視窗實現限流控制的文章就介紹到這了,更多相關ASP.NET Core基於滑動視窗實現限流控制內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com