Pregunta

Pregunta

Hola a todos,

Un poco de historia sobre mi problema ... Actualmente tengo un sitio creado para el ISP para el que trabajo, que muestra mensajes a los usuarios según su estado de facturación. Cuando están en Non-Pay, muestro un mensaje de Non-Pay y si están en Abuse, muestro un mensaje de abuso, etc. El Cisco SCE genera el tráfico que redirige el tráfico HTTP del usuario final a mi sitio.

El problema que estoy viendo es el tráfico excesivo. Creo que el tráfico puede ser tráfico P2P, actualizaciones automáticas o cualquier otra cosa del tipo. Básicamente, todo lo que usa el puerto 80 es redirigido por el SCE a mi página.

La solución que estoy tratando de implementar en mi servidor es colocar un módulo que bloquee a los usuarios en función de su número de visitas. Por lo tanto, si superan un umbral durante un cierto período de tiempo, serán redirigidos a otra página que, con suerte, reducirá la carga del procesador, ya que no tendrá que realizar todas las búsquedas de SQL y la inteligencia que tiene lugar en el Página ASP.NET.

Sin embargo, cuando trato de aplicar un módulo que construí, en realidad tiene el resultado opuesto (aumenta la carga de la CPU). El módulo utiliza una tabla en memoria que se almacena en el estado de la aplicación que utiliza para rastrear las solicitudes por IP. Aquí está el código para el módulo:

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();
        }
    }
}

Entonces, lo que me pregunto es lo siguiente: ¿Hay algo que pueda ver que estoy haciendo mal en el módulo, lo que podría estar causando la alta utilización de la CPU? ¿Hay alguna forma alternativa de bloquear este tráfico?

Cualquier ayuda que pueda proporcionar sería muy apreciada.

Gracias, C


Solución

He cambiado el código en el módulo para ejecutar solo la sección de eliminación cada 1 minuto:


    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;
    }

También agregué algo de código que creo que indexa la columna IP de mi conjunto de datos. Sin embargo, no parece correcto, así que no estoy seguro de que esté haciendo lo que pretendo que haga:


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

    table.PrimaryKey = key;

    ds.Tables.Add(table);

Después de realizar los dos cambios anteriores, la carga de la CPU parece haber disminuido drásticamente. Me imagino que nuestro servidor SQL también está agradeciendo a Dios ahora que finalmente puede respirar.

¡Gracias por toda la ayuda!

¿Fue útil?

Solución

Hay un par de cosas que intentaría:

  • Lo primero que veo es que estás llamando a " DeleteOldEntries " sub cada vez que se ejecuta este código, lo que hace que realice un escaneo a través de toda la tabla de datos en cada paso. ¿Hay alguna otra manera de limitar esto a correr solo en ciertos momentos? Si no es un temporizador que lo ejecuta cada 15 segundos, tal vez una segunda variable en el estado (como " ExecCount ") que se incremente cada vez que " CheckHitCount " se ejecuta, de modo que solo se purga cada 10 o 20 veces? De esta manera, puede evitar esta sección potencialmente costosa del código en cada ejecución.
  • Otra opción es agregar un índice a su DataTable. No estoy seguro de cómo .NET maneja las búsquedas en DataTables, pero quizás esto sea de su interés: Artículo de MSDN

¿Puedes usar algo como ANTS Profiler para ver dónde se gasta más tiempo durante la ejecución? Como me imagino que esta página se llama muchas, muchas veces por segundo, de cualquier manera puede reducir el impacto, aunque sea un poco, haría una gran diferencia.

Si obtiene algunos resultados pero aún no está satisfecho, asegúrese de modificar su pregunta para agregar la nueva información para que podamos seguir trabajando hacia una solución con la que esté satisfecho.

Otros consejos

Bueno, debe recordar que el DataSet estará en la memoria, y para buscar en el DataSet, se necesitarán muchos ciclos de CPU para encontrar los registros que está buscando.

Agregue a eso el hecho de que, dado que se trata de una aplicación web, va a recibir muchos hits, por lo que terminará llamando a esta rutina muy, muy a menudo.

Mi recomendación sería almacenar los recuentos de visitas en un servidor de base de datos y luego actualizar y consultar al servidor para ver si se supera la cantidad de visitas. Podrá manejar la carga, así como el tamaño del conjunto de datos que va a consultar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top