Hospedaje con Service Fabric

Orleans se puede hospedar en Azure Service Fabric mediante los paquetes NuGet Microsoft.ServiceFabric.Services y Microsoft.Orleans.Server. Los silos deben hospedarse como servicios sin estado ni particiones, ya que Orleans administra por sí mismo la distribución de granos. Otras opciones de hospedaje, como particionadas y con estado, son más complejas y no ofrecen ninguna ventaja sin personalización adicional por parte del desarrollador. Se recomienda hospedar Orleans sin particiones y sin estado.

Servicio sin estado de Service Fabric como silo

Tanto si va a crear una nueva aplicación de Service Fabric como si va a agregar Orleans a una existente, necesitará las referencias de paquete Microsoft.ServiceFabric.Services y Microsoft.Orleans.Server en el proyecto. El proyecto de servicio sin estado necesita una implementación en ICommunicationListener y una subclase de StatelessService.

El ciclo de vida del silo sigue el ciclo de vida típico del agente de escucha de comunicación:

Puesto que los silos de Orleans son capaces de vivir dentro de los límites de IHost, la implementación de ICommunicationListener es un contenedor alrededor de IHost. IHost se inicializa en el OpenAsync método y finaliza correctamente en el CloseAsync método:

ICommunicationListener IHostinteracciones
OpenAsync Se crea la instancia IHost y se realiza una llamada a StartAsync.
CloseAsync Se espera una llamada a StopAsync en la instancia de host.
Abort Una llamada a StopAsync se evalúa con fuerza, con GetAwaiter().GetResult().

Compatibilidad con clústeres

La compatibilidad oficial de agrupación en clústeres está disponible en varios paquetes, entre los que se incluyen:

También hay varios paquetes de terceros disponibles para otros servicios, como CosmosDB, Kubernetes, Redis y Aerospike. Para más información, consulte Administración de clústeres en Orleans.

Proyecto de ejemplo

En el proyecto de servicio sin estado, implemente la interfaz ICommunicationListener como se muestra en el ejemplo siguiente:

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 HostedServiceCommunicationListener clase acepta un Func<Task<IHost>> createHost parámetro de constructor. Esto se usa más adelante para crear la instancia IHost en el método OpenAsync.

La siguiente parte del proyecto de servicio sin estado es implementar la clase StatelessService. En el ejemplo siguiente se muestra la subclase de la StatelessService clase:

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

En el ejemplo anterior, la clase OrleansHostedStatelessService es responsable de producir una instancia ICommunicationListener. El método CreateServiceInstanceListeners es llamado por el tiempo de ejecución de Service Fabric cuando se inicializa el servicio.

Al extraer estas dos clases juntas, en el ejemplo siguiente se muestra el archivo Program.cs completo del proyecto de servicio sin estado:

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

En el código anterior:

  • El método ServiceRuntime.RegisterServiceAsync registra la clase OrleansHostedStatelessService con el entorno de ejecución de Service Fabric.
  • Se usa un delegado CreateHostAsync para crear la instancia IHost.