Héberger avec Service Fabric

Orleans peut être hébergé sur Azure Service Fabric à l’aide des packages NuGet Microsoft.ServiceFabric.Services et Microsoft.Orleans.Server. Les silos doivent être hébergés en tant que services non partitionnés et sans état, car Orleans gère lui-même la répartition des grains. Les autres options d’hébergement, telles que partitionné et avec état, sont plus complexes à mettre en œuvre et n’offrent aucun avantage sans personnalisation supplémentaire de la part du développeur. Pour Orleans, l’hébergement non partitionné et sans état est recommandé.

Service sans état Service Fabric en tant que silo

Que vous créiez une application Service Fabric ou que vous ajoutiez Orleans à une application existante, vous aurez besoin des références des packages Microsoft.ServiceFabric.Services et Microsoft.Orleans.Server dans votre projet. Le projet de service sans état a besoin d’une implémentation sur ICommunicationListener et d’une sous-classe de StatelessService.

Le cycle de vie du silo suit le cycle de vie de l’écouteur de communication classique :

Étant donné que les silos Orleans peuvent exister dans les limites de IHost, l’implémentation de ICommunicationListener consiste à établir un wrapper autour de IHost. Le IHost est initialisé dans la méthode OpenAsync et arrêté en douceur dans la méthode CloseAsync :

ICommunicationListener Interactions IHost
OpenAsync L’instance IHost est créée et un appel à StartAsync est effectué.
CloseAsync Un appel à StopAsync sur l’instance hôte est attendu.
Abort Un appel à StopAsync est évalué de force avec GetAwaiter().GetResult().

Prise en charge des clusters

La prise en charge officielle du clustering est disponible à partir de divers packages, notamment :

Il existe également plusieurs packages tiers disponibles pour d’autres services tels que CosmosDB, Kubernetes, Redis et Aerospike. Pour plus d’informations, consultez Gestion de cluster dans Orleans.

Exemple de projet

Dans le projet de service sans état, implémentez l’interface ICommunicationListener comme indiqué dans l’exemple suivant :

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 accepte un paramètre de constructeur Func<Task<IHost>> createHost. Elle est ensuite utilisée pour créer l’instance IHost dans la méthode OpenAsync.

La partie suivante du projet de service sans état consiste à implémenter la classe StatelessService. L’exemple suivant montre la sous-classe de la 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));
    }
}

Dans l’exemple précédent, la classe OrleansHostedStatelessService est chargée de générer une instance ICommunicationListener. La méthode CreateServiceInstanceListeners est appelée par le runtime Service Fabric lors de l’initialisation du service.

En regroupant ces deux classes, l’exemple suivant montre le fichier program.cs complet du projet de service sans état :

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

Dans le code précédent :

  • La méthodeServiceRuntime.RegisterServiceAsync enregistre la classe OrleansHostedStatelessService auprès du runtime Service Fabric.
  • Le délégué CreateHostAsync permet de créer l’instance IHost.