Graindienste

Graindienste sind remote zugängliche, partitionierte Dienste zur Unterstützung der Funktionalitätsgrains. Jede Instanz eines Graindiensts ist verantwortlich für einige Grains, und diese Grains können einen Verweis auf den Graindienst abrufen, der derzeit für die Wartung mithilfe eines GrainServiceClient-Elements verantwortlich ist.

Graindienste werden in Szenarios verwendet, in denen die Verantwortung für die Verwaltung von Grains um den Orleans-Cluster herum verteilt werden sollte. Beispielsweise werden Orleans-Erinnerungen mithilfe von Graindiensten implementiert: Jeder Silo ist für die Verarbeitung von Erinnerungsvorgängen für eine Teilmenge von Grains verantwortlich und benachrichtigt diese Grains, wenn die entsprechenden Erinnerungen ausgelöst werden.

Graindienste werden für Silos konfiguriert und beim Start des Silos initialisiert, bevor die Siloinitialisierung abgeschlossen ist. Sie werden nicht erfasst, wenn sie sich im Leerlauf befinden, und ihre Lebensdauer verlängert sich stattdessen um die Lebensdauer des Silos selbst.

Erstellen einer GrainService-Klasse

Eine GrainService-Klasse ist ein spezielles Grain, das keine stabile Identität aufweist und vom Start bis zum Herunterfahren in jedem Silo ausgeführt wird. Bei der Implementierung einer IGrainService-Schnittstelle sind mehrere Schritte erforderlich.

  1. Definieren Sie die Kommunikationsschnittstelle des Graindiensts. Die Schnittstelle einer GrainService-Klasse wird mit den gleichen Prinzipien erstellt, die Sie zum Erstellen der Schnittstelle eines Grains verwenden würden.

    public interface IDataService : IGrainService
    {
        Task MyMethod();
    }
    
  2. Erstellen Sie den DataService-Graindienst. Sie sollten wissen, dass Sie auch eine IGrainFactory-Schnittstelle einfügen können, um Grainaufrufe über die GrainService-Klasse durchzuführen.

    [Reentrant]
    public class DataService : GrainService, IDataService
    {
        readonly IGrainFactory _grainFactory;
    
        public DataService(
            IServiceProvider services,
            GrainId id,
            Silo silo,
            ILoggerFactory loggerFactory,
            IGrainFactory grainFactory)
            : base(id, silo, loggerFactory)
        {
            _grainFactory = grainFactory;
        }
    
        public override Task Init(IServiceProvider serviceProvider) =>
            base.Init(serviceProvider);
    
        public override Task Start() => base.Start();
    
        public override Task Stop() => base.Stop();
    
        public Task MyMethod()
        {
            // TODO: custom logic here.
            return Task.CompletedTask;
        }
    }
    
    [Reentrant]
    public class DataService : GrainService, IDataService
    {
        readonly IGrainFactory _grainFactory;
    
        public DataService(
            IServiceProvider services,
            IGrainIdentity id,
            Silo silo,
            ILoggerFactory loggerFactory,
            IGrainFactory grainFactory)
            : base(id, silo, loggerFactory)
        {
            _grainFactory = grainFactory;
        }
    
        public override Task Init(IServiceProvider serviceProvider) =>
            base.Init(serviceProvider);
    
        public override Task Start() => base.Start();
    
        public override Task Stop() => base.Stop();
    
        public Task MyMethod()
        {
            // TODO: custom logic here.
            return Task.CompletedTask;
        }
    }
    
  3. Erstellen Sie eine Schnittstelle für die GrainServiceClient<TGrainService>-Klasse GrainServiceClient, die von anderen Grains zum Herstellen einer Verbindung mit der GrainService-Klasse verwendet werden soll.

    public interface IDataServiceClient : IGrainServiceClient<IDataService>, IDataService
    {
    }
    
  1. Erstellen Sie den Graindienstclient. Clients fungieren in der Regel als Proxys für die Graindienste, die sie zum Ziel haben, sodass Sie in der Regel eine Methode für jede Methode für den Zieldienst hinzufügen. Diese Methoden müssen einen Verweis auf den Graindienst abrufen, auf den sie ausgelegt sind, damit sie ihn aufrufen können. Die Basisklasse GrainServiceClient<T> stellt mehrere Überladungen der GetGrainService-Methode bereit, die einen Grainverweis zurückgeben können, der einer GrainId, einem numerischen Hash (uint) oder einer SiloAddress entspricht. Die letzten beiden Überladungen gelten für erweiterte Szenarios, in denen Entwickler*innen einen anderen Mechanismus verwenden möchten, um Hosts die Verantwortung zu übertragen oder einen Host direkt zu adressieren. Im folgenden Beispielcode definieren Sie die Eigenschaft GrainService, die IDataService für das Grain zurückgibt, das den DataServiceClient aufruft. Dazu verwenden Sie die Überladung GetGrainService(GrainId) in Verbindung mit der CurrentGrainReference-Eigenschaft.

    public class DataServiceClient : GrainServiceClient<IDataService>, IDataServiceClient
    {
        public DataServiceClient(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
        }
    
        // For convenience when implementing methods, you can define a property which gets the IDataService
        // corresponding to the grain which is calling the DataServiceClient.
        private IDataService GrainService => GetGrainService(CurrentGrainReference.GrainId);
    
        public Task MyMethod() => GrainService.MyMethod();
    }
    
  1. Erstellen Sie den tatsächlichen Graindienstclient. Er fungiert einfach als Proxy für den Datendienst. Leider müssen Sie manuell alle Methodenzuordnungen eingeben, die nur aus einer einfachen Zeile bestehen.

    public class DataServiceClient : GrainServiceClient<IDataService>, IDataServiceClient
    {
        public DataServiceClient(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
        }
    
        public Task MyMethod() => GrainService.MyMethod();
    }
    
  1. Implementieren Sie den Graindienstclient in die anderen Grains, die ihn benötigen. Es ist nicht garantiert, dass der GrainServiceClient auf die GrainService-Klasse im lokalen Silo zugreifen kann. Ihr Befehl wurde möglicherweise an eine beliebige GrainService-Klasse für ein beliebiges Silo im Cluster gesendet.

    public class MyNormalGrain: Grain<NormalGrainState>, INormalGrain
    {
        readonly IDataServiceClient _dataServiceClient;
    
        public MyNormalGrain(
            IGrainActivationContext grainActivationContext,
            IDataServiceClient dataServiceClient) =>
                _dataServiceClient = dataServiceClient;
    }
    
  2. Konfigurieren Sie den Graindienst und den Graindienstclient im Silo. Sie müssen dies tun, damit der Silo die GrainService-Klasse startet.

    (ISiloHostBuilder builder) =>
        builder.ConfigureServices(
            services => services.AddGrainService<DataService>()
                                .AddSingleton<IDataServiceClient, DataServiceClient>());
    

Zusätzliche Hinweise

Es gibt eine Erweiterungsmethode für GrainServicesSiloBuilderExtensions.AddGrainService, mit der Graindienste registriert werden.

services.AddSingleton<IGrainService>(
    serviceProvider => GrainServiceFactory(grainServiceType, serviceProvider));

Das Silo ruft IGrainService-Typen vom Dienstanbieter beim Start von orleans/src/Orleans.Runtime/Silo/Silo.cs ab.

var grainServices = this.Services.GetServices<IGrainService>();

Das GrainService-Projekt sollte auf das NuGet-Paket Microsoft.Orleans.Runtime verweisen.

Das GrainService-Projekt sollte auf das NuGet-Paket Microsoft.Orleans.OrleansRuntime verweisen.

Damit dies funktioniert, müssen Sie sowohl den Dienst als auch den entsprechenden Client registrieren. Der Code sieht ungefähr wie folgt aus:

var builder = new HostBuilder()
    .UseOrleans(c =>
    {
        c.AddGrainService<DataService>()  // Register GrainService
        .ConfigureServices(services =>
        {
            // Register Client of GrainService
            services.AddSingleton<IDataServiceClient, DataServiceClient>();
        });
    })