Granos de trabajo sin estado

De manera predeterminada, el runtime de Orleans no crea más de una activación de un grano en el clúster. Esta es la expresión más intuitiva del modelo de Virtual Actor con cada grano correspondiente a una entidad con un tipo o identidad únicos. Sin embargo, en algunos casos la aplicación necesita realizar operaciones funcionales sin estado que no estén vinculadas a una entidad particular del sistema. Por ejemplo, si el cliente envía solicitudes con cargas comprimidas que deben descomprimirse antes de que se puedan enrutar al grano de destino para el procesamiento, dicha lógica de descompresión o enrutamiento no está vinculada a una entidad específica de la aplicación y se puede escalar horizontalmente con facilidad.

Cuando StatelessWorkerAttribute se aplica a una clase de grano, indica al runtime de Orleans que los granos de esa clase deben tratarse como granos de trabajo sin estado. Los granos de trabajo sin estado tienen las siguientes propiedades que hacen que su ejecución sea muy diferente de la de las clases de grano normal.

  1. El runtime de Orleans puede crear y creará varias activaciones de un grano de trabajo sin estado en diferentes silos del clúster.
  2. Las solicitudes realizadas a granos de trabajo sin estado se ejecutan localmente siempre que el silo sea compatible y, por lo tanto, no incurrirán en costos de red o serialización. Si el silo local no es compatible, las solicitudes se reenviarán a uno que sí sea compatible.
  3. El runtime de Orleans crea activaciones adicionales de un grano de trabajo sin estado de manera automática si los que ya existen están ocupados. El número máximo de activaciones de un grano de trabajo sin estado que crea el tiempo de ejecución por silo está limitado de forma predeterminada por el número de núcleos de CPU en la máquina, a menos que lo especifique de forma explícita el argumento opcional maxLocalWorkers.
  4. Debido a 2 y 3, las activaciones de grano de trabajo sin estado no son direccionables de forma individual. Dos solicitudes posteriores a un grano de trabajo sin estado se pueden procesar mediante diferentes activaciones del mismo.

Los granos de trabajo sin estado proporcionan una forma sencilla de crear un grupo autoadministrado de activaciones de grano que se escala y reduce verticalmente de forma automática en función de la carga real. El tiempo de ejecución siempre examina las activaciones de grano de trabajo sin estado disponibles en el mismo orden. Por este motivo, siempre envía solicitudes a la primera activación local inactiva que puede encontrar y solo llega a la última si todas las activaciones anteriores están ocupadas. Si todas las activaciones están ocupadas y no se ha alcanzado el límite de activación, crea una activación más al final de la lista y le envía la solicitud. Esto significa que, cuando aumenta la tasa de solicitudes a un grano de trabajo sin estado y las activaciones existentes están ocupadas actualmente, el tiempo de ejecución expande el grupo de activaciones hasta el límite. Por el contrario, cuando la carga disminuye y se puede controlar con un número menor de activaciones de grano de trabajo sin estado, las activaciones que están al final de la lista no recibirán las solicitudes que se les envíe. Se volverán inactivos y, al final, se desactivarán mediante el proceso de colección de activación estándar. Por tanto, el grupo de activaciones al final se reducirá para que coincida con la carga.

En el ejemplo siguiente se define una clase MyStatelessWorkerGrain de grano de trabajo sin estado con el límite de número de activación máximo predeterminado.

[StatelessWorker]
public class MyStatelessWorkerGrain : Grain, IMyStatelessWorkerGrain
{
    // ...
}

Realizar una llamada a un grano de trabajo sin estado es igual que para cualquier otro grano. La única diferencia es que, en la mayoría de los casos, se usa un id. de grano único como, por ejemplo, 0 o Guid.Empty. Se pueden usar varios identificadores de grano cuando se tienen varios grupos de granos de trabajo sin estado, lo ideal es uno por cada id.

var worker = GrainFactory.GetGrain<IMyStatelessWorkerGrain>(0);
await worker.Process(args);

Esta define una clase de grano de trabajo sin estado sin más de una activación de grano por silo.

[StatelessWorker(1)] // max 1 activation per silo
public class MyLonelyWorkerGrain : ILonelyWorkerGrain
{
    //...
}

Tenga en cuenta que StatelessWorkerAttribute no cambia la reentrada de la clase de grano de destino. Al igual que cualquier otro grano, los granos de trabajo sin estado no son reentrantes de forma predeterminada. Se pueden convertir de forma explícita en reentrantes mediante la agregación de ReentrantAttribute a la clase de grano.

State

La parte "sin estado" del "trabajo sin estado" no significa que un trabajo sin estado no pueda tener un estado y solo se limite a ejecutar operaciones funcionales. Al igual que cualquier otro grano, un grano de trabajo sin estado puede cargar y mantener en memoria cualquier estado que necesite. Esto se debe a que se pueden crear varias activaciones de un grano de trabajo sin estado en diferentes silos del clúster o en los mismos, no existe ningún mecanismo fácil para coordinar el estado que mantienen las diferentes activaciones.

Varios patrones útiles implican un estado de retención de trabajo sin estado.

Escalado horizontal de elementos en caché de accesos

En el caso de los elementos en caché de accesos que experimentan un alto rendimiento, el mantenimiento de cada uno de estos elementos en un grano de trabajo sin estado hace que:

  1. Escalar horizontalmente de forma automática en un silo y en todos los silos del clúster, y;
  2. Hace que los datos estén siempre disponibles localmente en el silo que recibió la solicitud de cliente a través de la puerta de enlace de cliente, de modo que las solicitudes se puedan responder sin un salto de red adicional a otro silo.

Reducción de la agregación de estilos

En algunos escenarios, las aplicaciones necesitan calcular determinadas métricas en todos los granos de un tipo determinado del clúster e informar periódicamente de los agregados. Algunos ejemplos son los informes de varios jugadores por mapa de juego, la duración media de una llamada VoIP, etc. Si cada uno de los miles o millones de granos notificara sus métricas a un único agregador global, el agregador se sobrecargaría inmediatamente y no podría procesar el desbordamiento de informes. El enfoque alternativo consiste en convertir esta tarea en 2 (o más) pasos para reducir la agregación de estilos. La primera capa de agregación se realiza mediante el envío de métricas de grano a un grano previo a la agregación de trabajo sin estado. El runtime de Orleans creará automáticamente varias activaciones del grano de trabajo sin estado con cada silo. Dado que todas estas llamadas se procesarán localmente sin llamadas remotas ni serialización de los mensajes, el costo de dicha agregación será significativamente menor que en un caso remoto. Ahora, cada una de las activaciones de grano de trabajo sin estado previas a la agregación, independientemente o en coordinación con otras activaciones locales, puede enviar sus informes agregados al agregador final global (o a otra capa de reducción si es necesario) sin sobrecargarlo.