¿Estamos haciendo cola y serializando correctamente?

13

Procesamos mensajes a través de una variedad de servicios (un mensaje tocará probablemente 9 servicios antes de que se realice, cada uno realizando una función específica relacionada con IO). En este momento tenemos una combinación del peor de los casos (serialización de contrato de datos XML) y el mejor de los casos (MSMQ en memoria) para el rendimiento.

La naturaleza del mensaje significa que nuestros datos serializados terminan entre 12 y 15 kilobytes, y procesamos alrededor de 4 millones de mensajes por semana. Los mensajes persistentes en MSMQ fueron demasiado lentos para nosotros y, a medida que crecen los datos, sentimos la presión de los archivos mapeados en memoria de MSMQ. El servidor tiene 16 GB de uso de memoria y está creciendo, solo para hacer cola. El rendimiento también sufre cuando el uso de memoria es alto, ya que la máquina comienza a intercambiar. Ya estamos haciendo el comportamiento de autolimpieza de MSMQ.

Siento que hay una parte que estamos haciendo mal aquí. Intenté usar RavenDB para persistir los mensajes y simplemente poner en cola un identificador, pero el rendimiento allí fue muy lento (1000 mensajes por minuto, en el mejor de los casos). No estoy seguro de si eso es el resultado del uso de la versión de desarrollo o qué, pero definitivamente necesitamos un mayor rendimiento [1]. El concepto funcionó muy bien en teoría, pero el rendimiento no estaba a la altura.

El patrón de uso tiene un servicio que actúa como un enrutador, que realiza todas las lecturas. Los otros servicios adjuntarán información basada en su enlace de terceros y lo enviarán de vuelta al enrutador. La mayoría de los objetos se tocan de 9 a 12 veces, aunque alrededor del 10% se ve obligado a dar vueltas en este sistema durante un tiempo hasta que los terceros responden adecuadamente. Los servicios ahora dan cuenta de esto y tienen comportamientos apropiados para dormir, ya que utilizamos el campo de prioridad del mensaje por este motivo.

Entonces, mi pregunta, ¿cuál es una pila ideal para el paso de mensajes entre máquinas discretas pero LAN en un entorno C # / Windows? Normalmente comenzaría con BinaryFormatter en lugar de la serialización XML, pero eso es un gran obstáculo si una mejor manera es descargar la serialización en un almacén de documentos. Por lo tanto, mi pregunta.

[1]: La naturaleza de nuestro negocio significa que cuanto antes procesamos los mensajes, más dinero ganamos. Hemos demostrado empíricamente que procesar un mensaje más adelante en la semana significa que es menos probable que ganemos ese dinero. Si bien el rendimiento de "1000 por minuto" suena bastante rápido, realmente necesitamos ese número de más de 10k / minuto. El hecho de que dé números en mensajes por semana no significa que tengamos una semana completa para procesar esos mensajes.

=============== editar:

Información Adicional

Según los comentarios, agregaré algunas aclaraciones:

  • No estoy seguro de que la serialización sea nuestro cuello de botella. He comparado la aplicación y, aunque la serialización aparece en el gráfico de calor, solo es responsable de aproximadamente el 2.5-3% de la utilización de la CPU del servicio.

  • Me preocupa principalmente la permanencia de nuestros mensajes y el posible uso indebido de MSMQ. Estamos utilizando mensajes no transaccionales y no persistentes para que podamos mantener el rendimiento de la cola, y realmente me gustaría tener al menos mensajes persistentes para que sobrevivan al reinicio.

  • Agregar más RAM es una medida provisional. La máquina ya pasó de 4 GB a> 16 GB de RAM y cada vez es más difícil desmontarla para seguir agregando más.

  • Debido al patrón de enrutamiento en estrella de la aplicación, la mitad del tiempo que aparece un objeto y luego lo empuja a una cola no cambia en absoluto. Esto se presta nuevamente (IMO) para almacenarlo en algún tipo de almacén de valores clave en otro lugar y simplemente pasar identificadores de mensaje.

  • El patrón de enrutamiento en estrella es parte integral de la aplicación y no cambiará. No podemos centipedearlo en la aplicación porque cada pieza en el camino funciona de forma asíncrona (en forma de sondeo) y queremos centralizar el comportamiento de reintento en un solo lugar.

  • La lógica de la aplicación está escrita en C #, los objetos son POCO inmutables, el entorno de implementación de destino es Windows Server 2012, y se nos permite poner en marcha máquinas adicionales si un software en particular solo es compatible con Linux.

  • Mis objetivos son mantener el rendimiento actual al tiempo que reduzco la huella de memoria y aumentan la tolerancia a fallas con un gasto mínimo de capital.

Bryan Boettcher
fuente
Los comentarios se limpiaron a medida que los puntos relevantes se incorporaron a la pregunta.
ChrisF
Tendría sentido abordar el problema más acuciante antes de preocuparse por el intercambio de subsistemas de colas (aunque puede que valga la pena hacerlo eventualmente). El hecho de que la memoria esté creciendo fuera de control sugiere que todavía hay fugas en alguna parte. ¿Qué (si alguno) perfil de memoria se ha hecho?
Dan Lyons
@DanLyons: el único crecimiento de memoria está en MSMQ. Nadie habla realmente de eso, pero parece ser debido a mensajes no persistentes que están todos mapeados en memoria. Como estamos serializando una gran cantidad de datos, mantiene una cantidad considerable de memoria asignada. La memoria se recupera (eventualmente) a medida que se consumen los mensajes y se ejecuta la limpieza interna de MSMQ.
Bryan Boettcher

Respuestas:

1

Aquí hay algunos puntos de referencia de la cola que podrían interesarle. MSMQ debería ser capaz de manejar mensajes de 10K por segundo. ¿Podría ser un problema de configuración o tal vez los clientes no están al día con la lectura de la cola? También tenga en cuenta cuán increíblemente rápido es ZeroMQ en esos puntos de referencia (alrededor de 100K mensajes por segundo), no ofrece una opción de persistencia, pero debería llevarlo a donde desea ser inteligente en cuanto al rendimiento.

metal de piedra
fuente
4

Tuvimos una situación similar hace varios años, con un sistema de mensajes en cola (huellas digitales de audio en nuestro caso). Valoramos mucho la persistencia de los paquetes de datos en cola, pero descubrimos que colocar todo en el disco y consumir la cola desde el disco era muy costoso.

Si cambiamos a colas basadas en memoria, el rendimiento fue excepcional, pero tuvimos un gran problema. De vez en cuando, los consumidores de las colas dejaron de estar disponibles durante un tiempo considerable (los elementos de consumidor y productor en nuestro caso están conectados a través de WAN), por lo que la cola del productor crecería hasta un punto en que sería inmanejable y, como su caso, una vez que el consumo de memoria fue muy alto, el exceso de memoria durante el intercambio llevó al sistema a un rastreo completo.

Diseñamos una cola que bautizamos VMQueue (para Virtual Memory Queue, un nombre muy malo en retrospectiva). La idea de esta cola es que si el proceso del consumidor se está ejecutando a la par, en otras palabras, procesando lo suficientemente rápido como para poder mantener el número de elementos en cola por debajo de un cierto nivel, entonces tiene básicamente el mismo rendimiento de una memoria. cola basada Sin embargo, cuando el consumidor se ralentiza o deja de estar disponible y la cola del productor crece hasta cierto tamaño, entonces la cola comenzará a buscar automáticamente elementos hacia y desde el disco (usandoBinaryFormatterserialización por cierto). Este proceso mantiene el uso de la memoria completamente controlado, y el proceso de paginación es rápido, o al menos mucho más rápido que el intercambio de memoria virtual que ocurre durante una carga de memoria pesada. Una vez que el consumidor logra drenar la cola por debajo del umbral, continúa trabajando como una cola pura basada en memoria

Si el sistema falla o se reinicia, entonces la cola puede recuperar todos los elementos paginados que se almacenaron en el disco, solo perderá los elementos que aún se mantuvieron en la memoria antes del bloqueo. Si puede permitirse perder una cantidad limitada de paquetes durante un bloqueo o reinicio, esta cola puede ser útil.

Si está interesado, puedo compartir el VMQueuecódigo fuente de la clase para que pueda jugar con él. La cola aceptará cualquier clase que esté marcada como serializable. Al crear la cola, establece el tamaño de la página en número de elementos. La interfaz de clase es prácticamente la misma que una clase de cola estándar. Sin embargo, el código es muy antiguo (.net 1.1), por lo que desafortunadamente no existe una interfaz genérica.

Sé que pasar de la tecnología probada MSMQ es una gran apuesta, sin embargo, esta cola ha funcionado de manera confiable durante casi 6 años y nos ha permitido sobrevivir y recuperarnos de escenarios en los que la máquina productora ha estado desconectada durante varias semanas. Por favor hazme saber si estas interesado. :)

sgorozco
fuente
1

El sistema HP ProLiant ML350G5 obtiene 82k transacciones por minuto, es decir, tiene más de 8 veces ese rendimiento de "10k / minuto" que usted mencionó.

Rendimiento: 82,774 tpmC

Además, para ser honesto, me habría ido con 64 o incluso 128 GB de RAM: la RAM es barata. Greenspun señala la diferencia entre "arrojar RAM" y "conseguir que un tipo inteligente educado en MIT lo optimice", y la RAM gana.

Terminó con una máquina SQL Server equipada con 64 GB de RAM y un puñado de máquinas front-end que ejecutan páginas ASP.NET ... El sitio, swaptree.com, maneja su membresía actual de más de 400,000 usuarios (creciendo rápidamente) sin dificultad...

Tenga en cuenta que "la máquina ya pasó a 16 GB de RAM" está lejos de ser suficiente, con un artículo que señala un servidor que manejaba 400k usuarios con 64 GB de RAM.

Marcel Popescu
fuente