Colocación de los granos

Orleans garantiza que, cuando se realice una llamada de grano, habrá una instancia de ese grano disponible en memoria en algún servidor del clúster para controlar la solicitud. Si el grano no está activo actualmente en el clúster, Orleans elige uno de los servidores para que el grano se active allí. Esto se denomina colocación del grano. La colocación también es una forma de equilibrar la carga: incluso la colocación de granos que ya tienen actividad ayuda a repartir equitativamente la carga de trabajo en todo el clúster.

El proceso de colocación en Orleans es completamente configurable: los desarrolladores pueden elegir entre un conjunto de directivas de colocación predeterminadas, por ejemplo, aleatoria, con prioridad para el entorno local o en función de la carga. También pueden configurar una lógica personalizada. Esto ofrece una flexibilidad total para decidir dónde se crean los granos. Por ejemplo, los granos se pueden colocar en un servidor cerca de los recursos que necesitan para ser operativos o cerca de otros granos con los que establecen comunicaciones. De manera predeterminada, Orleans elegirá un servidor compatible aleatoriamente.

La estrategia de colocación que usa Orleans se puede configurar globalmente o según la clase del grano.

Colocación aleatoria

Se selecciona aleatoriamente un servidor de los servidores compatibles del clúster. Esta estrategia de colocación se configura agregando RandomPlacementAttribute a un grano.

Colocación local

Si el servidor local es compatible, selecciónelo; de lo contrario, seleccione un servidor aleatorio. Esta estrategia de colocación se configura agregando PreferLocalPlacementAttribute a un grano.

Colocación en función del hash

Aplique un hash del identificador de grano a un entero no negativo y calcule su módulo con el número de servidores compatibles. Seleccione el servidor correspondiente de la lista de servidores compatibles, ordenados según su dirección. Tenga en cuenta que no hay garantías de que esto permanezca estable según cambia la pertenencia al clúster. En concreto, agregar, quitar o reiniciar servidores puede modificar el servidor seleccionado para un identificador de grano específico. Dado que los granos colocados mediante esta estrategia se registran en el directorio de granos, este cambio en la opción de colocación conforme cambia la pertenencia no suele tener un efecto reseñable.

Esta estrategia de colocación se configura agregando HashBasedPlacementAttribute a un grano.

Colocación en función del recuento de activación

Esta estrategia de colocación pretende colocar nuevas activaciones de granos en el servidor menos cargado en función del número de granos con actividad reciente. Incluye un mecanismo en el que todos los servidores publican periódicamente su recuento total de activaciones en todos los demás servidores. Después, el director de colocación selecciona el servidor cuya predicción indique que tendrá el menor número de activaciones. Esto se hace examinando el recuento de activaciones más reciente y con una predicción del recuento actual de activaciones, que se basa en el último recuento de activaciones que haya realizado el director de colocación en el servidor actual. El director selecciona varios servidores de forma aleatoria al realizar esta predicción, en un intento de evitar que varios servidores independientes sobrecarguen el mismo servidor. De manera predeterminada, se seleccionan dos servidores aleatorios, pero este valor se puede configurar mediante ActivationCountBasedPlacementOptions.

Este algoritmo se basa en la tesis The Power of Two Choices in Randomized Load Balancing (El poder de dos decisiones en el equilibrio de carga aleatorio) de Michael David Mitzenmacher, y también se usa en Nginx para el equilibrio de carga distribuido, como se describe en el artículo NGINX and the "Power of Two Choices" Load-Balancing Algorithm (NGINX y el algoritmo de equilibrio de carga del “Poder de dos decisiones”).

Esta estrategia de colocación se configura agregando ActivationCountBasedPlacementAttribute a un grano.

Colocación de trabajo sin estado

La colocación de trabajos sin estado es una estrategia especial de colocación que utilizan los granos de trabajo sin estado. Esta colocación funciona casi de forma idéntica a PreferLocalPlacement, salvo que cada servidor puede tener varias activaciones del mismo grano y que el grano no está registrado en el directorio de granos, ya que no hace falta que lo esté.

Esta estrategia de colocación se configura agregando StatelessWorkerAttribute a un grano.

Colocación basada en roles de silo

Una estrategia de colocación determinista que sitúa granos en silos con un rol específico. Esta estrategia de colocación se configura agregando SiloRoleBasedPlacementAttribute a un grano.

Selección de una estrategia de colocación

Elegir la estrategia de colocación de grano adecuada, más allá de los valores predeterminados que proporciona Orleans, requiere la supervisión y la evaluación del desarrollador. La elección de la estrategia de colocación debe basarse en el tamaño y la complejidad de la aplicación, las características de la carga de trabajo y el entorno de implementación.

La colocación aleatoria se basa en la ley de números grandes, por lo que suele ser un valor predeterminado bueno cuando hay una carga imprevisible distribuida en una gran cantidad de granos (más de 10 000).

La colocación basada en recuento de activación también tiene un elemento aleatorio, basándose en el principio del Poder de dos decisiones, que es un algoritmo usado habitualmente para el equilibrio de carga distribuido y que se usa en equilibradores de carga populares. Los silos publican con frecuencia estadísticas en tiempo de ejecución en otros silos del clúster, entre los que se incluyen los siguientes:

  • Memoria disponible, memoria física total y uso de memoria.
  • Uso de CPU.
  • Recuento total de activación y recuento de activación activa reciente.
    • Ventana deslizante de las activaciones que estaban activas en los últimos segundos, a veces denominada conjunto de trabajo de activación.

A partir de estas estadísticas, actualmente solo se usan los recuentos de activación para determinar la carga en un silo determinado.

En última instancia, debe experimentar con diferentes estrategias y supervisar las métricas de rendimiento para determinar la mejor opción. Al seleccionar la estrategia adecuada de colocación de granos, puede optimizar el rendimiento, la escalabilidad y la rentabilidad de las aplicaciones de Orleans.

Configuración de la estrategia de colocación predeterminada

Orleans usará la colocación aleatoria a menos que se invalide la configuración predeterminada. La estrategia de colocación predeterminada se puede invalidar mediante el registro de una implementación de PlacementStrategy durante la configuración:

siloBuilder.ConfigureServices(services =>
    services.AddSingleton<PlacementStrategy, MyPlacementStrategy>());

Configuración de la estrategia de colocación de un grano

La estrategia de colocación para un tipo de grano se configura agregando el atributo adecuado en la clase del grano. Los atributos pertinentes se especifican en las secciones de las estrategias de colocación.

Ejemplo de estrategia de colocación personalizada

En primer lugar, defina una clase que implemente la interfaz IPlacementDirector, lo que requiere un único método. En este ejemplo, se supone que tiene una función GetSiloNumber definida que devolverá un número de silo según el Guid del grano que se va a crear.

public class SamplePlacementStrategyFixedSiloDirector : IPlacementDirector
{
    public Task<SiloAddress> OnAddActivation(
        PlacementStrategy strategy,
        PlacementTarget target,
        IPlacementContext context)
    {
        var silos = context.GetCompatibleSilos(target).OrderBy(s => s).ToArray();
        int silo = GetSiloNumber(target.GrainIdentity.PrimaryKey, silos.Length);

        return Task.FromResult(silos[silo]);
    }
}

Después, debe definir dos clases para permitir que las clases del grano se asignen a la estrategia:

[Serializable]
public sealed class SamplePlacementStrategy : PlacementStrategy
{
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class SamplePlacementStrategyAttribute : PlacementAttribute
{
    public SamplePlacementStrategyAttribute() :
        base(new SamplePlacementStrategy())
    {
    }
}

Después, simplemente etiquete las clases del grano con las que quiera usar esta estrategia con el atributo:

[SamplePlacementStrategy]
public class MyGrain : Grain, IMyGrain
{
    // ...
}

Y, por último, registre la estrategia al compilar SiloHost:

private static async Task<ISiloHost> StartSilo()
{
    var builder = new HostBuilder(c =>
    {
        // normal configuration methods omitted for brevity
        c.ConfigureServices(ConfigureServices);
    });

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

private static void ConfigureServices(IServiceCollection services)
{
    services.AddSingletonNamedService<
        PlacementStrategy, SamplePlacementStrategy>(
            nameof(SamplePlacementStrategy));

    services.AddSingletonKeyedService<
        Type, IPlacementDirector, SamplePlacementStrategyFixedSiloDirector>(
            typeof(SamplePlacementStrategy));
}

Para obtener un segundo ejemplo sencillo que muestra un uso adicional del contexto de selección de ubicación, consulte en PreferLocalPlacementDirector el repositorio de origen de Orleans.