Лучший способ реализовать регулирование запросов в ASP.NET MVC?

Мы экспериментируем с различными способами регулирования действий пользователя в данный период времени:

  • Ограничить вопрос / ответ постов
  • Ограничение правок
  • Ограничить поиск каналов

В настоящее время мы используем Cache для простой вставки записи активности пользователя - если эта запись существует, если / когда пользователь выполняет ту же самую активность, мы ограничиваемся.

Использование кэша автоматически дает нам устаревшую очистку данных и скользящие окна активности пользователей, но проблема масштабирования может быть проблемой.

Каковы другие способы обеспечения эффективного регулирования запросов / действий пользователя (акцент на стабильность)?

6 ответов

Решение

Вот общая версия того, что мы использовали в переполнении стека в прошлом году:

/// <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 еще быстрее:)

У Microsoft есть новое расширение для IIS 7, которое называется "Динамическое расширение ограничений IP для IIS 7.0 - бета-версия".

"Динамические ограничения IP для IIS 7.0 - это модуль, который обеспечивает защиту от атак типа" отказ в обслуживании "и атак" грубой силы "на веб-сервер и веб-сайты. Такая защита обеспечивается путем временной блокировки IP-адресов клиентов HTTP, которые выполняют необычно большое количество одновременных запросов или которые делают большое количество запросов в течение небольшого периода времени. " http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/

Пример:

Если вы установите критерии для блокировки после X requests in Y milliseconds или же X concurrent connections in Y milliseconds IP-адрес будет заблокирован для Y milliseconds тогда запросы будут снова разрешены.

Мы используем технику, заимствованную из этого URL http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx, не для регулирования, а для отказа в обслуживании бедного человека (DOS). Это также основано на кеше и может быть похоже на то, что вы делаете. Вы душите, чтобы предотвратить атаки DOS? Маршрутизаторы, безусловно, могут быть использованы для уменьшения DOS; Как вы думаете, маршрутизатор может справиться с регулированием, которое вам нужно?

Мне потребовалось некоторое время, чтобы разработать эквивалент для .NET 5+ (ранее .NET Core), так что вот отправная точка.

Старый способ кэширования ушел и был заменен Microsoft.Extensions.Caching.Memoryс IMemoryCache .

Я еще немного выделил, так что вот что вам нужно...

Класс управления кэшем

Я добавил все это здесь, чтобы вы могли видеть операторы использования.

      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://docs.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).

Для проекта, созданного в .NET6+, я думаю, вы бы добавили эквивалент между builder.Services.AddRazorPages();а также var app = builder.Build();в программе.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.NET Core.

Он похож на AspNetCoreRateLimit Стефана Продана ипромежуточное ПО для ограничения скорости ASP.NET 7 , но имеет преимущества:

  • Регулирование как входящего, так и исходящего трафика ( выход означает, что ваш специально настроенный HttpClient не будет выполнять более N запросов в секунду, а вместо этого будет сам создавать код состояния 429).
  • Распределенные хранилища счетчиков ставок (включая, помимо прочего, Redis).
  • Динамическая (ре)конфигурация — позволяет настраивать лимиты без перезапуска службы.
  • Распространение 429 статусов от выхода к входу.

Узнайте больше в репозитории.

Поскольку ответы на этот вопрос, получившие большое количество голосов, слишком устарели, я делюсь последним решением, которое сработало для меня.

Я попытался использовать ограничения динамического IP-адреса, как указано в ответе на этой странице, но когда я попытался использовать это расширение, я обнаружил, что это расширение было прекращено Microsoft, и на странице загрузки они четко написали следующее сообщение.

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

Поэтому я провел дополнительные исследования и обнаружил, что ограничения динамического IP-адреса теперь по умолчанию включены в IIS 8.0 и более поздние версии. Приведенная ниже информация получена со страницы Microsoft Dynamic IP Restrictions.

В IIS 8.0 Microsoft расширила встроенную функциональность, включив в нее несколько новых функций:

  • Динамическая фильтрация IP-адресов, которая позволяет администраторам настроить свой сервер для блокировки доступа для IP-адресов, которые превышают указанное количество запросов.
  • Функции фильтрации IP-адресов теперь позволяют администраторам указывать поведение, когда IIS блокирует IP-адрес, поэтому запросы от вредоносных клиентов могут быть прерваны сервером вместо того, чтобы возвращать клиенту ответы HTTP 403.6.
  • IP-фильтрация теперь имеет режим прокси, который позволяет блокировать IP-адреса не только по IP-адресу клиента, который видит IIS, но и по значениям, полученным в HTTP-заголовке x-forwarded-for.

Для получения пошаговых инструкций по внедрению динамических ограничений IP-адресов перейдите по ссылке ниже:

https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-динамический-IP-адрес-ограничения

Я надеюсь, что это поможет кому-то, застрявшему в подобной проблеме.

Другие вопросы по тегам