Compartilhar via


Visão geral do ciclo de vida do silo no Orleans

Os silos do Orleans usam um ciclo de vida observável para inicialização e desligamento ordenados de sistemas Orleans, bem como componentes da camada de aplicativo. Para saber mais sobre os detalhes da implementação, consulte Ciclo de vida do Orleans.

Estágios

Os clientes de silo e cluster do Orleans usam um conjunto comum de estágios do ciclo de vida de serviço.

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;
}

Log

Devido à inversão de controle, no qual os participantes ingressam no ciclo de vida em vez de no ciclo de vida que tem algum conjunto centralizado de etapas de inicialização, nem sempre fica claro no código qual é a ordem de inicialização/desligamento. Para ajudar a resolver isso, o log foi adicionado antes da inicialização do silo para relatar quais componentes estão participando em cada estágio. Esses logs são registrados no nível de log de informações no agente Orleans.Runtime.SiloLifecycleSubject. Por exemplo:

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"

Além disso, as informações de tempo e erro são registradas da mesma forma para cada componente por estágio. Por exemplo:

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."

Participação no ciclo de vida do Silo

A lógica do aplicativo pode participar do ciclo de vida do silo registrando um serviço participante no contêiner de serviço do silo. O serviço deve ser registrado como um ILifecycleParticipant<TLifecycleObservable> em que T é um ISiloLifecycle.

public interface ISiloLifecycle : ILifecycleObservable
{
}

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

Quando o silo for iniciado, todos os participantes (ILifecycleParticipant<ISiloLifecycle>) no contêiner poderão participar chamando seu comportamento ILifecycleParticipant<TLifecycleObservable>.Participate. Depois que todos tiverem a oportunidade de participar, o ciclo de vida observável do silo iniciará todas as etapas em ordem.

Exemplo

Com a introdução do ciclo de vida do silo, os provedores de inicialização, que costumavam permitir que os desenvolvedores de aplicativos injetassem lógica na fase de inicialização do provedor, não são mais necessários, já que a lógica do aplicativo agora pode ser injetada em qualquer estágio da inicialização do silo. No entanto, adicionamos uma fachada de "tarefa de inicialização" para ajudar na transição para desenvolvedores que usavam provedores de inicialização. Um exemplo de como os componentes que participam do ciclo de vida do silo podem ser desenvolvidos, examinaremos a fachada da tarefa de inicialização.

A tarefa de inicialização só precisa herdar de ILifecycleParticipant<ISiloLifecycle> e assinar a lógica do aplicativo no ciclo de vida do silo no estágio especificado.

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));
    }
}

Na implementação acima, podemos ver que na chamada de Participate(...), ele assina o ciclo de vida do silo no estágio configurado, passando o retorno de chamada do aplicativo em vez de sua lógica de inicialização. Componentes que precisam ser inicializados em um determinado estágio forneceriam seu retorno de chamada, mas o padrão é o mesmo. Agora que temos um StartupTask que garantirá que o gancho do aplicativo seja chamado no estágio configurado, precisamos garantir que o StartupTask participe do ciclo de vida do silo.

Para isso, só precisamos registrá-lo no contêiner. Fazemos isso com uma função de extensão no 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;
}

Ao registrar o StartupTask no contêiner de serviço do silo como a interface do marcador ILifecycleParticipant<ISiloLifecycle>, isso sinaliza para o silo que esse componente precisa participar do ciclo de vida do silo.