Compartir a través de


Escalabilidad

El término escalabilidad suele ser mal utilizado. Para esta sección, se proporciona una definición dual:

  • La escalabilidad es la capacidad de utilizar completamente la potencia de procesamiento disponible en un sistema multiprocesador (2, 4, 8, 32 o más procesadores).
  • La escalabilidad es la capacidad de atender un gran número de clientes.

Estas dos definiciones relacionadas se conocen normalmente como escalado vertical. El final de este tema proporciona sugerencias sobre el escalado horizontal.

Esta explicación se centra exclusivamente en la escritura de servidores escalables, no clientes escalables, ya que los servidores escalables son requisitos más comunes. En esta sección también se aborda la escalabilidad solo en el contexto de los servidores RPC y RPC. Los procedimientos recomendados para la escalabilidad, como reducir la contención, evitar errores de caché frecuentes en ubicaciones de memoria global o evitar el uso compartido falso, no se describen aquí.

Modelo de subprocesos RPC

Cuando un servidor recibe una llamada RPC, se llama a la rutina del servidor (rutina de administrador) en un subproceso proporcionado por RPC. RPC usa un grupo de subprocesos adaptable que aumenta y disminuye a medida que fluctúa la carga de trabajo. A partir de Windows 2000, el núcleo del grupo de subprocesos RPC es un puerto de finalización. El puerto de finalización y su uso por RPC se ajustan para rutinas de servidor de contención cero a baja. Esto significa que el grupo de subprocesos RPC aumenta agresivamente el número de subprocesos de mantenimiento si algunos se bloquean. Funciona con la presunción de que el bloqueo es poco frecuente y, si un subproceso se bloquea, se trata de una condición temporal que se resuelve rápidamente. Este enfoque permite la eficacia de los servidores de contención bajos. Por ejemplo, un servidor RPC de llamada void que funciona en un servidor de ocho procesadores de 550 MHz al que se accede a través de una red de área del sistema de alta velocidad (SAN) atiende más de 30 000 llamadas void por segundo desde más de 200 clientes remotos. Esto representa más de 108 millones de llamadas por hora.

El resultado es que el grupo de subprocesos agresivo realmente se pone en marcha cuando la contención en el servidor es alta. Para ilustrarlo, imagine un servidor pesado que se usa para acceder de forma remota a archivos. Supongamos que el servidor adopta el enfoque más sencillo: simplemente lee y escribe el archivo de forma sincrónica en el subproceso en el que RPC invoca la rutina del servidor. Además, supongamos que tenemos un servidor de cuatro procesadores que atiende a muchos clientes.

El servidor comenzará con cinco subprocesos (esto varía realmente, pero se usan cinco subprocesos para simplificar). Una vez que RPC recoge la primera llamada RPC, envía la llamada a la rutina del servidor y la rutina del servidor emite la E/S. Con poca frecuencia, se pierde la memoria caché de archivos y, a continuación, se bloquea la espera del resultado. En cuanto se bloquea, se libera el quinto subproceso para recoger una solicitud y se crea un sexto subproceso como espera activa. Suponiendo que cada décima operación de E/S pierde la memoria caché y bloqueará 100 milisegundos (un valor de tiempo arbitrario) y suponiendo que el servidor de cuatro procesadores atiende aproximadamente 20 000 llamadas por segundo (5000 llamadas por procesador), un modelado simplista predeciría que cada procesador generará aproximadamente 50 subprocesos. Esto supone una llamada que bloqueará cada 2 milisegundos y, después de 100 milisegundos, el primer subproceso se libera de nuevo, por lo que el grupo se estabilizará en aproximadamente 200 subprocesos (50 por procesador).

El comportamiento real es más complicado, ya que el gran número de subprocesos provocará cambios de contexto adicionales que ralentizan el servidor y también ralentizan la velocidad de creación de nuevos subprocesos, pero la idea básica es clara. El número de subprocesos sube rápidamente a medida que los subprocesos del servidor comienzan a bloquearse y esperan algo (ya sea una E/S o acceso a un recurso).

RPC y el puerto de finalización que valida las solicitudes entrantes intentarán mantener el número de subprocesos RPC utilizables en el servidor para que sean iguales al número de procesadores de la máquina. Esto significa que en un servidor de cuatro procesadores, una vez que un subproceso vuelve a RPC, si hay cuatro o más subprocesos RPC utilizables, el quinto subproceso no puede recoger una nueva solicitud y, en su lugar, se sentará en un estado de espera activa en caso de que uno de los subprocesos utilizables actualmente bloquee. Si el quinto subproceso espera lo suficiente como espera activa sin el número de subprocesos RPC utilizables que se van a colocar por debajo del número de procesadores, se liberará, es decir, el grupo de subprocesos disminuirá.

Imagine un servidor con muchos subprocesos. Como se explicó anteriormente, un servidor RPC termina con muchos subprocesos, pero solo si los subprocesos se bloquean con frecuencia. En un servidor donde los subprocesos a menudo bloquean, un subproceso que vuelve a RPC pronto se saca de la lista de espera activa, ya que todos los subprocesos utilizables actualmente bloquean y se le da una solicitud para procesar. Cuando un subproceso se bloquea, el distribuidor de subprocesos del kernel cambia el contexto a otro subproceso. Este modificador de contexto por sí mismo consume ciclos de CPU. El siguiente subproceso ejecutará código diferente, accederá a diferentes estructuras de datos y tendrá una pila diferente, lo que significa que la tasa de aciertos de caché de memoria (las cachés L1 y L2) será mucho menor, lo que provocará una ejecución más lenta. Los numerosos subprocesos que se ejecutan simultáneamente aumentan la contención de los recursos existentes, como el montón, las secciones críticas en el código del servidor, etc. Esto aumenta aún más la contención a medida que se forman convoyes en los recursos. Si la memoria es baja, la presión de memoria que ejerce el gran y creciente número de subprocesos provocará errores de página, lo que aumentará aún más la velocidad a la que se bloquean los subprocesos y hará que se creen aún más subprocesos. En función de la frecuencia con la que se bloquea y la cantidad de memoria física disponible, el servidor puede estabilizarse en algún nivel inferior de rendimiento con una frecuencia de cambio de contexto alta, o puede deteriorarse hasta el punto en el que solo accede repetidamente al disco duro y al cambio de contexto sin realizar ningún trabajo real. Esta situación no se mostrará en la carga de trabajo ligera, por supuesto, pero una carga de trabajo pesada lleva rápidamente el problema a la superficie.

¿Cómo se puede evitar esto? Si se espera que los subprocesos bloqueen, declare llamadas como asincrónicas y, una vez que la solicitud entre en la rutina del servidor, escríbala en un grupo de subprocesos de trabajo que usan las funcionalidades asincrónicas del sistema de E/S o RPC. Si el servidor realiza llamadas RPC a su vez realiza esas llamadas asincrónicas y asegúrese de que la cola no crezca demasiado grande. Si la rutina del servidor realiza E/S de archivos, use E/S asincrónica de archivos para poner en cola varias solicitudes al sistema de E/S y tener solo unos pocos subprocesos en cola y recoger los resultados. Si la rutina del servidor está realizando E/S de red, use de nuevo las funcionalidades asincrónicas del sistema para emitir las solicitudes y recoger las respuestas de forma asincrónica y usar el menor número de subprocesos posible. Una vez finalizada la E/S, o bien se completa la llamada RPC realizada al servidor, complete la llamada RPC asincrónica que entregó la solicitud. Esto permitirá que el servidor se ejecute con el menor número de subprocesos posible, lo que aumenta el rendimiento y el número de clientes que un servidor puede atender.

Escalabilidad horizontal

RPC se puede configurar para que funcione con el equilibrio de carga de red (NLB) si NLB está configurado de modo que todas las solicitudes de una dirección de cliente determinada vayan al mismo servidor. Dado que cada cliente RPC abre un grupo de conexiones (para obtener más información, consulte RPC y network), es esencial que todas las conexiones del grupo de clientes especificado terminen en el mismo equipo servidor. Siempre que se cumpla esta condición, se puede configurar un clúster NLB para que funcione como un servidor RPC grande con una escalabilidad potencialmente excelente.