Share via


Externe communicatie van services in C# met Reliable Services

Voor services die niet zijn gekoppeld aan een bepaald communicatieprotocol of een bepaalde stack, zoals een web-API, Windows Communication Foundation of andere, biedt het Reliable Services-framework een mechanisme voor externe communicatie om snel en eenvoudig externe procedureaanroepen voor services in te stellen. In dit artikel wordt beschreven hoe u externe procedureaanroepen instelt voor services die zijn geschreven met C#.

Externe communicatie instellen voor een service

U kunt externe toegang voor een service in twee eenvoudige stappen instellen:

  1. Maak een interface voor uw service om te implementeren. Deze interface definieert de methoden die beschikbaar zijn voor een externe procedureaanroep op uw service. De methoden moeten asynchrone methoden zijn die taken retourneren. De interface moet worden geïmplementeerd Microsoft.ServiceFabric.Services.Remoting.IService om aan te geven dat de service een externe interface heeft.
  2. Gebruik een listener voor externe communicatie in uw service. Een listener voor externe communicatie is een ICommunicationListener implementatie die mogelijkheden voor externe communicatie biedt. De Microsoft.ServiceFabric.Services.Remoting.Runtime naamruimte bevat de extensiemethode CreateServiceRemotingInstanceListeners voor zowel stateless als stateful services die kunnen worden gebruikt om een listener voor externe communicatie te maken met behulp van het standaardprotocol voor externe communicatie.

Notitie

De Remoting naamruimte is beschikbaar als een afzonderlijk NuGet-pakket met de naam Microsoft.ServiceFabric.Services.Remoting.

De volgende staatloze service maakt bijvoorbeeld één methode beschikbaar om 'Hallo wereld' op te halen via een externe procedureaanroep.

using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Remoting;
using Microsoft.ServiceFabric.Services.Remoting.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;

public interface IMyService : IService
{
    Task<string> HelloWorldAsync();
}

class MyService : StatelessService, IMyService
{
    public MyService(StatelessServiceContext context)
        : base (context)
    {
    }

    public Task<string> HelloWorldAsync()
    {
        return Task.FromResult("Hello!");
    }

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
     return this.CreateServiceRemotingInstanceListeners();
    }
}

Notitie

De argumenten en de retourtypen in de service-interface kunnen eenvoudige, complexe of aangepaste typen zijn, maar ze moeten kunnen worden geserialiseerd door de .NET DataContractSerializer.

Externe servicemethoden aanroepen

Notitie

Als u meer dan één partitie gebruikt, moet ServiceProxy.Create() de juiste ServicePartitionKey worden opgegeven. Dit is niet nodig voor een scenario met één partitie.

Het aanroepen van methoden in een service met behulp van de externe stack wordt uitgevoerd met behulp van een lokale proxy naar de service via de Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy klasse. Met ServiceProxy de methode wordt een lokale proxy gemaakt met behulp van dezelfde interface die de service implementeert. Met die proxy kunt u methoden op de interface op afstand aanroepen.


IMyService helloWorldClient = ServiceProxy.Create<IMyService>(new Uri("fabric:/MyApplication/MyHelloWorldService"));

string message = await helloWorldClient.HelloWorldAsync();

Het framework voor externe communicatie doorgeeft uitzonderingen die door de service zijn gegenereerd aan de client. Als gevolg hiervan is de client, wanneer ServiceProxywordt gebruikt, verantwoordelijk voor het verwerken van de uitzonderingen die door de service worden gegenereerd.

Levensduur van serviceproxy

Het maken van een serviceproxy is een eenvoudige bewerking, zodat u er zoveel kunt maken als u nodig hebt. Serviceproxy-exemplaren kunnen opnieuw worden gebruikt zolang ze nodig zijn. Als een externe procedureaanroep een uitzondering genereert, kunt u hetzelfde proxy-exemplaar nog steeds opnieuw gebruiken. Elke serviceproxy bevat een communicatieclient die wordt gebruikt om berichten via de kabel te verzenden. Tijdens het aanroepen van externe oproepen worden interne controles uitgevoerd om te bepalen of de communicatieclient geldig is. Op basis van de resultaten van deze controles wordt de communicatieclient indien nodig opnieuw gemaakt. Als er een uitzondering optreedt, hoeft u daarom niet opnieuw te maken ServiceProxy.

Levensduur van serviceproxy factory

ServiceProxyFactory is een factory die proxy-exemplaren maakt voor verschillende externe interfaces. Als u de API ServiceProxyFactory.CreateServiceProxy gebruikt om een proxy te maken, maakt het framework een singleton-serviceproxy. Het is handig om er handmatig een te maken wanneer u IServiceRemotingClientFactory-eigenschappen moet overschrijven. Het maken van een fabriek is een dure bewerking. Een serviceproxyfactory onderhoudt een interne cache van de communicatieclient. Een best practice is om de serviceproxyfactory zo lang mogelijk in de cache te opslaan.

Verwerking van externe uitzonderingen

Alle externe uitzonderingen die door de service-API worden gegenereerd, worden teruggestuurd naar de client als AggregateException. Externe uitzonderingen moeten kunnen worden geserialiseerd door DataContract. Als dat niet zo is, genereert de proxy-API ServiceException met daarin de serialisatiefout.

De serviceproxy verwerkt alle failover-uitzonderingen voor de servicepartitie waarvoor deze is gemaakt. De eindpunten worden opnieuw omgezet als er failover-uitzonderingen zijn (niet-tijdelijke uitzonderingen) en de aanroep opnieuw wordt geprobeerd met het juiste eindpunt. Het aantal nieuwe pogingen voor failover-uitzonderingen is voor onbepaalde tijd. Als er tijdelijke uitzonderingen optreden, voert de proxy de aanroep opnieuw uit.

Standaardparameters voor opnieuw proberen worden geleverd door OperationRetrySettings.

Een gebruiker kan deze waarden configureren door het object OperationRetrySettings door te geven aan de Constructor ServiceProxyFactory.

De V2-stack voor externe communicatie gebruiken

Vanaf versie 2.8 van het NuGet-externe pakket hebt u de optie om de V2-stack voor externe communicatie te gebruiken. De externe V2-stack presteert beter. Het biedt ook functies zoals aangepaste serialisatie en meer pluggable API's. Sjablooncode blijft de externe V1-stack gebruiken. Externe communicatie V2 is niet compatibel met V1 (de vorige externe stack). Volg de instructies in het artikel Upgrade van V1 naar V2 om gevolgen voor de beschikbaarheid van de service te voorkomen.

De volgende benaderingen zijn beschikbaar om de V2-stack in te schakelen.

Een assembly-kenmerk gebruiken om de V2-stack te gebruiken

Met deze stappen wijzigt u de sjablooncode om de V2-stack te gebruiken met behulp van een assembly-kenmerk.

  1. Wijzig de eindpuntresource van "ServiceEndpoint" in "ServiceEndpointV2" het servicemanifest.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Gebruik de Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners extensiemethode om externe listeners te maken (gelijk voor zowel V1 als V2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Markeer de assembly die de externe interfaces bevat met een FabricTransportServiceRemotingProvider kenmerk.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2, RemotingClientVersion = RemotingClientVersion.V2)]
    

Er zijn geen codewijzigingen vereist in het clientproject. Bouw de clientassembly met de interface-assembly om ervoor te zorgen dat het eerder weergegeven assembly-kenmerk wordt gebruikt.

Expliciete V2-klassen gebruiken om de V2-stack te gebruiken

Als alternatief voor het gebruik van een assembly-kenmerk kan de V2-stack ook worden ingeschakeld met behulp van expliciete V2-klassen.

Met deze stappen wijzigt u de sjablooncode om de V2-stack te gebruiken met behulp van expliciete V2-klassen.

  1. Wijzig de eindpuntresource van "ServiceEndpoint" in "ServiceEndpointV2" het servicemanifest.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Gebruik FabricTransportServiceRemotingListener vanuit de Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime naamruimte.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Gebruik FabricTransportServiceRemotingClientFactory vanuit de Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client naamruimte om clients te maken.

    var proxyFactory = new ServiceProxyFactory((c) =>
           {
               return new FabricTransportServiceRemotingClientFactory();
           });
    

Upgraden van externe communicatie V1 naar externe communicatie V2

Voor een upgrade van V1 naar V2 zijn upgrades in twee stappen vereist. Volg de stappen in deze volgorde.

  1. Upgrade de V1-service naar de V2-service met behulp van dit kenmerk. Deze wijziging zorgt ervoor dat de service luistert op de V1- en V2-listener.

    a. Voeg een eindpuntresource met de naam ServiceEndpointV2 toe in het servicemanifest.

    <Resources>
      <Endpoints>
        <Endpoint Name="ServiceEndpointV2" />  
      </Endpoints>
    </Resources>
    

    b. Gebruik de volgende extensiemethode om een externe listener te maken.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return this.CreateServiceRemotingInstanceListeners();
    }
    

    c. Voeg een assembly-kenmerk toe aan externe interfaces om de V1- en V2-listener en de V2-client te gebruiken.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
    
    
  2. Upgrade de V1-client naar een V2-client met behulp van het V2-clientkenmerk. Deze stap zorgt ervoor dat de client de V2-stack gebruikt. Er is geen wijziging in het clientproject/de clientservice vereist. Het bouwen van clientprojecten met een bijgewerkte interfaceassembly is voldoende.

  3. Deze stap is optioneel. Gebruik het kenmerk V2-listener en voer vervolgens een upgrade uit van de V2-service. Deze stap zorgt ervoor dat de service alleen luistert op de V2-listener.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2, RemotingClientVersion = RemotingClientVersion.V2)]
    

De externe V2-stack (compatibel met interface) gebruiken

De V2-stack voor externe communicatie (interface-compatibel) staat bekend als V2_1 en is de meest recente versie. Het heeft alle functies van de V2 externe stack. De interfacestack is compatibel met de externe V1-stack, maar is niet achterwaarts compatibel met V2 en V1. Als u een upgrade wilt uitvoeren van V1 naar V2_1 zonder dat dit van invloed is op de beschikbaarheid van de service, volgt u de stappen in het artikel Upgraden van V1 naar V2 (compatibel met interface).

Een assembly-kenmerk gebruiken om de externe V2-stack (interface-compatibel) te gebruiken

Volg deze stappen om over te schakelen naar een V2_1 stack.

  1. Voeg een eindpuntresource toe met de naam 'ServiceEndpointV2_1' in het servicemanifest.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Gebruik de extensiemethode voor externe communicatie om een externe listener te maken.

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Voeg een assembly-kenmerk toe aan externe interfaces.

     [assembly:  FabricTransportServiceRemotingProvider(RemotingListenerVersion=  RemotingListenerVersion.V2_1, RemotingClientVersion= RemotingClientVersion.V2_1)]
    
    

Er zijn geen wijzigingen vereist in het clientproject. Bouw de clientassembly met de interface-assembly om ervoor te zorgen dat het vorige assembly-kenmerk wordt gebruikt.

Expliciete klassen voor externe communicatie gebruiken om een listener/client factory te maken voor de versie V2 (interface-compatibel)

Volg deze stappen:

  1. Voeg een eindpuntresource toe met de naam 'ServiceEndpointV2_1' in het servicemanifest.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Gebruik de externe V2-listener. De standaardnaam van de service-eindpuntresource die wordt gebruikt, is 'ServiceEndpointV2_1'. Deze moet worden gedefinieerd in het servicemanifest.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 var settings = new FabricTransportRemotingListenerSettings();
                 settings.UseWrappedMessage = true;
                 return new FabricTransportServiceRemotingListener(c, this,settings);
    
             })
         };
     }
    
  3. Gebruik de V2-clientfactory.

    var proxyFactory = new ServiceProxyFactory((c) =>
           {
             var settings = new FabricTransportRemotingSettings();
             settings.UseWrappedMessage = true;
             return new FabricTransportServiceRemotingClientFactory(settings);
           });
    

Upgraden van externe communicatie van V1 naar externe communicatie V2 (compatibel met interface)

Voor een upgrade van V1 naar V2 (interface-compatibel, ook wel V2_1 genoemd), zijn upgrades in twee stappen vereist. Volg de stappen in deze volgorde.

Notitie

Wanneer u een upgrade uitvoert van V1 naar V2, moet u ervoor zorgen dat de Remoting naamruimte is bijgewerkt voor gebruik van V2. Voorbeeld: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. Voer een upgrade uit van de V1-service naar V2_1 service met behulp van het volgende kenmerk. Deze wijziging zorgt ervoor dat de service luistert op de V1 en de V2_1 listener.

    a. Voeg een eindpuntresource toe met de naam 'ServiceEndpointV2_1' in het servicemanifest.

    <Resources>
      <Endpoints>
        <Endpoint Name="ServiceEndpointV2_1" />  
      </Endpoints>
    </Resources>
    

    b. Gebruik de volgende extensiemethode om een externe listener te maken.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return this.CreateServiceRemotingInstanceListeners();
    }
    

    c. Voeg een assembly-kenmerk toe aan externe interfaces om V1, V2_1 listener en V2_1-client te gebruiken.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    
    
  2. Upgrade de V1-client naar de V2_1-client met behulp van het kenmerk V2_1 client. Deze stap zorgt ervoor dat de client de V2_1-stack gebruikt. Er is geen wijziging in het clientproject/de clientservice vereist. Het bouwen van clientprojecten met een bijgewerkte interfaceassembly is voldoende.

  3. Deze stap is optioneel. Verwijder de versie van de V1-listener uit het kenmerk en voer vervolgens een upgrade uit van de V2-service. Deze stap zorgt ervoor dat de service alleen luistert op de V2-listener.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    

Aangepaste serialisatie gebruiken met een bericht met ingepakte externe toegang

Voor een bericht met ingepakte externe communicatie maken we één verpakt object met alle parameters als een veld erin. Volg deze stappen:

  1. Implementeer de IServiceRemotingMessageSerializationProvider interface voor implementatie voor aangepaste serialisatie. In dit codefragment ziet u hoe de implementatie eruitziet.

    public class ServiceRemotingJsonSerializationProvider : IServiceRemotingMessageSerializationProvider
    {
      public IServiceRemotingMessageBodyFactory CreateMessageBodyFactory()
      {
        return new JsonMessageFactory();
      }
    
      public IServiceRemotingRequestMessageBodySerializer CreateRequestMessageSerializer(Type serviceInterfaceType, IEnumerable<Type> requestWrappedType, IEnumerable<Type> requestBodyTypes = null)
      {
        return new ServiceRemotingRequestJsonMessageBodySerializer();
      }
    
      public IServiceRemotingResponseMessageBodySerializer CreateResponseMessageSerializer(Type serviceInterfaceType, IEnumerable<Type> responseWrappedType, IEnumerable<Type> responseBodyTypes = null)
      {
        return new ServiceRemotingResponseJsonMessageBodySerializer();
      }
    }
    
      class JsonMessageFactory : IServiceRemotingMessageBodyFactory
          {
    
            public IServiceRemotingRequestMessageBody CreateRequest(string interfaceName, string methodName, int numberOfParameters, object wrappedRequestObject)
            {
              return new JsonBody(wrappedRequestObject);
            }
    
            public IServiceRemotingResponseMessageBody CreateResponse(string interfaceName, string methodName, object wrappedRequestObject)
            {
              return new JsonBody(wrappedRequestObject);
            }
          }
    
    class ServiceRemotingRequestJsonMessageBodySerializer : IServiceRemotingRequestMessageBodySerializer
      {
          private JsonSerializer serializer;
    
          public ServiceRemotingRequestJsonMessageBodySerializer()
          {
            serializer = JsonSerializer.Create(new JsonSerializerSettings()
            {
              TypeNameHandling = TypeNameHandling.All
              });
            }
    
            public IOutgoingMessageBody Serialize(IServiceRemotingRequestMessageBody serviceRemotingRequestMessageBody)
           {
             if (serviceRemotingRequestMessageBody == null)
             {
               return null;
             }          
             using (var writeStream = new MemoryStream())
             {
               using (var jsonWriter = new JsonTextWriter(new StreamWriter(writeStream)))
               {
                 serializer.Serialize(jsonWriter, serviceRemotingRequestMessageBody);
                 jsonWriter.Flush();
                 var bytes = writeStream.ToArray();
                 var segment = new ArraySegment<byte>(bytes);
                 var segments = new List<ArraySegment<byte>> { segment };
                 return new OutgoingMessageBody(segments);
               }
             }
            }
    
            public IServiceRemotingRequestMessageBody Deserialize(IIncomingMessageBody messageBody)
           {
             using (var sr = new StreamReader(messageBody.GetReceivedBuffer()))
             {
               using (JsonReader reader = new JsonTextReader(sr))
               {
                 var ob = serializer.Deserialize<JsonBody>(reader);
                 return ob;
               }
             }
           }
          }
    
    class ServiceRemotingResponseJsonMessageBodySerializer : IServiceRemotingResponseMessageBodySerializer
     {
       private JsonSerializer serializer;
    
      public ServiceRemotingResponseJsonMessageBodySerializer()
      {
        serializer = JsonSerializer.Create(new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.All
          });
        }
    
        public IOutgoingMessageBody Serialize(IServiceRemotingResponseMessageBody responseMessageBody)
        {
          if (responseMessageBody == null)
          {
            return null;
          }
    
          using (var writeStream = new MemoryStream())
          {
            using (var jsonWriter = new JsonTextWriter(new StreamWriter(writeStream)))
            {
              serializer.Serialize(jsonWriter, responseMessageBody);
              jsonWriter.Flush();
              var bytes = writeStream.ToArray();
              var segment = new ArraySegment<byte>(bytes);
              var segments = new List<ArraySegment<byte>> { segment };
              return new OutgoingMessageBody(segments);
            }
          }
        }
    
        public IServiceRemotingResponseMessageBody Deserialize(IIncomingMessageBody messageBody)
        {
    
           using (var sr = new StreamReader(messageBody.GetReceivedBuffer()))
           {
             using (var reader = new JsonTextReader(sr))
             {
               var obj = serializer.Deserialize<JsonBody>(reader);
               return obj;
             }
           }
         }
     }
    
    class JsonBody : WrappedMessage, IServiceRemotingRequestMessageBody, IServiceRemotingResponseMessageBody
    {
          public JsonBody(object wrapped)
          {
            this.Value = wrapped;
          }
    
          public void SetParameter(int position, string parameName, object parameter)
          {  //Not Needed if you are using WrappedMessage
            throw new NotImplementedException();
          }
    
          public object GetParameter(int position, string parameName, Type paramType)
          {
            //Not Needed if you are using WrappedMessage
            throw new NotImplementedException();
          }
    
          public void Set(object response)
          { //Not Needed if you are using WrappedMessage
            throw new NotImplementedException();
          }
    
          public object Get(Type paramType)
          {  //Not Needed if you are using WrappedMessage
            throw new NotImplementedException();
          }
    }
    
  2. Overschrijf de standaardserialisatieprovider met JsonSerializationProvider voor een externe listener.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[]
        {
            new ServiceInstanceListener((c) =>
            {
                return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new         ServiceRemotingJsonSerializationProvider());
            })
        };
    }
    
  3. Overschrijf de standaardserialisatieprovider met JsonSerializationProvider voor een externe clientfactory.

    var proxyFactory = new ServiceProxyFactory((c) =>
    {
        return new FabricTransportServiceRemotingClientFactory(
        serializationProvider: new ServiceRemotingJsonSerializationProvider());
      });
    

Volgende stappen