我们正在尝试各种方法来限制用户在特定时间段内的行为:

限制问答帖子 限制编辑 限制提要检索

目前,我们使用缓存只是简单地插入用户活动的记录——如果该记录存在,并且/当用户执行相同的活动时,我们就会节流。

自动使用缓存给我们提供了陈旧的数据清洗和滑动用户活动窗口,但它将如何扩展可能是一个问题。

还有什么其他方法可以确保请求/用户操作可以被有效地控制(强调稳定性)?


当前回答

微软为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修复更快:)