2012-04-03 15 views
12

Tengo una aplicación de servidor ASP.NET 3.5 escrita en C#. Realiza solicitudes de salida a una API REST utilizando HttpWebRequest y HttpWebResponse.HttpWebResponse no escalará solicitudes de salida simultáneas

He configurado una aplicación de prueba para enviar estas solicitudes en hilos separados (para simular de manera imprecisa la concurrencia con el servidor).

Tenga en cuenta que esto es más una pregunta de Mono/Medio ambiente que una pregunta de código; así que tenga en cuenta que el siguiente código no es literal; solo un corte/pegado de los bits funcionales.

Aquí hay algunos pseudo-código:

// threaded client piece 
int numThreads = 1; 
ManualResetEvent doneEvent; 

using (doneEvent = new ManualResetEvent(false)) 
     { 

      for (int i = 0; i < numThreads; i++) 
      { 

       ThreadPool.QueueUserWorkItem(new WaitCallback(Test), random_url_to_same_host); 

      } 
      doneEvent.WaitOne(); 
     } 

void Test(object some_url) 
{ 
    // setup service point here just to show what config settings Im using 
    ServicePoint lgsp = ServicePointManager.FindServicePoint(new Uri(some_url.ToString())); 

     // set these to optimal for MONO and .NET 
     lgsp.Expect100Continue = false; 
     lgsp.ConnectionLimit = 100; 
     lgsp.UseNagleAlgorithm = true; 
     lgsp.MaxIdleTime = 100000;   

    _request = (HttpWebRequest)WebRequest.Create(some_url); 


    using (HttpWebResponse _response = (HttpWebResponse)_request.GetResponse()) 
    { 
     // do stuff 
    } // releases the response object 

    // close out threading stuff 

    if (Interlocked.Decrement(ref numThreads) == 0) 
    { 
     doneEvent.Set(); 
    } 
} 

Si funciono con la aplicación en mi máquina de desarrollo local (Windows 7) en el servidor de Estudio Web visual, que puede hasta los numThreads y recibir la misma respuesta promedio tiempo con variación mínima ya sea 1 "usuario" o 100.

Publicar y desplegar la aplicación en Apache2 en un entorno Mono 2.10.2, la escala de tiempos de respuesta es casi lineal. (es decir, 1 subproceso = 300 ms, 5 subprocesos = 1500 ms, 10 subprocesos = 3000 ms). Esto sucede independientemente del punto final del servidor (nombre de host diferente, red diferente, etc.).

Usando IPTRAF (y otras herramientas de red), parece como si la aplicación solo abre 1 o 2 puertos para enrutar todas las conexiones y las respuestas restantes tienen que esperar.

Hemos creado una aplicación PHP similar e implementada en Mono con las mismas solicitudes y las respuestas se escalan de forma adecuada.

He revisado cada configuración de configuración que se me ocurre para Mono y Apache y la ÚNICA configuración que es diferente entre los dos entornos (al menos en código) es que a veces el ServicePoint admitePipelining = falso en Mono, mientras es cierto desde mi máquina.

Parece que ConnectionLimit (valor predeterminado de 2) no cambia en Mono por alguna razón, pero estoy estableciendo un valor más alto tanto en el código como en el archivo web.config para el host especificado.

O mi equipo y yo estamos pasando por alto algo importante o esto es algún tipo de error en Mono.

+1

¿Has encontrado una solución? – Kugel

Respuesta

8

Creo que está golpeando un cuello de botella en el HttpWebRequest. Las solicitudes web utilizan una infraestructura común de puntos de servicio dentro de .NET Framework. Esto parece estar destinado a permitir que se reutilicen las solicitudes al mismo host, pero en mi experiencia se producen dos cuellos de botella.

En primer lugar, los puntos de servicio solo permiten dos conexiones concurrentes a un host determinado de forma predeterminada para cumplir con la especificación HTTP. Esto puede anularse estableciendo la propiedad estática ServicePointManager.DefaultConnectionLimit en un valor más alto. Consulte esta página MSDN para obtener más detalles. Parece que ya está abordando esto para el punto de servicio individual en sí, pero debido al esquema de bloqueo de concurrencia en el nivel de punto de servicio, hacerlo puede estar contribuyendo al cuello de botella.

En segundo lugar, parece haber un problema con la granularidad de bloqueo en la clase ServicePoint. Si descompila y observa la fuente de la palabra clave lock, encontrará que utiliza la instancia para sincronizarse y lo hace en muchos lugares. Con la instancia del punto de servicio compartida entre las solicitudes web para un host determinado, en mi experiencia esto tiende a atascar a medida que se abren más HttpWebRequests y hace que escale mal.Este segundo punto es principalmente observación personal y hurgando en la fuente, así que tómalo con un grano de sal; No lo consideraría una fuente autorizada.

Desafortunadamente, no encontré un sustituto razonable en el momento en que estaba trabajando con él. Ahora que se ha lanzado ASP.NET Web API, le recomendamos echar un vistazo al HttpClient. Espero que ayude.

+0

Jesse, gracias por la información. Establecer ServicePointManager DefaultConnectionLimit simplemente lo configura por ServicePoint/endpoint, por lo que no sé si eso evitará el problema de bloqueo que describe en el nivel de SP. Ciertamente esperaría que ServicePoint se atasque a medida que crecen los hilos/solicitudes, pero no tan dramáticamente. Y todavía parece que las solicitudes no salen sin problemas, pero las respuestas esperan. Tendré que buscar en HttpClient como alternativa. – erasend

+0

Re: DefaultConnectionLimit - No reclamo que funcionará milagros, pero omite una llamada para bloquear (esto) en el setter para ConnectionLimit en ServicePoint sí mismo. En cuanto a la escala, mi impresión fue la misma. Todavía no puedo afirmar que realmente entiendo por qué el cuello de botella golpea tan fuerte. Hay algunas construcciones de sincronización para obtener la respuesta y nuevamente si se obtienen las secuencias.En su caso, especularía que la caída de local a no local se debe a la latencia adicional con un servidor externo ... pero, de nuevo, eso es pura especulación. –

+0

DefaultConnectionLimit no cambió nada (y la eliminación de ConnectionLimit en el objeto SP lo revertió al valor predeterminado de 2). La diferencia de local a no local definitivamente está relacionada con Mono/environment ya que el script de PHP no tuvo problemas, las mismas solicitudes. De eso estoy tratando de llegar al fondo, bajo la suposición de que está en cómo Mono está manejando estas respuestas. – erasend

8

Sé que esto es bastante antiguo, pero lo estoy poniendo aquí en caso de que pueda ayudar a alguien más que se encuentre con este problema. Nos encontramos con el mismo problema con las solicitudes HTTPS de salida paralelas. Hay algunos problemas en juego.

El primer problema es que ServicePointManager.DefaultConnectionLimit no modificó el límite de conexión por lo que yo sé. Establecer esto en 50, crear una nueva conexión y luego verificar el límite de conexión en el punto de servicio para la nueva conexión dice 2. Configurarlo en ese punto de servicio una vez 50 parece funcionar y persistir para todas las conexiones que terminarán pasando por ese punto de servicio.

El segundo problema con el que tropezamos fue con el enhebrado. La implementación actual del grupo de subprocesos mono parece crear como máximo 2 nuevos subprocesos por segundo. Esto es una eternidad si haces muchas solicitudes paralelas que comienzan exactamente al mismo tiempo. Para contrarrestar esto, intentamos configurar ThreadPool.SetMinThreads en un número mayor. Parece que Mono solo crea hasta 1 hilo nuevo cuando realiza esta llamada, independientemente del delta entre el número actual de hilos y el número deseado. Pudimos solucionar esto llamando a SetMinThreads en un bucle hasta que el grupo de subprocesos tenía el número deseado de subprocesos inactivos.

me abrió un error sobre esta última cuestión, ya que es el que yo estoy más seguro de que no está funcionando según lo previsto: https://bugzilla.xamarin.com/show_bug.cgi?id=7055

+0

Puede confirmar 'ServicePointManager.DefaultConnectionLimit' no tiene ningún efecto en Mono, el límite sigue siendo 2 – KCD

+0

configurándolo en [' app.config' o 'web.config'] (http://stackoverflow.com/a/436477/516748) funciona ... sin embargo, está demostrando ser contraproducente en Mono con cualquier otra cosa que no sea 2 reduciendo el rendimiento masivo (?!). También ServicePoint.Connections siempre informa 0 mientras que Windows informa hasta y justo sobre el ConnectionLimit ... – KCD

0

Si @ Jake-moshenko tiene razón sobre ServicePointManager.DefaultConnectionLimit no tener ningún efecto si se ha cambiado en mono, por favor presente esto como un error en http://bugzilla.xamarin.com/.

Sin embargo me gustaría probar algunas cosas antes de descartar por completo esta como una cuestión Mono:

  1. Trate de usar el recolector de basura Sgen en lugar de la antigua Boehm, pasando --gc=sgen como bandera a mono.
  2. Si lo anterior no ayuda, actualice a Mono 3.2 (que BTW también utiliza el SGEN GC), porque ha habido muchas correcciones desde que hizo la pregunta.
  3. Si lo anterior no ayuda, construya su propio Mono (rama principal), ya que this important pull request sobre el subprocesamiento se ha fusionado recientemente.
  4. Si lo anterior no ayuda, construya su propio Mono con this pull request agregado. Si soluciona su problema, agregue un "+1" a la solicitud de extracción. Podría ser una solución para bug 7055.
Cuestiones relacionadas