Ограничение количества звонков в день в ASP.NET MVC 3?

Я видел решение Джаррода Диксона (Лучший способ реализовать регулирование запросов в ASP.NET MVC?) для реализации ограничения скорости вызовов в секунду. Сейчас я пытаюсь понять, как построить аналогичный фильтр для N звонков в день.

Я создаю API для разработчиков, где бесплатные учетные записи получают ~ 100 звонков в день, а платные учетные записи получают более высокий предел скорости. Как лучше всего ограничить количество звонков в день в MVC 3?


person Wesley Tansey    schedule 12.04.2011    source источник


Ответы (1)


Я не думаю, что здесь будет достаточно структуры в памяти из-за большой продолжительности, которую вам нужно измерить. В этом случае переработка IIS будет проблематичной. Таким образом, я бы рекомендовал записывать доступ пользователей к ресурсу в БД и разрешать счет только до 100 за последние 24 часа.

С другой стороны, вот наша реализация ограничителя дырявого ведра (который более удобен для краткосрочного ограничения, когда отказ относительно не важен). Использование параллельных коллекций .NET 4 может улучшить блокировку грубой силы в этой реализации:

public class RateLimiter
{
    private readonly double numItems;
    private readonly double ratePerSecond;
    private readonly Dictionary<object, RateInfo> rateTable = 
        new Dictionary<object, RateInfo>();
    private readonly object rateTableLock = new object();
    private readonly double timePeriod;

    public RateLimiter(double numItems, double timePeriod)
    {
        this.timePeriod = timePeriod;
        this.numItems = numItems;
        ratePerSecond = numItems / timePeriod;
    }

    public double Count
    {
        get
        {
            return numItems;
        }
    }

    public double Per
    {
        get
        {
            return timePeriod;
        }
    }

    public bool IsPermitted(object key)
    {
        RateInfo rateInfo;
        var permitted = true;
        var now = DateTime.UtcNow;
        lock (rateTableLock)
        {
            var expiredKeys = 
                rateTable
                .Where(kvp => 
                    (now - kvp.Value.LastCheckTime) 
                    > TimeSpan.FromSeconds(timePeriod))
                .Select(k => k.Key)
                .ToArray();
            foreach (var expiredKey in expiredKeys)
            {
                rateTable.Remove(expiredKey);
            }
            var dataExists = rateTable.TryGetValue(key,
                                                   out rateInfo);
            if (dataExists)
            {
                var timePassedSeconds = (now - rateInfo.LastCheckTime).TotalSeconds;
                var newAllowance = 
                     Math.Min(
                         rateInfo.Allowance 
                         + timePassedSeconds 
                         * ratePerSecond,
                         numItems);
                if (newAllowance < 1d)
                {
                    permitted = false;
                }
                else
                {
                    newAllowance -= 1d;
                }
                rateTable[key] = new RateInfo(now,
                                              newAllowance);
            }
            else
            {
                rateTable.Add(key,
                              new RateInfo(now,
                                           numItems - 1d));
            }

        }
        return permitted;
    }

    public void Reset(object key)
    {
        lock (rateTableLock)
        {
            rateTable.Remove(key);
        }
    }

    private struct RateInfo
    {
        private readonly double allowance;
        private readonly DateTime lastCheckTime;

        public RateInfo(DateTime lastCheckTime, double allowance)
        {
            this.lastCheckTime = lastCheckTime;
            this.allowance = allowance;
        }

        public DateTime LastCheckTime
        {
            get
            {
                return lastCheckTime;
            }
        }

        public double Allowance
        {
            get
            {
                return allowance;
            }
        }
    }

}
person spender    schedule 12.04.2011
comment
Я бы настоятельно не советовал объявлять (и инициализировать now) вне оператора блокировки. Хотя это маловероятно для вашего приложения, если вызывающий поток будет заблокирован на долгое время оператором lock, вы можете получить результаты, отличные от ожидаемых при вызове IsPermitted. - person Mike Bailey; 17.12.2012