Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El término, escalabilidad, a menudo se usa incorrectamente. 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 a un gran número de clientes.
Estas dos definiciones relacionadas se conocen normalmente como escalado vertical. Al final de este tema se proporcionan sugerencias sobre 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 en el contexto de los servidores RPC y RPC únicamente. Los procedimientos recomendados para la escalabilidad, como reducir la contención, evitar errores frecuentes de caché 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 se bloquea un subproceso, 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 550MHz 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 nulas 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 obtiene de la manera en que la contención en el servidor es alta. Para ilustrarlo, imagine un servidor de gran servicio que se usa para acceder de forma remota a archivos. Supongamos que el servidor adopta el enfoque más sencillo: simplemente lee o 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 pierda 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ía aproximadamente 50 subprocesos. Esto supone que una llamada que bloqueará viene cada 2 milisegundos y, después de 100 milisegundos, el primer subproceso se libera de nuevo, por lo que el grupo se estabilizará a unos 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 aumenta rápidamente a medida que los subprocesos del servidor empiezan a bloquearse y a esperar algo (ya sea una E/S o acceso a un recurso).
RPC y el puerto de finalización que puertas 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 actualmente utilizables bloquee. Si el quinto subproceso espera lo suficiente como espera activa sin el número de subprocesos RPC utilizables que caen 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 a menudo. En un servidor en el que los subprocesos suelen bloquearse, un subproceso que vuelve a RPC pronto se quita de la lista de espera activa, ya que todos los subprocesos que se pueden usar 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 montón, secciones críticas en el código del servidor, etc. Esto aumenta aún más la contención a medida que los convoyes se forman en el formulario de 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. Dependiendo 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 está accediendo 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, en cola 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, realice esas llamadas asincrónicas y asegúrese de que la cola no crece demasiado grande. Si la rutina del servidor realiza E/S de archivos, use la 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. Cuando haya terminado la E/S, o la llamada RPC realizada al servidor se complete, complete la llamada RPC asincrónica que entregó la solicitud. Esto permitirá que el servidor se ejecute con tan pocos subprocesos como sea posible, lo que aumenta el rendimiento y el número de clientes que un servidor puede atender.
Escalado horizontal
RPC se puede configurar para que funcione con el equilibrio de carga de red (NLB) si NLB está configurado de forma 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, vea RPC y network), es esencial que todas las conexiones del grupo del cliente 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.