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

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

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

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

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


我们使用从这个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毫秒,然后请求将再次被允许。


下面是我们过去一年在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修复更快:)


我花了一些时间为。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+中的缓存,我还做了一个缓存控制台演示。


由于这个问题的高投票的答案太老了,我分享了对我有效的最新解决方案。

我尝试使用在本页上的答案中给出的动态IP限制,但当我尝试使用该扩展时,我发现该扩展已被微软停止,在下载页面上他们已经清楚地写了下面的消息。

Microsoft has discontinued the Dynamic IP Restrictions extension and this download is no longer available.

因此,我进一步研究发现,iis8.0及以上版本默认包含动态IP限制。下面的信息是从微软动态IP限制页面获取的。

在IIS 8.0中,微软扩展了内置功能,包括以下几个新特性:

Dynamic IP address filtering, which allows administrators to configure their server to block access for IP addresses that exceed the specified number of requests. The IP address filtering features now allow administrators to specify the behavior when IIS blocks an IP address, so requests from malicious clients can be aborted by the server instead of returning HTTP 403.6 responses to the client. IP filtering now feature a proxy mode, which allows IP addresses to be blocked not only by the client IP that is seen by IIS but also by the values that are received in the x-forwarded-for HTTP header

有关实施动态IP限制的分步说明,请访问以下链接:

https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-dynamic-ip-address-restrictions

我希望它能帮助那些陷入类似问题的人。


创建ThrottlingTroll -我对ASP中的节流/速率限制的看法。净的核心。

它类似于Stefan Prodan的AspNetCoreRateLimit和ASP。NET 7的速率限制中间件,但是它有以下优点:

入口和出口节流(出口意味着你特别配置的HttpClient每秒不会发出超过N个请求,而是会自己产生429个状态码)。 分布式价格计数器商店(包括但不限于Redis)。 动态(重新)配置-允许调整限制而不重新启动服务。 从出口传播到入口的429个状态。

在回购中查看更多信息。