Модуль IIS для блокировки интенсивного трафика сайта - PullRequest
0 голосов
/ 02 января 2009

Вопрос

Привет всем,

Немного предыстории по моей проблеме ... В настоящее время у меня есть сайт, созданный для интернет-провайдера, для которого я работаю, который отображает сообщения для пользователей на основе их статуса выставления счетов. Когда они не платят, я отображаю сообщение неоплаты, а если они злоупотребляют, я показываю сообщение о злоупотреблении и т. Д. Трафик генерируется Cisco SCE, который перенаправляет трафик HTTP конечного пользователя на мой сайт.

Проблема, которую я вижу, - это чрезмерный трафик. Я полагаю, что трафик может быть трафиком P2P, автоматическими обновлениями или чем-то еще в этом роде. В основном все, что использует порт 80, перенаправляется SCE на мою страницу.

Решение, которое я пытаюсь применить на своем сервере, состоит в том, чтобы установить модуль, который блокирует пользователей на основе их количества обращений. Таким образом, если они превышают пороговое значение в течение определенного промежутка времени, они будут перенаправлены на другую страницу, которая, как мы надеемся, снимет нагрузку с процессора, поскольку ему не придется выполнять все операции поиска и анализа SQL, которые происходят в Страница ASP.NET.

Однако, когда я пытаюсь применить модуль, который я построил, он на самом деле дает противоположный результат (увеличивает нагрузку на процессор). Модуль использует таблицу в памяти, которая хранится в Application State и используется для отслеживания запросов по IP. Вот код для модуля:

public class IpHitCount : IHttpModule
{
    const string tableKey = "appIpLog";

    #region IHttpModule Members

    public void Dispose()
    {

    }

    public void Init(HttpApplication context)
    {
        context.PreRequestHandlerExecute += new EventHandler(checkHitCount);
    }

    #endregion

    private void checkHitCount(object sender, EventArgs e)
    {
        // Cast the parameter into a HttpApp object
        HttpApplication app = (HttpApplication)sender;

        // make sure that this is the user's first request for the app
        // (all first requests are routed through main)
        if (app.Request.Url.AbsolutePath.ToLower().Contains("main.aspx"))
        {
            // If the in memory table does not exist, then create it
            if (app.Application[tableKey] == null)
            {
                app.Application[tableKey] = CreateTable();
            }

            DataSet ds = (DataSet)app.Application[tableKey];
            DataTable tbl = ds.Tables["IpTable"];
            DeleteOldEntries(tbl);

            string filter = string.Format("ip = '{0}'", app.Request.UserHostAddress);
            DataRow[] matchedRows = tbl.Select(filter);

            if (matchedRows.Length > 0)
            {
                DataRow matchedRow = matchedRows[0];
                if ((int)matchedRow["hitCount"] > 4)
                {
                    app.Response.Redirect("HitCountExceeded.htm", true);
                }
                else
                {
                    matchedRow["hitCount"] = (int)matchedRow["hitCount"] + 1;
                }
            }
            else
            {
                DataRow newEntry = tbl.NewRow();
                newEntry["timestamp"] = DateTime.Now;
                newEntry["hitCount"] = 1;
                newEntry["ip"] = app.Request.UserHostAddress;
                tbl.Rows.Add(newEntry);
            }                
        }
    }

    private DataSet CreateTable()
    {
        DataSet ds = new DataSet();
        DataTable table = new DataTable("IpTable");

        DataColumn col1 = new DataColumn("timestamp", typeof(DateTime));
        col1.AutoIncrement = false;
        col1.DefaultValue = DateTime.Now;
        col1.ReadOnly = false;
        col1.Unique = false;

        DataColumn col2 = new DataColumn("ip", typeof(string));
        col1.AutoIncrement = false;
        col1.ReadOnly = false;  
        col1.Unique = false;

        DataColumn col3 = new DataColumn("hitCount", typeof(int));
        col1.AutoIncrement = false;
        col1.ReadOnly = false;
        col1.Unique = false;

        table.Columns.Add(col1);
        table.Columns.Add(col2);
        table.Columns.Add(col3);

        ds.Tables.Add(table);

        return ds;
    }

    private void DeleteOldEntries(DataTable tbl)
    {
        // build the where clause
        string filter = "timestamp < '" + DateTime.Now.AddMinutes(-5.0).ToString() + "'";

        // run the query against the table
        DataRow[] rowsToDelete = tbl.Select(filter);

        // individually delete each row returned
        foreach (DataRow row in rowsToDelete)
        {
            row.Delete();
        }
    }
}

Так что мне интересно следующее: есть ли что-то, что вы видите, что я делаю неправильно в модуле, что может быть причиной высокой загрузки ЦП? Есть ли альтернативный способ блокировать этот трафик?

Любая помощь, которую вы можете оказать, будет принята с благодарностью.

Спасибо, C


Решение

Я изменил код в модуле, чтобы запускать раздел удаления только раз в 1 минуту:


    if (app.Application[deletedKey] == null)
    app.Application[deletedKey] = DateTime.Now;

    DateTime deletedDate = (DateTime)app.Application[deletedKey];

    if (DateTime.Now >= deletedDate.AddMinutes(1))
    {
        DeleteOldEntries(tbl);
        app.Application[deletedKey] = DateTime.Now;
    }

Я также добавил некоторый код, который, по моему мнению, индексирует столбец IP моего набора данных. Хотя это не кажется правильным, поэтому я не уверен, что он делает то, что я собираюсь сделать:


    DataColumn[] key = new DataColumn[1];
    key[0] = col1;

    table.PrimaryKey = key;

    ds.Tables.Add(table);

После внесения двух вышеперечисленных изменений загрузка ЦП, похоже, резко снизилась. Я полагаю, что наш SQL-сервер теперь также благодарит Бога за то, что он, наконец, может дышать.

Спасибо за помощь !!

Ответы [ 2 ]

2 голосов
/ 02 января 2009

Что ж, вы должны помнить, что DataSet будет в памяти, а для поиска по DataSet потребуется много циклов ЦП, чтобы найти записи, которые вы ищете.

Добавьте к этому тот факт, что, поскольку это веб-приложение, вы получите много обращений, поэтому в конечном итоге вы будете вызывать эту процедуру очень и очень часто.

Я бы порекомендовал сохранить количество обращений на сервере базы данных, а затем обновить и запросить сервер, чтобы определить, превышено ли число обращений. Он сможет обрабатывать нагрузку, а также обрабатывать размер набора данных, который вы собираетесь запросить.

1 голос
/ 02 января 2009

Я бы попробовал пару вещей:

  • Первое, что я вижу, это то, что вы вызываете подпрограмму «DeleteOldEntries» каждый раз, когда запускается этот код, что заставляет его сканировать всю DataTable на каждом проходе. Есть ли другой способ ограничить это выполнение только в определенное время? Если не таймер, который запускает его каждые 15 секунд, то, может быть, вторая переменная в состоянии (например, «ExecCount»), которая увеличивается каждый раз при запуске «CheckHitCount», так что вы выполняете очистку только каждые 10 или 20 раз? Таким образом, вы можете избежать этой потенциально дорогой части кода при каждом запуске.
  • Другой вариант - добавить индекс в таблицу данных. Я не уверен, как .NET обрабатывает поиски в DataTables, но, возможно, это вас заинтересует: Статья MSDN

Можете ли вы использовать что-то вроде ANTS Profiler, чтобы увидеть, где больше всего времени тратится во время выполнения? Поскольку я полагаю, что эта страница вызывается много, много раз в секунду, любой способ уменьшить влияние, даже небольшое, будет иметь большое значение.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...