Host mit Service Fabric

Orleans kann in Azure Service Fabric mit den Paketen Microsoft.ServiceFabric.Services und Microsoft.Orleans.Server NuGet gehostet werden. Silos sollten als nicht partitionierte, zustandslose Dienste gehostet werden, da Orleans die Verteilung der Aggregationsintervalle selbst übernimmt. Andere Hostingoptionen, z. B. partitioniert und zustandsbehaftet, sind komplexer und erzielen keine Vorteile ohne zusätzliche Anpassungen von Seiten des Entwicklers. Es wird empfohlen, nicht partitionierte und zustandslose Orleans zu hosten.

Zustandsloser Service Fabric-Dienst als Silo

Ganz gleich, ob Sie eine neue Service Fabric-Anwendung erstellen oder Orleans zu einer vorhandenen hinzufügen, sie benötigen beide Paketverweise Microsoft.ServiceFabric.Services und Microsoft.Orleans.Server in Ihrem Projekt. Das zustandslose Dienstprojekt benötigt eine Implementierung für ICommunicationListener und eine Unterklasse von StatelessService.

Der Silo-Lebenszyklus folgt dem typischen Lebenszyklus des Kommunikationslisteners:

Da Orleans-Silos in der Lage sind, innerhalb der Grenzen der IHost, die Umsetzung der ICommunicationListener ist ein Wrapper um IHost. IHost wird in der OpenAsync-Methode initialisiert und in der CloseAsync-Methode ordnungsgemäß beendet:

ICommunicationListener IHost-Interaktionen
OpenAsync Die IHost-Instanz wird erstellt und ein Aufruf für StartAsync erfolgt.
CloseAsync Es wird ein Aufruf für StopAsync an die Hostinstanz erwartet.
Abort Ein Aufruf für StopAsync wird erzwungen ausgewertet, mit GetAwaiter().GetResult().

Unterstützung für Cluster

Offizieller Clustering-Support steht in verschiedenen Paketen zur Verfügung, darunter:

Es gibt auch mehrere Drittanbieterpakete für andere Dienste wie CosmosDB, Kubernetes, Redis und Aerospike. Weitere Informationen finden Sie unter Clusterverwaltung in Orleans.

Beispielprojekt

Implementieren Sie die ICommunicationListener-Schnittstelle im zustandslosen Dienstprojekt, wie im folgenden Beispiel gezeigt:

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

Die HostedServiceCommunicationListener Klasse akzeptiert einen Func<Task<IHost>> createHost-Konstruktparameter. Dies wird später verwendet, um die IHost-Instanz in der OpenAsync-Methode zu erstellen.

Der nächste Teil des zustandslosen Dienstprojekts besteht darin, die StatelessService-Klasse zu implementieren. Das folgende Beispiel zeigt die Unterklasse der StatelessService-Klasse:

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

Im vorherigen Beispiel ist die OrleansHostedStatelessService-Klasse für das Anhalten einer ICommunicationListener-Instanz verantwortlich. Die CreateServiceInstanceListeners-Methode wird von der Service Fabric-Laufzeit aufgerufen, wenn der Dienst initialisiert wird.

Wenn Sie diese beiden Klassen zusammenziehen, zeigt das folgende Beispiel die vollständige Datei Program.cs des zustandslosen Dienstprojekts:

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

Im obigen Code:

  • Die ServiceRuntime.RegisterServiceAsync-Methode registriert die OrleansHostedStatelessService-Klasse mit der Service Fabric-Laufzeit.
  • Das CreateHostAsync-Delegat zum Erstellen der IHost-Instanz.