Introducción al ciclo de vida de los silos de Orleans

Los silos de Orleans usan un ciclo de vida observable para el inicio y apagado ordenado de los sistemas Orleans, así como componentes de capa de aplicación. Para más información sobre los detalles de implementación, consulte Ciclo de vida de Orleans.

Fases

Los clientes de silos y clústeres de Orleans usan un conjunto común de fases de ciclo de vida de servicio.

public static class ServiceLifecycleStage
{
    public const int First = int.MinValue;
    public const int RuntimeInitialize = 2_000;
    public const int RuntimeServices = 4_000;
    public const int RuntimeStorageServices = 6_000;
    public const int RuntimeGrainServices = 8_000;
    public const int ApplicationServices = 10_000;
    public const int BecomeActive = Active - 1;
    public const int Active = 20_000;
    public const int Last = int.MaxValue;
}

Registro

Debido a la inversión de control, donde los participantes se unen al ciclo de vida en lugar del ciclo de vida que tiene un conjunto centralizado de pasos de inicialización, no siempre está claro del código lo que es el orden de inicio y apagado. Para ayudar a solucionar esto, se ha agregado el registro antes del inicio del silo para informar de qué componentes participan en cada fase. Estos registros se registran en el nivel de registro de información del Orleans.Runtime.SiloLifecycleSubject registrador. Por ejemplo:

Information, Orleans.Runtime.SiloLifecycleSubject, "Stage 2000: Orleans.Statistics.PerfCounterEnvironmentStatistics, Orleans.Runtime.InsideRuntimeClient, Orleans.Runtime.Silo"

Information, Orleans.Runtime.SiloLifecycleSubject, "Stage 4000: Orleans.Runtime.Silo"

Information, Orleans.Runtime.SiloLifecycleSubject, "Stage 10000: Orleans.Runtime.Versions.GrainVersionStore, Orleans.Storage.AzureTableGrainStorage-Default, Orleans.Storage.AzureTableGrainStorage-PubSubStore"

Además, la información de tiempo y error se registran de forma similar para cada componente por fase. Por ejemplo:

Information, Orleans.Runtime.SiloLifecycleSubject, "Lifecycle observer Orleans.Runtime.InsideRuntimeClient started in stage 2000 which took 33 Milliseconds."

Information, Orleans.Runtime.SiloLifecycleSubject, "Lifecycle observer Orleans.Statistics.PerfCounterEnvironmentStatistics started in stage 2000 which took 17 Milliseconds."

Participación del ciclo de vida del silo

La lógica de la aplicación puede participar en el ciclo de vida del silo registrando un servicio participante en el contenedor de servicios del silo. El servicio debe registrarse como un donde ILifecycleParticipant<TLifecycleObservable>T es un ISiloLifecycle.

public interface ISiloLifecycle : ILifecycleObservable
{
}

public interface ILifecycleParticipant<TLifecycleObservable>
    where TLifecycleObservable : ILifecycleObservable
{
    void Participate(TLifecycleObservable lifecycle);
}

Cuando se inicia el silo, todos los participantes (ILifecycleParticipant<ISiloLifecycle>) del contenedor podrán participar llamando a su ILifecycleParticipant<TLifecycleObservable>.Participate comportamiento. Una vez que todos hayan tenido la oportunidad de participar, el ciclo de vida observable del silo iniciará todas las fases en orden.

Ejemplo

Con la introducción del ciclo de vida del silo, los proveedores de arranque, que se usan para permitir que los desarrolladores de aplicaciones inserten lógica en la fase de inicialización del proveedor, ya no son necesarios, ya que la lógica de la aplicación ahora se puede insertar en cualquier fase del inicio del silo. Sin embargo, agregamos una fachada de «tarea de inicio» para ayudar a la transición para los desarrolladores que habían estado usando proveedores de arranque. Como ejemplo de cómo se pueden desarrollar los componentes que participan en el ciclo de vida del silo, veremos la fachada de la tarea de inicio.

La tarea de inicio solo debe heredar de ILifecycleParticipant<ISiloLifecycle> y suscribir la lógica de la aplicación al ciclo de vida del silo en la fase especificada.

class StartupTask : ILifecycleParticipant<ISiloLifecycle>
{
    private readonly IServiceProvider _serviceProvider;
    private readonly Func<IServiceProvider, CancellationToken, Task> _startupTask;
    private readonly int _stage;

    public StartupTask(
        IServiceProvider serviceProvider,
        Func<IServiceProvider, CancellationToken, Task> startupTask,
        int stage)
    {
        _serviceProvider = serviceProvider;
        _startupTask = startupTask;
        _stage = stage;
    }

    public void Participate(ISiloLifecycle lifecycle)
    {
        lifecycle.Subscribe<StartupTask>(
            _stage,
            cancellation => _startupTask(_serviceProvider, cancellation));
    }
}

Desde la implementación anterior, podemos ver que en la Participate(...) llamada se suscribe al ciclo de vida del silo en la fase configurada, pasando la devolución de llamada de la aplicación en lugar de su lógica de inicialización. Los componentes que deben inicializarse en una fase determinada proporcionarían su devolución de llamada, pero el patrón es el mismo. Ahora que tenemos un StartupTask que garantizará que se llame al enlace de la aplicación en la fase configurada, es necesario asegurarse de que StartupTask participa en el ciclo de vida del silo.

Para ello, solo es necesario registrarlo en el contenedor. Lo hacemos con una función de extensión en ISiloHostBuilder:

public static ISiloHostBuilder AddStartupTask(
    this ISiloHostBuilder builder,
    Func<IServiceProvider, CancellationToken, Task> startupTask,
    int stage = ServiceLifecycleStage.Active)
{
    builder.ConfigureServices(services =>
        services.AddTransient<ILifecycleParticipant<ISiloLifecycle>>(
            serviceProvider =>
                new StartupTask(
                    serviceProvider, startupTask, stage)));

    return builder;
}

Al registrar startupTask en el contenedor de servicios del silo como la interfaz ILifecycleParticipant<ISiloLifecycle>de marcador, esto indica al silo que este componente debe participar en el ciclo de vida del silo.