併發請求太多,伺服器崩潰了?試試使用 ASP.NET Core Web API 操作篩選器對請求進行限流

代码掌控者發表於2024-10-19

Coding-7

前言

請求限流(Rate Limiting)主要是一種用於控制客戶端對伺服器的請求頻率的機制。

其目的是限制客戶端在一定時間內可以傳送的請求數量,保護伺服器免受過多請求的影響,確保系統的穩定性和可靠性。

請求限流通常會基於以下幾個因素來進行限制:

  1. 時間視窗:規定了在多長時間內允許的請求次數
  2. 請求配額:在時間視窗內允許的最大請求數量
  3. 客戶端標識:根據客戶端的 IP 地址、使用者標識或其他識別符號來進行限流

請求限流技術可以應用在很多場景,本文主要講述 ASP.NET Core Web API 如何使用操作篩選器對請求進行限流。

Step By Step 步驟

  1. 建立一個ASP.NET Core Web API 專案

  2. 編寫自定義的操作篩選器 RateLimitFilter,實現 "1s內只允許最多有一個來自同一個IP地址的請求"(留意註釋

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Caching.Memory;
    
    public class RateLimitFilter : IAsyncActionFilter
    {
    	private readonly IMemoryCache memCache;
    
    	// 注入的IMemoryCache
    	public RateLimitFilter(IMemoryCache memCache)
    	{
    		this.memCache = memCache;
    	}
    
    	public Task OnActionExecutionAsync(
    		ActionExecutingContext context, 
    		ActionExecutionDelegate next)
    	{
    		// 透過注入的 IMemoryCache 來記錄使用者上一次訪問的時間戳
    		// 在分散式系統下可以改用分散式快取來代替記憶體快取
    		string removeIP = context.HttpContext.Connection.RemoteIpAddress!.ToString();
    		string cacheKey = $"LastVisitTick_{removeIP}";
    		
    		// 從快取中獲取這個客戶端IP地址上一次訪問伺服器的時間
    		long? lastTick = memCache.Get<long?>(cacheKey);
    		if (lastTick == null || Environment.TickCount64 - lastTick > 1000)
    		{
    			// 如果快取中不存在上一次訪問時間或者上一次訪問時間距離現在已經超過 1s,則透過 next 來執行後面的篩選器
    			memCache.Set(cacheKey, Environment.TickCount64, TimeSpan.FromSeconds(10));
    			return next();
    		}
    		else
    		{
    			// 否則說明 IP 頻繁訪問,不執行 next,相當於終止操作方法的執行
    			context.Result = new ContentResult { StatusCode = 429 };
    			return Task.CompletedTask;
    		}
    	}
    }
    

    程式碼中的記憶體快取和分散式快取可以參考本人之前文章《看看 Asp.net core Webapi 專案如何優雅地使用記憶體快取》和《看看 Asp.net core Webapi 專案如何優雅地使用分散式快取

  3. 開啟 Program.cs,註冊這個操作篩選器

    using Microsoft.AspNetCore.Mvc;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    
    builder.Services.AddControllers();
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    // 註冊記憶體快取服務
    builder.Services.AddMemoryCache();
    
    // 註冊請求限流過濾器
    builder.Services.Configure<MvcOptions>(options => { 
    	options.Filters.Add<RateLimitFilter>();
    });
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
    	app.UseSwagger();
    	app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

測試

啟動專案,並且訪問介面,如果訪問頻率不高的話,介面能夠正常工作。

如果訪問頻率很高的話,伺服器就會提示 "Only once per second!"

總結

在操作篩選器中,透過 await next() 來執行下一個篩選器,

如果沒有下一個篩選器,程式就會執行目標操作方法。

如果不呼叫 await next(),就可以終止操作方法的執行了

相關文章