Host con Service Fabric

Orleans può essere ospitato in Azure Service Fabric usando i pacchetti NuGet Microsoft.ServiceFabric.Services e Microsoft.Orleans. Server. I silo devono essere ospitati come servizi senza stato, poiché Orleans gestisce la distribuzione dei grani stessi. Altre opzioni di hosting, ad esempio partizionate e con stato, sono più complesse e non offrono alcun vantaggio senza alcuna personalizzazione aggiuntiva da parte dello sviluppatore. È consigliabile ospitare Orleans senza stato e senza partizionamento.

Servizio senza stato di Service Fabric come silo

Sia che si stia creando una nuova applicazione Service Fabric, sia che si stia aggiungendo Orleans a una esistente, è necessario avere i riferimenti ai pacchetti Microsoft.ServiceFabric.Services e Microsoft.Orleans.Server nel progetto. Il progetto di servizio senza stato richiede un'implementazione in ICommunicationListener e una sottoclasse dell'oggetto StatelessService.

Il ciclo di vita del silo segue il ciclo di vita tipico del listener di comunicazione:

Poiché i silo Orleans sono in grado di vivere entro i confini di IHost, l'implementazione di ICommunicationListener è un wrapper intorno a IHost. L'oggetto IHost viene inizializzato nel metodo OpenAsync e terminato normalmente nel metodo CloseAsync:

ICommunicationListener InterazioniIHost
OpenAsync Viene creata l'istanza IHost e viene effettuata una chiamata a StartAsync.
CloseAsync È attesa una chiamata a StopAsync nell'istanza host.
Abort Una chiamata a StopAsync viene valutata in modo forzato, con GetAwaiter().GetResult().

Supporto di cluster

Il supporto ufficiale per il clustering è disponibile da vari pacchetti, tra cui:

Sono disponibili anche diversi pacchetti di terze parti per altri servizi, ad esempio CosmosDB, Kubernetes, Redis e Aerospike. Per altre informazioni, vedere Gestione del cluster in Orleans.

Progetto di esempio

Nel progetto di servizio senza stato implementare l'interfaccia ICommunicationListener come illustrato nell'esempio seguente:

using Microsoft.Extensions.Hosting;
using Microsoft.ServiceFabric.Services.Communication.Runtime;

namespace ServiceFabric.HostingExample;

internal sealed class HostedServiceCommunicationListener : ICommunicationListener
{
    private IHost? _host;
    private readonly Func<Task<IHost>> _createHost;

    public HostedServiceCommunicationListener(Func<Task<IHost>> createHost) =>
        _createHost = createHost ?? throw new ArgumentNullException(nameof(createHost));

    /// <inheritdoc />
    public async Task<string?> OpenAsync(CancellationToken cancellationToken)
    {
        try
        {
            _host = await _createHost.Invoke();
            await _host.StartAsync(cancellationToken);
        }
        catch
        {
            Abort();
            throw;
        }

        // This service does not expose any endpoints to Service Fabric for discovery by others.
        return null;
    }

    /// <inheritdoc />
    public async Task CloseAsync(CancellationToken cancellationToken)
    {
        if (_host is { } host)
        {
            await host.StopAsync(cancellationToken);
        }

        _host = null;
    }

    /// <inheritdoc />
    public void Abort()
    {
        IHost? host = _host;
        if (host is null)
        {
            return;
        }

        using CancellationTokenSource cancellation = new();
        cancellation.Cancel(false);

        try
        {
            host.StopAsync(cancellation.Token).GetAwaiter().GetResult();
        }
        catch
        {
            // Ignore.
        }
        finally
        {
            _host = null;
        }
    }
}

La classe HostedServiceCommunicationListener accetta un parametro del costruttore Func<Task<IHost>> createHost. Viene usato successivamente per creare l'istanza di IHost nel metodo OpenAsync.

La parte successiva del progetto di servizio senza stato consiste nell'implementare la classe StatelessService. Nell'esempio seguente viene illustrata la sottoclasse della classe StatelessService:

using System.Fabric;
using Microsoft.Extensions.Hosting;
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;

namespace ServiceFabric.HostingExample;

public sealed class OrleansHostedStatelessService : StatelessService
{
    private readonly Func<StatelessServiceContext, Task<IHost>> _createHost;

    public OrleansHostedStatelessService(
        Func<StatelessServiceContext, Task<IHost>> createHost, StatelessServiceContext serviceContext)
        : base(serviceContext) =>
        _createHost = createHost ?? throw new ArgumentNullException(nameof(createHost));  

    /// <inheritdoc/>
    protected sealed override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        // Create a listener which creates and runs an IHost
        yield return new ServiceInstanceListener(
            context => new HostedServiceCommunicationListener(() => _createHost(context)),
            nameof(HostedServiceCommunicationListener));
    }
}

Nell'esempio precedente la classe OrleansHostedStatelessService è responsabile della resa di un'istanza di ICommunicationListener. Il metodo CreateServiceInstanceListeners viene chiamato dal runtime di Service Fabric quando il servizio viene inizializzato.

Eseguendo il pull di queste due classi, nell'esempio seguente viene illustrato il file Program.cs del progetto di servizio senza stato completo:

using System.Fabric;
using Microsoft.Extensions.Hosting;
using Microsoft.ServiceFabric.Services.Runtime;
using ServiceFabric.HostingExample;

try
{
    // The ServiceManifest.XML file defines one or more service type names.
    // Registering a service maps a service type name to a .NET type.
    // When Service Fabric creates an instance of this service type,
    // an instance of the class is created in this host process.
    await ServiceRuntime.RegisterServiceAsync(
        "Orleans.ServiceFabric.Stateless",
        context => new OrleansHostedStatelessService(
            CreateHostAsync, context));

    ServiceEventSource.Current.ServiceTypeRegistered(
        Environment.ProcessId,
        typeof(OrleansHostedStatelessService).Name);

    // Prevents this host process from terminating so services keep running.
    await Task.Delay(Timeout.Infinite);
}
catch (Exception ex)
{
    ServiceEventSource.Current.ServiceHostInitializationFailed(
        ex.ToString());
    throw;
}

static async Task<IHost> CreateHostAsync(StatelessServiceContext context)
{
    await Task.CompletedTask;

    return Host.CreateDefaultBuilder()
        .UseOrleans((_, builder) =>
        {
            // TODO, Use real storage, something like table storage
            // or SQL Server for clustering.
            builder.UseLocalhostClustering();

            // Service Fabric manages port allocations, so update the 
            // configuration using those ports. Gather configuration from 
            // Service Fabric.
            var activation = context.CodePackageActivationContext;
            var endpoints = activation.GetEndpoints();

            // These endpoint names correspond to TCP endpoints 
            // specified in ServiceManifest.xml
            var siloEndpoint = endpoints["OrleansSiloEndpoint"];
            var gatewayEndpoint = endpoints["OrleansProxyEndpoint"];
            var hostname = context.NodeContext.IPAddressOrFQDN;
            builder.ConfigureEndpoints(hostname,
                siloEndpoint.Port, gatewayEndpoint.Port);
        })
        .Build();
}

Nel codice precedente:

  • Il metodo ServiceRuntime.RegisterServiceAsync registra la classe OrleansHostedStatelessService con il runtime di Service Fabric.
  • Il delegato CreateHostAsync viene usato per creare l'istanza di IHost.