網站首頁 編程語言 正文
前言:
在實際項目中,為了保障服務器的穩定運行,需要對接口的可訪問頻次進行限流控制,避免因客戶端頻繁請求導致服務器壓力過大。
而?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();
結論:
使用滑動窗口算法,可以有效避免固定窗口算法存在的窗口邊緣大量請求無法限制的問題。
原文鏈接:https://blog.51cto.com/MyIO/5056224
相關推薦
- 2023-01-17 Android繪制文本與圖片及動效詳解_Android
- 2023-07-15 oracle 序列/自增ID
- 2022-08-16 python上下文管理器協議的實現_python
- 2022-07-07 一篇文章讀懂nginx的gzip功能_nginx
- 2022-08-20 python操作csv格式文件之csv.DictReader()方法_python
- 2022-06-28 ASP.NET一次性對GridView批量更新多行數據_實用技巧
- 2023-05-21 詳解Python中文件路徑_python
- 2022-05-14 C++?STL中vector容器的使用_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支