2008-11-26 32 views
32

Tengo un servidor .NET 2.0 que parece estar corriendo en problemas de escala, probablemente debido a un mal diseño del código de toma de manejo, y estoy en busca de orientación sobre cómo podría rediseñarlo para mejorar el rendimiento.Consejos/técnicas de alto rendimiento sockets de servidor de C#

Escenario de uso: 50 - 150 clientes, alta velocidad (hasta 100s/segundo) de mensajes pequeños (10s de bytes cada uno) a/desde cada cliente. Las conexiones de los clientes son de larga vida, generalmente horas. (El servidor es parte de un sistema comercial. Los mensajes del cliente se agregan en grupos para enviar a un intercambio en un número menor de conexiones de socket 'salientes', y los mensajes de confirmación se envían a los clientes a medida que el intercambio procesa cada grupo .) SO es Windows Server 2003, el hardware es 2 x 4-core X5355.

diseño de socket del cliente actual: Un TcpListener genera un subproceso para leer cada socket de cliente que se conectan los clientes. Los hilos bloquean en Socket.Receive, analizan los mensajes entrantes y los insertan en un conjunto de colas para su procesamiento por la lógica del servidor central. Los mensajes de acuse de recibo se envían nuevamente a través de los sockets del cliente utilizando las llamadas asincrónicas Socket.BeginSend de los hilos que hablan al lado de la centralita.

problemas observados: Al reducirse la cantidad de clientes ha crecido (ahora 60-70), hemos empezado a ver los retrasos intermitentes de hasta 100s de milisegundos al enviar y recibir datos a/de los clientes. (Registramos marcas de tiempo para cada mensaje de acuse de recibo, y podemos ver brechas largas ocasionales en la secuencia de marca de tiempo para grupos de acks del mismo grupo que normalmente salen en unos pocos totales.)

El uso total de la CPU del sistema es bajo (< 10%), hay un montón de memoria RAM libre, y la lógica de la base y el lado de salida (intercambio orientación) está realizando muy bien, por lo que el problema parece estar aislado con el código de socket de cara al cliente. Existe un ancho de banda de red amplio entre el servidor y los clientes (LAN gigabit), y hemos descartado problemas de red o de capa de hardware.

Cualquier sugerencia o punteros a recursos útiles serán bienvenidos. Si alguien tiene algún consejo de diagnóstico o depuración para averiguar exactamente qué es lo que está mal, también sería genial.

Nota: Tengo el artículo Winsock: Get Closer to the Wire with High-Performance Sockets in .NET de MSDN Magazine, y he echado un vistazo al componente "XF.Server" de Kodart: parece incompleto en el mejor de los casos.

Respuesta

18

Mucho de esto tiene que ver con muchos subprocesos que se ejecutan en su sistema y el kernel que le da a cada uno de ellos un intervalo de tiempo. El diseño es simple, pero no escala bien.

Probablemente deberías utilizar Socket.BeginReceive que se ejecutará en los grupos de subprocesos .NET (puedes especificar de algún modo el número de subprocesos que usa), y luego presionando sobre una cola desde la devolución de llamada asincrónica (que puede estar ejecutándose) en cualquiera de los hilos .NET). Esto debería darte un rendimiento mucho más alto.

+0

estuvo de acuerdo, aunque debo añadir que a pesar de que "descartado" problemas de red, consideraría intercambiar varias piezas (especialmente el servidor) y asegurarse de tener todos los últimos firmware y controladores. –

-1

que no tienen una respuesta, pero para obtener más información me gustaría sugerir rociar su código con temporizadores y el registro de tiempo promedio y máximo dado por operaciones sospechosas como la adición a la cola o la apertura de una toma de corriente.

Al menos de esa manera usted tendrá una idea de lo que a la vista y por dónde empezar.

8

Un hilo por cliente parece enormemente exagerado, especialmente teniendo en cuenta el bajo uso de CPU en general aquí.Normalmente le gustaría que un pequeño grupo de subprocesos para dar servicio a todos los clientes, utilizando BeginReceive que esperar a que asíncrono de trabajo - a continuación, simplemente despachar la transformación en uno de los trabajadores (tal vez simplemente añadiendo el trabajo a una cola sincronizada en la que todos los trabajadores están a la espera)

3

El Socket.BeginConnect y Socket.BeginAccept son definitivamente útil. Creo que usan las llamadas ConnectEx y AcceptEx en su implementación. Estas llamadas envuelven la negociación de conexión inicial y la transferencia de datos en una transición de usuario/kernel. Como el búfer de envío/recepción inicial ya está listo, el kernel puede simplemente enviarlo, ya sea al host remoto o al espacio de usuario.

También tienen una lista de listeners/connectors preparada que probablemente da un poco de impulso al evitar la latencia involucrada con el espacio de usuario que acepta/recibe una conexión y la entrega (y todas las conmutaciones de usuario/kernel).

Para utilizar BeginConnect con un tampón parece que usted tiene que escribir los datos iniciales a la toma de corriente antes de conectar.

6

No soy un chico de C# de ninguna manera, pero para los servidores de socket de alto rendimiento la solución más escalable es usar I/O Completion Ports con un número de subprocesos activos apropiados para la (s) CPU (s) en que se ejecuta el proceso, en lugar de usando el modelo de un hilo por conexión.

En su caso, con una máquina de 8 núcleos le gustaría que el total de 16 hilos con 8 ejecutan al mismo tiempo. (El otro 8 están básicamente mantiene en reserva.)

+6

CLR ya utiliza puertos de terminación de E/S para conectores. Entonces, obtienes ese beneficio por defecto en .NET. – feroze

+0

WCF también utilizará los puertos de terminación IO para responder a cada una de sus llamadas de servicio. Pero es un buen punto hacer que los puertos IO livianos estén específicamente diseñados para esta tarea. – Spence

+0

¿Cómo se especifica el número total de subprocesos? – Legend

22

rendimiento del zócalo de E/S ha mejorado en .NET 3.5 medio ambiente. Puede usar ReceiveAsync/SendAsync en lugar de BeginReceive/BeginSend para un mejor rendimiento. Chech esto:

http://msdn.microsoft.com/en-us/library/bb968780.aspx

+0

Gracias por el enlace. Probablemente no estemos en 3.5 por un tiempo (por muchas razones), pero cuando cambiemos, echaré un vistazo a estos nuevos métodos. – McKenzieG1

4

como han sugerido otros, la mejor forma de implementar esto sería que el cliente se enfrenta todo el código asíncrono. Utilice BeginAccept() en el TcpServer() para que no tenga que generar manualmente un hilo. Luego use BeginRead()/BeginWrite() en la secuencia de red subyacente que obtiene del TcpClient aceptado.

Sin embargo, hay una cosa que no puedo entender aquí. Dijiste que estas son conexiones de larga vida y una gran cantidad de clientes. Suponiendo que el sistema ha alcanzado el estado estable, donde tiene sus clientes máximos (por ejemplo, 70) conectados. Tienes 70 hilos para escuchar los paquetes del cliente. Entonces, el sistema aún debe ser receptivo. A menos que su aplicación tenga pérdidas de memoria/identificador y se esté quedando sin recursos para que su servidor esté buscando. Me gustaría poner un temporizador en la llamada a Aceptar() donde se inicia un hilo de cliente y ver cuánto tiempo lleva. Además, iniciaría taskmanager y PerfMon, y supervisaría "Pool no paginado", "Memoria virtual", "Recuento de control" para la aplicación y vería si la aplicación tiene problemas de recursos.

Si bien es cierto que va asíncrono es el camino correcto a seguir, no estoy convencido de si realmente resolver el problema de fondo. Supervisaría la aplicación como sugerí y me aseguraré de que no haya problemas intrínsecos de pérdida de memoria y de identificadores. En este sentido, "BigBlackMan" de arriba tenía razón, necesita más instrumentación para continuar. No sé por qué fue votado negativamente.

3

azar ~ 250 ms retrasos intermitentes podrían deberse a que el algoritmo de Nagle utilizado por TCP. Intenta inhabilitar eso y mira lo que sucede.

1

Una cosa que quieren eliminar es que no es algo tan simple como el recolector de basura en ejecución. Si todos sus mensajes están en el montón, está generando 10000 objetos por segundo.

Tome una lectura de Garbage Collection every 100 seconds

La única solución es mantener sus mensajes fuera del montón.

0

Tuve el mismo problema hace 7 u 8 años y pausas de 100ms a 1 segundo, el problema fue la recolección de basura ... Tenía unos 400 Meg en uso de 4 gigas PERO había muchos objetos.

Terminé el almacenamiento de mensajes en C++ pero se puede usar caché de ASP.NET (que solía utilizar COM y se trasladó fuera del montón)

Cuestiones relacionadas