asp.net mvc - tag - Almacenar cualquier cosa en la sesión de ASP.NET provoca retrasos de 500 ms
razor mvc (4)
Como mencionó Gats, el problema es que ASP.NET bloquea la sesión, por lo que cada solicitud para la misma sesión debe ejecutarse en serie.
La parte molesta es que si ejecutara las 5 solicitudes en serie (del ejemplo), tomaría ~ 40ms. Con el bloqueo de ASP.NET es más de 1000 ms. Parece que ASP.NET dice: "Si la sesión está en uso, duerma 500ms e intente de nuevo".
Si usa StateServer o SqlServer en lugar de InProc, no será de ayuda; ASP.NET aún bloquea la sesión.
Hay algunas soluciones diferentes. Terminamos usando el primero.
Use cookies en su lugar
Las cookies se envían en el encabezado de cada solicitud, por lo que debe mantenerlas claras y evitar información confidencial. Dicho esto, la sesión usa cookies de manera predeterminada para recordar quién es quién al almacenar una cadena ASPNET_SessionId. Todo lo que necesitaba era esa identificación, por lo que no hay razón para soportar el bloqueo de la sesión de ASP.NET cuando solo se trata de un identificador en una cookie.
Así que evitamos la sesión por completo y almacenamos un guid en la cookie. Las cookies no se bloquean, por lo que las demoras son fijas.
Usar atributos de MVC
Puede usar la sesión, pero evite los bloqueos en ciertas solicitudes haciendo que la sesión sea de solo lectura.
Para aplicaciones MVC 3, use este atributo en su controlador para hacer que la sesión sea de solo lectura (no funciona en una acción específica):
[SessionState(SessionStateBehavior.ReadOnly)]
Deshabilitar sesión para algunas rutas
También puede desactivar la sesión a través del enrutamiento MVC , pero es un poco complicado.
Deshabilitar sesión en una página específica
Para WebForms, puede desactivar la sesión para ciertas páginas aspx .
El problema:
Cuando utiliza sesión en un sitio ASP.NET, causa demoras dramáticas (múltiplos de 500 ms) al cargar varias solicitudes casi al mismo tiempo.
Más específicamente, mi problema
Nuestro sitio web utiliza sesión exclusivamente para su SessionId. Usamos esta clave para buscar una tabla db que tenga información de usuario, etc. Este parecía ser un buen diseño ya que almacenaba datos de sesión mínimos. Desafortunadamente, SessionId cambiará si no almacena nada en la sesión, entonces almacenamos Session["KeepId"] = 1;
. Esto es suficiente para convencer a SessionId de no cambiar .
En una nota aparentemente no relacionada, el sitio sirve imágenes de productos personalizados a través de una acción de controlador. Esta acción genera una imagen si es necesario y luego redirige a la imagen en caché. Esto significa que una página web puede contener imágenes u otros recursos, enviando docenas de solicitudes a través de la canalización de ASP.NET.
Por alguna razón, cuando (a) envía múltiples solicitudes al mismo tiempo y (b) tiene una sesión involucrada de alguna manera, entonces terminará con retrasos aleatorios de 500 ms aproximadamente la mitad del tiempo. En algunos casos, estos retrasos serán de 1000 ms o más, siempre aumentando en intervalos de ~ 500 ms. Más solicitudes significa tiempos de espera más largos. Nuestra página con docenas de imágenes puede esperar más de 10 segundos para algunas imágenes.
Cómo reproducir el problema:
- Crear una aplicación web ASP.NET MVC vacía
Crear una acción de controlador vacía:
public class HomeController : Controller { public ActionResult Test() { return new EmptyResult(); } }
Cree una página test.html con algunas etiquetas img que lleven a cabo esa acción:
<img src="Home/Test?1" /> <img src="Home/Test?2" /> <img src="Home/Test?3" /> <img src="Home/Test?4" />
Ejecute la página y mire los tiempos de carga rápida en Firebug:
Haga uno de los siguientes (ambos tienen el mismo resultado):
Agregue un controlador Session_Start vacío a su Global.asax.cs
public void Session_Start() { }
Pon algo en sesión
public class HomeController : Controller { public ActionResult Test() { HttpContext.Current.Session["Test"] = 1; return new EmptyResult(); } }
Ejecute la página nuevamente y observe las demoras ocasionales / aleatorias en las respuestas.
Lo que sé hasta ahora
- Sucede con MVC 2 y 3 y WebForms
- Sucede con cualquier navegador (probamos FF, Chrome, IE, Safari)
- Sucede independientemente de si el depurador está conectado. Además, nada tiene que almacenarse realmente en sesión (ver el ejemplo de Session_Start más arriba) .
- Sucede con WebDev.exe, IIS Express e IIS
Mi pregunta
¿Cómo puedo usar la sesión pero evitar estas demoras? ¿Alguien sabe de una solución?
Si no hay una solución, creo que tendremos que eludir la sesión y usar cookies directamente (la sesión usa cookies ).
Eché un vistazo al marco ASP.NET. La clase donde se gestiona el estado de la sesión se define aquí: http://referencesource.microsoft.com/#System.Web/State/SessionStateModule.cs,114
Así es como funciona (código simplificado):
LOCKED_ITEM_POLLING_INTERVAL = 500ms;
LOCKED_ITEM_POLLING_DELTA = 250ms;
bool GetSessionStateItem() {
item = sessionStore.GetItem(out locked);
if (item == null && locked) {
PollLockedSession();
return false;
}
return true;
}
void PollLockedSession() {
if (timer == null) {
timer = CreateTimer(PollLockedSessionCallback, LOCKED_ITEM_POLLING_INTERVAL);
}
}
void PollLockedSessionCallback() {
if (DateTime.UtcNow - lastPollCompleted >= LOCKED_ITEM_POLLING_DELTA) {
isCompleted = GetSessionStateItem();
lastPollCompleted = DateTime.UtcNow;
if(isCompleted) {
ResetPollTimer();
}
}
}
Resumen: si el elemento de la sesión no se puede recuperar porque está bloqueado por otro hilo, se creará un temporizador. Regularmente agrupará la sesión para intentar recuperar el elemento nuevamente (cada 500 ms de manera predeterminada). Una vez que el artículo se ha recuperado con éxito, el temporizador se borra. Además, hay una verificación para asegurarse de que haya un retraso determinado entre las llamadas a GetSessionStateItem () ( LOCKED_ITEM_POLLING_DELTA
= 250 ms de manera predeterminada).
Es posible cambiar el valor predeterminado de LOCKED_ITEM_POLLING_INTERVAL
creando la siguiente clave en el registro (esto afectará a todos los sitios web que se ejecutan en la máquina):
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/ASP.NET
SessionStateLockedItemPollInterval (this is a DWORD)
Otra forma (que es un truco) es cambiar el valor por reflexión:
Type type = typeof(SessionStateModule);
FieldInfo fieldInfo = type.GetField("LOCKED_ITEM_POLLING_INTERVAL",
BindingFlags.NonPublic | BindingFlags.Static);
fieldInfo.SetValue(null, 100); //100ms
DESCARGO DE RESPONSABILIDAD: las consecuencias exactas de bajar este valor son desconocidas. Puede aumentar la contención de subprocesos en el servidor, crear posibles interbloqueos, etc. Una mejor solución es evitar el uso de estado de sesión o decorar su controlador con el atributo SessionStateBehavior.ReadOnly
como otros usuarios sugirieron.
Hay algunas formas de acelerar la sesión en ASP.NET. En primer lugar, algunos puntos clave:
La sesión es segura para subprocesos, lo que significa que no funciona bien con las solicitudes de clientes simultáneos que necesitan acceder a ella. Si necesita procesos poco sincronizados para acceder a los datos de tipo sesión, puede usar alguna otra forma de almacenamiento temporal como el caché o su base de datos (para el seguimiento del progreso asíncrono esto es necesario). De lo contrario, la información de la sesión de los usuarios se bloqueará en el contexto de la solicitud para evitar problemas de alteración. Por lo tanto, por qué la solicitud 1 está bien, entonces las siguientes solicitudes tienen un retraso. PS esto es absolutamente necesario para garantizar la integridad de los datos en sesión ... como pedidos de usuarios en comercio electrónico.
La sesión se serializa dentro y fuera del servidor. Una cookie se usa para mantener la sesión, aunque es un tipo diferente de cookie, lo que significa que no esperaría mucha diferencia en el rendimiento.
Entonces, mi forma favorita de acelerar las solicitudes de sesión es usar el servidor de estado. Elimina los problemas en proceso y también significa que al probar puede reconstruir su proyecto sin tener que iniciar sesión de nuevo en su sitio. También hace que las sesiones sean posibles en escenarios simples de equilibrio de carga. http://msdn.microsoft.com/en-us/library/ms972429.aspx
PD: Debo agregar que se supone que el servicio de estado técnicamente fuera de proceso es más lento. Según mi experiencia, es más rápido y fácil de desarrollar, pero supongo que eso depende de cómo se use.
Solo quería agregar un pequeño detalle a la gran respuesta de @bendytree. Al usar WebForms, no solo puede deshabilitar completamente el estado de la sesión
<%@ Page EnableSessionState="False" %>
pero también configúralo para solo leer:
<%@ Page EnableSessionState="ReadOnly" %>
Esto resolvió el problema descrito en mi caso. Ver documentation