我们正在尝试各种方法来限制用户在特定时间段内的行为:
限制问答帖子 限制编辑 限制提要检索
目前,我们使用缓存只是简单地插入用户活动的记录——如果该记录存在,并且/当用户执行相同的活动时,我们就会节流。
自动使用缓存给我们提供了陈旧的数据清洗和滑动用户活动窗口,但它将如何扩展可能是一个问题。
还有什么其他方法可以确保请求/用户操作可以被有效地控制(强调稳定性)?
我们正在尝试各种方法来限制用户在特定时间段内的行为:
限制问答帖子 限制编辑 限制提要检索
目前,我们使用缓存只是简单地插入用户活动的记录——如果该记录存在,并且/当用户执行相同的活动时,我们就会节流。
自动使用缓存给我们提供了陈旧的数据清洗和滑动用户活动窗口,但它将如何扩展可能是一个问题。
还有什么其他方法可以确保请求/用户操作可以被有效地控制(强调稳定性)?
当前回答
微软为iis7提供了一个名为iis7.0 Beta动态IP限制扩展的新扩展。
iis7.0的动态IP限制是一个模块,提供了针对web服务器和网站的拒绝服务和暴力攻击的保护。这种保护是通过暂时阻止HTTP客户端的IP地址来提供的,这些客户端并发请求的数量异常高,或者在短时间内发出大量请求。” http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/
例子:
如果你在Y毫秒内设置了X个请求后阻塞,或者在Y毫秒内设置了X个并发连接,IP地址将被阻塞Y毫秒,然后请求将再次被允许。
其他回答
我们使用从这个URL http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx借来的技术,不是为了节流,而是为了一个可怜人的拒绝服务(dos)。这也是基于缓存的,可能与您正在做的类似。你是在节流防止国防部攻击吗?路由器当然可以用来减少dos;你觉得路由器能满足你的需求吗?
微软为iis7提供了一个名为iis7.0 Beta动态IP限制扩展的新扩展。
iis7.0的动态IP限制是一个模块,提供了针对web服务器和网站的拒绝服务和暴力攻击的保护。这种保护是通过暂时阻止HTTP客户端的IP地址来提供的,这些客户端并发请求的数量异常高,或者在短时间内发出大量请求。” http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/
例子:
如果你在Y毫秒内设置了X个请求后阻塞,或者在Y毫秒内设置了X个并发连接,IP地址将被阻塞Y毫秒,然后请求将再次被允许。
我花了一些时间为。net 5+(以前的。net Core)开发了一个对等的版本,所以这里是一个起点。
旧的缓存方式已经消失,取而代之的是microsoft . extensions . cache . memory with IMemoryCache。
我把它分开了一点,所以这是你需要的……
缓存管理类
我在这里添加了所有的东西,所以你可以看到using语句。
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using System;
using System.Threading;
namespace MyWebApplication
{
public interface IThrottleCache
{
bool AddToCache(string key, int expriryTimeInSeconds);
bool AddToCache<T>(string key, T value, int expriryTimeInSeconds);
T GetFromCache<T>(string key);
bool IsInCache(string key);
}
/// <summary>
/// A caching class, based on the docs
/// https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-6.0
/// Uses the recommended library "Microsoft.Extensions.Caching.Memory"
/// </summary>
public class ThrottleCache : IThrottleCache
{
private IMemoryCache _memoryCache;
public ThrottleCache(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public bool AddToCache(string key, int expriryTimeInSeconds)
{
bool isSuccess = false; // Only a success if a new value gets added.
if (!IsInCache(key))
{
var cancellationTokenSource = new CancellationTokenSource(
TimeSpan.FromSeconds(expriryTimeInSeconds));
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSize(1)
.AddExpirationToken(
new CancellationChangeToken(cancellationTokenSource.Token));
_memoryCache.Set(key, DateTime.Now, cacheEntryOptions);
isSuccess = true;
}
return isSuccess;
}
public bool AddToCache<T>(string key, T value, int expriryTimeInSeconds)
{
bool isSuccess = false;
if (!IsInCache(key))
{
var cancellationTokenSource = new CancellationTokenSource(
TimeSpan.FromSeconds(expriryTimeInSeconds));
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(DateTimeOffset.Now.AddSeconds(expriryTimeInSeconds))
.SetSize(1)
.AddExpirationToken(
new CancellationChangeToken(cancellationTokenSource.Token));
_memoryCache.Set<T>(key, value, cacheEntryOptions);
isSuccess = true;
}
return isSuccess;
}
public T GetFromCache<T>(string key)
{
return _memoryCache.Get<T>(key);
}
public bool IsInCache(string key)
{
var item = _memoryCache.Get(key);
return item != null;
}
}
}
属性本身
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Net;
namespace MyWebApplication
{
/// <summary>
/// Decorates any MVC route that needs to have client requests limited by time.
/// Based on how they throttle at stack overflow (updated for .NET5+)
/// https://stackoverflow.com/questions/33969/best-way-to-implement-request-throttling-in-asp-net-mvc/1318059#1318059
/// </summary>
/// <remarks>
/// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ThrottleByIPAddressAttribute : ActionFilterAttribute
{
/// <summary>
/// The caching class (which will be instantiated as a singleton)
/// </summary>
private IThrottleCache _throttleCache;
/// <summary>
/// A unique name for this Throttle.
/// </summary>
/// <remarks>
/// We'll be inserting a Cache record based on this name and client IP, e.g. "Name-192.168.0.1"
/// </remarks>
public string Name { get; set; }
/// <summary>
/// The number of seconds clients must wait before executing this decorated route again.
/// </summary>
public int Seconds { get; set; }
/// <summary>
/// A text message that will be sent to the client upon throttling. You can include the token {n} to
/// show this.Seconds in the message, e.g. "Wait {n} seconds before trying again".
/// </summary>
public string Message { get; set; } = "You may only perform this action every {n} seconds.";
public override void OnActionExecuting(ActionExecutingContext c)
{
if(_throttleCache == null)
{
var cache = c.HttpContext.RequestServices.GetService(typeof(IThrottleCache));
_throttleCache = (IThrottleCache)cache;
}
var key = string.Concat(Name, "-", c.HttpContext.Request.HttpContext.Connection.RemoteIpAddress);
var allowExecute = _throttleCache.AddToCache(key, Seconds);
if (!allowExecute)
{
if (String.IsNullOrEmpty(Message))
Message = "You may only perform this action every {n} seconds.";
c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
// see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
}
}
}
}
Startup.cs或Program.cs -将服务注册到DI
这个例子使用了Startup.cs/ConfigureServices -把代码放在AddControllersWithViews之后的某个地方)。
对于在。net 6+中创建的项目,我认为您应该在builder.Services.AddRazorPages();and var app = builder.Build();在program.cs。services将是builder.Services。
如果您没有正确地放置这些代码,那么每次检查缓存时缓存都会为空。
// The cache for throttling must be a singleton and requires IMemoryCache to be set up.
// Place it after AddControllersWithViews or AddRazorPages as they build a cache themselves
// Need this for IThrottleCache to work.
services.AddMemoryCache(_ => new MemoryCacheOptions
{
SizeLimit = 1024, /* TODO: CHECK THIS IS THIS THE RIGHT SIZE FOR YOU! */
CompactionPercentage = .3,
ExpirationScanFrequency = TimeSpan.FromSeconds(30),
});
services.AddSingleton<IThrottleCache, ThrottleCache>();
示例使用
[HttpGet, Route("GetTest")]
[ThrottleByIPAddress(Name = "MyControllerGetTest", Seconds = 5)]
public async Task<ActionResult<string>> GetTest()
{
return "Hello world";
}
为了帮助理解。net 5+中的缓存,我还做了一个缓存控制台演示。
创建ThrottlingTroll -我对ASP中的节流/速率限制的看法。净的核心。
它类似于Stefan Prodan的AspNetCoreRateLimit和ASP。NET 7的速率限制中间件,但是它有以下优点:
入口和出口节流(出口意味着你特别配置的HttpClient每秒不会发出超过N个请求,而是会自己产生429个状态码)。 分布式价格计数器商店(包括但不限于Redis)。 动态(重新)配置-允许调整限制而不重新启动服务。 从出口传播到入口的429个状态。
在回购中查看更多信息。
下面是我们过去一年在Stack Overflow上使用的一个通用版本:
/// <summary>
/// Decorates any MVC route that needs to have client requests limited by time.
/// </summary>
/// <remarks>
/// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ThrottleAttribute : ActionFilterAttribute
{
/// <summary>
/// A unique name for this Throttle.
/// </summary>
/// <remarks>
/// We'll be inserting a Cache record based on this name and client IP, e.g. "Name-192.168.0.1"
/// </remarks>
public string Name { get; set; }
/// <summary>
/// The number of seconds clients must wait before executing this decorated route again.
/// </summary>
public int Seconds { get; set; }
/// <summary>
/// A text message that will be sent to the client upon throttling. You can include the token {n} to
/// show this.Seconds in the message, e.g. "Wait {n} seconds before trying again".
/// </summary>
public string Message { get; set; }
public override void OnActionExecuting(ActionExecutingContext c)
{
var key = string.Concat(Name, "-", c.HttpContext.Request.UserHostAddress);
var allowExecute = false;
if (HttpRuntime.Cache[key] == null)
{
HttpRuntime.Cache.Add(key,
true, // is this the smallest data we can have?
null, // no dependencies
DateTime.Now.AddSeconds(Seconds), // absolute expiration
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null); // no callback
allowExecute = true;
}
if (!allowExecute)
{
if (String.IsNullOrEmpty(Message))
Message = "You may only perform this action every {n} seconds.";
c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
// see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
}
}
}
示例用法:
[Throttle(Name="TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]
public ActionResult TestThrottle()
{
return Content("TestThrottle executed");
}
ASP。NET Cache在这里就像一个冠军——通过使用它,你可以自动清理你的节流条目。随着我们不断增长的流量,我们没有看到这是服务器上的一个问题。
请随意对这个方法给出反馈;当我们使Stack Overflow更好,你得到你的Ewok修复更快:)