Problemas de rendimiento al realizar llamadas a servicios web desde una aplicación ASP.NET

En este artículo se proporciona ayuda para resolver los problemas de rendimiento que se producen al realizar llamadas a servicios web desde una aplicación de Microsoft ASP.NET.

              Versión original del producto: ASP.NET
Número de KB original: 821268

Síntomas

Al realizar llamadas a servicios web desde una aplicación de ASP.NET, puede experimentar contención, rendimiento deficiente e interbloqueos. Los clientes pueden informar de que las solicitudes dejan de responder o tardan mucho tiempo en ejecutarse. Si se sospecha que hay un interbloqueo, se puede reciclar el proceso de trabajo.

Puede recibir el siguiente mensaje de error de excepción al realizar una llamada al HttpWebRequest.GetResponse método :

"System.InvalidOperationException: No había suficientes subprocesos libres en el objeto ThreadPool para completar la operación".

También puede recibir el siguiente mensaje de error de excepción en el explorador:

"HttpException (0x80004005): se agotó el tiempo de espera de la solicitud".

Nota:

Este artículo también se aplica a las aplicaciones que realizan HttpWebRequest solicitudes directamente.

Causa

Este problema puede producirse porque ASP.NET limita el número de subprocesos de trabajo y subprocesos de puerto de finalización que una llamada puede usar para ejecutar solicitudes.

Normalmente, una llamada a un servicio web usa un subproceso de trabajo para ejecutar el código que envía la solicitud y un subproceso de puerto de finalización para recibir la devolución de llamada del servicio web. Sin embargo, si la solicitud se redirige o requiere autenticación, la llamada puede usar hasta dos subprocesos de trabajo y dos subprocesos de puerto de finalización. Por lo tanto, puede agotar el administrado ThreadPool cuando se producen varias llamadas al servicio web al mismo tiempo.

Por ejemplo, supongamos que está ThreadPool limitado a 10 subprocesos de trabajo y que los 10 subprocesos de trabajo están ejecutando código que está esperando a que se ejecute una devolución de llamada. La devolución de llamada nunca se puede ejecutar, porque los elementos de trabajo que se ponen en cola en se ThreadPool bloquean hasta que un subproceso esté disponible.

Otro posible origen de contención es el maxconnection parámetro que usa el System.Net espacio de nombres para limitar el número de conexiones. Por lo general, este límite funciona según lo esperado. Sin embargo, si muchas aplicaciones intentan realizar muchas solicitudes a una sola dirección IP al mismo tiempo, es posible que los subprocesos tengan que esperar una conexión disponible.

Solución

Para resolver estos problemas, puede ajustar los siguientes parámetros en el archivo Machine.config para ajustarlo mejor a su situación:

  • maxWorkerThreads
  • minWorkerThreads
  • maxIoThreads
  • minFreeThreads
  • minLocalRequestFreeThreads
  • maxconnection
  • executionTimeout

Para resolver correctamente estos problemas, realice las siguientes acciones:

  • Limite el número de solicitudes de ASP.NET que se pueden ejecutar al mismo tiempo a aproximadamente 12 por CPU.
  • Permitir que las devoluciones de llamada del servicio web usen libremente subprocesos en ThreadPool.
  • Seleccione un valor adecuado para el maxconnections parámetro . Base la selección en el número de direcciones IP y AppDomains que se usan.

Nota:

La recomendación de limitar el número de solicitudes de ASP.NET a 12 por CPU es un poco arbitraria. Sin embargo, este límite ha demostrado funcionar bien para la mayoría de las aplicaciones.

MaxWorkerThreads y maxIoThreads

ASP.NET usa los dos valores de configuración siguientes para limitar el número máximo de subprocesos de trabajo y subprocesos de finalización que se usan:

<processModel maxWorkerThreads="20" maxIoThreads="20">

El maxWorkerThreads parámetro y el maxIoThreads parámetro se multiplican implícitamente por el número de CPU. Por ejemplo, si tiene dos procesadores, el número máximo de subprocesos de trabajo es 2 * maxWorkerThreads.

MinFreeThreads y minLocalRequestFreeThreads

ASP.NET también contiene los siguientes valores de configuración que determinan cuántos subprocesos de trabajo y subprocesos de puerto de finalización deben estar disponibles para iniciar una solicitud remota o una solicitud local:

<httpRuntime minFreeThreads="8" minLocalRequestFreeThreads="8">

Si no hay suficientes subprocesos disponibles, la solicitud se pone en cola hasta que haya suficientes subprocesos libres para realizar la solicitud. Por lo tanto, ASP.NET no se ejecutará más que el siguiente número de solicitudes al mismo tiempo:

(maxWorkerThreads * número de CPU) - minFreeThreads

Nota:

El minFreeThreads parámetro y el minLocalRequestFreeThreads parámetro no se multiplican implícitamente por el número de CPU.

MinWorkerThreads

ASP.NET también contiene la siguiente configuración que determina cuántos subprocesos de trabajo pueden estar disponibles inmediatamente para atender una solicitud remota.

<processModel minWorkerThreads="1">

Los subprocesos controlados por esta configuración se pueden crear a una velocidad mucho más rápida que los subprocesos de trabajo creados a partir de las funcionalidades predeterminadas de optimización de subprocesos de Common Language Runtime (CLR).

Esta configuración permite ASP.NET a las solicitudes de servicio que pueden estar llenando repentinamente la cola de solicitudes de ASP.NET debido a una ralentización en un servidor back-end, una ráfaga repentina de solicitudes del extremo cliente o algo similar que provocaría un aumento repentino del número de solicitudes en la cola.

El valor predeterminado del minWorkerThreads parámetro es 1. Se recomienda establecer el valor del minWorkerThreads parámetro en el valor siguiente:

minWorkerThreads = maxWorkerThreads / 2

De forma predeterminada, el minWorkerThreads parámetro no está presente en el archivo Web.config ni en el archivo Machine.config . Esta configuración se multiplica implícitamente por el número de CPU.

Maxconnection

El maxconnection parámetro determina cuántas conexiones se pueden realizar a una dirección IP específica. El parámetro aparece como sigue:

<connectionManagement>
    <add address="*" maxconnection="2">
    <add address="http://65.53.32.230" maxconnection="12">
</connectionManagement>

Si el código de la aplicación hace referencia a la aplicación por nombre de host en lugar de por dirección IP, el parámetro debe aparecer de la siguiente manera:

<connectionManagement>
    <add address="*" maxconnection="2">
    <add address="http://hostname" maxconnection="12">
</connectionManagement>

Por último, si la aplicación se hospeda en un puerto distinto de 80, el parámetro debe incluir el puerto no estándar en la dirección URL, de forma similar a la siguiente:

<connectionManagement>
    <add address="*" maxconnection="2">
    <add address="http://hostname:8080" maxconnection="12">
</connectionManagement>

La configuración de los parámetros que se describen anteriormente en este artículo se encuentra en el nivel de proceso. Sin embargo, la configuración del maxconnection parámetro se aplica al nivel AppDomain. De forma predeterminada, dado que esta configuración se aplica al nivel AppDomain, puede crear un máximo de dos conexiones a una dirección IP específica desde cada AppDomain en el proceso.

ExecutionTimeout

ASP.NET usa la siguiente configuración para limitar el tiempo de ejecución de la solicitud:

<httpRuntime executionTimeout="90"/>

También puede establecer este límite mediante la Server.ScriptTimeout propiedad .

Nota:

Si aumenta el valor del executionTimeout parámetro, es posible que también tenga que modificar la configuración del processModelresponseDeadlockInterval parámetro.

Recomendaciones

Es posible que la configuración recomendada en esta sección no funcione para todas las aplicaciones. Sin embargo, la siguiente información adicional puede ayudarle a realizar los ajustes adecuados.

Si realiza una llamada de servicio web a una sola dirección IP desde cada página ASPX, Microsoft recomienda usar las siguientes opciones de configuración:

  • Establezca los valores del maxWorkerThreads parámetro y el maxIoThreads parámetro en 100.
  • Establezca el valor del maxconnection parámetro en 12*N (donde N es el número de CPU que tiene).
  • Establezca los valores del minFreeThreads parámetro en 88*N y el minLocalRequestFreeThreads parámetro en 76*N.
  • Establezca el valor de minWorkerThreads en 50. Recuerde que no minWorkerThreads está en el archivo de configuración de forma predeterminada. Debe agregarlo.

Algunas de estas recomendaciones implican una fórmula sencilla que implica el número de CPU en un servidor. La variable que representa el número de CPU en las fórmulas es N.

Para esta configuración, si tiene habilitado el hiperthreading, debe usar el número de CPU lógicas en lugar del número de CPU físicas. Por ejemplo, si tiene un servidor de cuatro procesadores con hyperthreading habilitado, el valor de N en las fórmulas será 8 en lugar de 4.

Nota:

Al usar esta configuración, puede ejecutar un máximo de 12 solicitudes de ASP.NET por CPU al mismo tiempo porque 100-88=12. Por lo tanto, al menos 88*N subprocesos de trabajo y 88*N subprocesos de puerto de finalización están disponibles para otros usos (por ejemplo, para las devoluciones de llamada del servicio web).

Por ejemplo, tiene un servidor con cuatro procesadores e hiperthreading habilitados. En función de estas fórmulas, usaría los siguientes valores para los valores de configuración que se mencionan en este artículo.

<system.web>
    <processModel maxWorkerThreads="100" maxIoThreads="100" minWorkerThreads="50"/>
    <httpRuntime minFreeThreads="704" minLocalRequestFreeThreads="608"/>
</system.web>
<system.net>
    <connectionManagement>
        <add address="[ProvideIPHere]" maxconnection="96"/>
    </connectionManagement>
</system.net>

Además, cuando se usa esta configuración, hay 12 conexiones disponibles por CPU por dirección IP para cada AppDomain. Por lo tanto, en el siguiente escenario, se produce muy poca contención cuando las solicitudes están esperando conexiones y ThreadPool no se agota:

  • La web hospeda solo una aplicación (AppDomain).
  • Cada solicitud de una página ASPX realiza una solicitud de servicio web.
  • Todas las solicitudes son a la misma dirección IP.

Sin embargo, cuando se usa esta configuración, los escenarios que implican uno de los siguientes probablemente usarán demasiadas conexiones:

  • Las solicitudes son a varias direcciones IP.
  • Se redirigen las solicitudes (código de estado 302).
  • Las solicitudes requieren autenticación.
  • Las solicitudes se realizan desde varios AppDomains.

En estos escenarios, es una buena idea usar un valor inferior para el maxconnection parámetro y valores superiores para el minFreeThreads parámetro y el minLocalRequestFreeThreads parámetro.

Más información

Para obtener más información, consulte Mejora del rendimiento de ASP.NET.

Si experimenta un rendimiento y una contención deficientes en IIS junto con ASP.NET, vaya a los siguientes blogs de Microsoft: