Tjänstkommunikation i C# med Reliable Services

För tjänster som inte är knutna till ett visst kommunikationsprotokoll eller en viss stack, till exempel ett webb-API, Windows Communication Foundation eller andra, tillhandahåller Reliable Services-ramverket en fjärrkommunikationsmekanism för att snabbt och enkelt konfigurera fjärrproceduranrop för tjänster. Den här artikeln beskriver hur du konfigurerar fjärrproceduranrop för tjänster som skrivits med C#.

Konfigurera fjärrkommunikation för en tjänst

Du kan konfigurera fjärrkommunikation för en tjänst i två enkla steg:

  1. Skapa ett gränssnitt för din tjänst att implementera. Det här gränssnittet definierar de metoder som är tillgängliga för ett fjärrproceduranrop i din tjänst. Metoderna måste vara aktivitetsre returnerade asynkrona metoder. Gränssnittet måste implementeras Microsoft.ServiceFabric.Services.Remoting.IService för att signalera att tjänsten har ett fjärrkommunikationsgränssnitt.
  2. Använd en fjärrlyssnare i tjänsten. En fjärrkommunikationslyssnare är en ICommunicationListener implementering som tillhandahåller fjärrkommunikationsfunktioner. Namnområdet Microsoft.ServiceFabric.Services.Remoting.Runtime innehåller tilläggsmetoden CreateServiceRemotingInstanceListeners för både tillståndslösa och tillståndskänsliga tjänster som kan användas för att skapa en fjärrkommunikationslyssnare med hjälp av standardprotokollet för fjärrkommunikation.

Anteckning

Namnområdet Remoting är tillgängligt som ett separat NuGet-paket med namnet Microsoft.ServiceFabric.Services.Remoting.

Följande tillståndslösa tjänst exponerar till exempel en enda metod för att få "Hello World" via ett fjärrproceduranrop.

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

Anteckning

Argumenten och returtyperna i tjänstgränssnittet kan vara enkla, komplexa eller anpassade typer, men de måste kunna serialiseras av .NET DataContractSerializer.

Anropa fjärrtjänstmetoder

Anteckning

Om du använder mer än en partition måste ServiceProxy.Create() anges som lämplig ServicePartitionKey. Detta behövs inte för ett scenario med en partition.

Anropsmetoder för en tjänst med hjälp av fjärrkommunikationsstacken görs med hjälp av en lokal proxy till tjänsten via Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy klassen . Metoden ServiceProxy skapar en lokal proxy med hjälp av samma gränssnitt som tjänsten implementerar. Med den proxyn kan du anropa metoder i gränssnittet via fjärranslutning.


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

string message = await helloWorldClient.HelloWorldAsync();

Fjärrkommunikationsramverket sprider undantag som genereras av tjänsten till klienten. ServiceProxyNär används ansvarar klienten därför för att hantera de undantag som genereras av tjänsten.

Livslängd för tjänstproxy

Att skapa tjänstproxy är en enkel åtgärd, så du kan skapa så många du behöver. Tjänstproxyinstanser kan återanvändas så länge de behövs. Om ett fjärrproceduranrop utlöser ett undantag kan du fortfarande återanvända samma proxyinstans. Varje tjänstproxy innehåller en kommunikationsklient som används för att skicka meddelanden via kabel. Vid anrop av fjärranrop utförs interna kontroller för att avgöra om kommunikationsklienten är giltig. Baserat på resultatet av dessa kontroller återskapas kommunikationsklienten om det behövs. Om ett undantag inträffar behöver du därför inte återskapa ServiceProxy.

Livslängd för tjänstproxyfabrik

ServiceProxyFactory är en fabrik som skapar proxyinstanser för olika fjärrkommunikationsgränssnitt. Om du använder API ServiceProxyFactory.CreateServiceProxy :et för att skapa en proxy skapar ramverket en singleton-tjänstproxy. Det är användbart att skapa en manuellt när du behöver åsidosätta IServiceRemotingClientFactory-egenskaper. Att skapa en fabrik är en dyr åtgärd. En tjänstproxyfabrik upprätthåller en intern cache för kommunikationsklienten. Bästa praxis är att cachelagrade tjänstproxyfabriken så länge som möjligt.

Hantering av fjärrkommunikationsfel

Alla fjärrundantag som genereras av tjänst-API:et skickas tillbaka till klienten som AggregateException. Fjärrfel bör kunna serialiseras av DataContract. Om de inte är det genererar proxy-API:et ServiceException med serialiseringsfelet i det.

Tjänstproxyn hanterar alla redundansundanter för tjänstpartitionen som den skapas för. Den löser slutpunkterna igen om det finns redundansundantag (icke-tillfälliga undantag) och försöker anropa igen med rätt slutpunkt. Antalet återförsök för redundansundantag är obestämt. Om tillfälliga undantag inträffar försöker proxyn anropa igen.

Standardparametrar för återförsök tillhandahålls av OperationRetrySettings.

En användare kan konfigurera dessa värden genom att skicka objektet OperationRetrySettings till Konstruktorn ServiceProxyFactory.

Använda V2-stacken för fjärrkommunikation

Från och med version 2.8 av NuGet-fjärrkommunikationspaketet har du möjlighet att använda V2-stacken för fjärrkommunikation. V2-stacken för fjärrkommunikation fungerar bättre. Den innehåller också funktioner som anpassad serialisering och mer anslutningsbara API:er. Mallkoden fortsätter att använda V1-stacken för fjärrkommunikation. Fjärrkommunikation V2 är inte kompatibelt med V1 (den tidigare fjärrkommunikationsstacken). Följ anvisningarna i artikeln Uppgradera från V1 till V2 för att undvika effekter på tjänstens tillgänglighet.

Följande metoder är tillgängliga för att aktivera V2-stacken.

Använda ett sammansättningsattribut för att använda V2-stacken

De här stegen ändrar mallkoden så att V2-stacken används med hjälp av ett sammansättningsattribut.

  1. Ändra slutpunktsresursen från "ServiceEndpoint" till "ServiceEndpointV2" i tjänstmanifestet.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Använd tilläggsmetoden Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners för att skapa fjärrkommunikationslyssnare (lika med både V1 och V2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Markera sammansättningen som innehåller fjärrkommunikationsgränssnitten med ett FabricTransportServiceRemotingProvider attribut.

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

Inga kodändringar krävs i klientprojektet. Skapa klientsammansättningen med gränssnittssammansättningen för att se till att det sammansättningsattribut som tidigare visades används.

Använd explicita V2-klasser för att använda V2-stacken

Som ett alternativ till att använda ett sammansättningsattribut kan V2-stacken också aktiveras med hjälp av explicita V2-klasser.

De här stegen ändrar mallkoden så att den använder V2-stacken med hjälp av explicita V2-klasser.

  1. Ändra slutpunktsresursen från "ServiceEndpoint" till "ServiceEndpointV2" i tjänstmanifestet.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Använd FabricTransportServiceRemotingListener från Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime namnområdet.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Använd FabricTransportServiceRemotingClientFactory från Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client namnområdet för att skapa klienter.

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

Uppgradera från fjärrkommunikation V1 till fjärrkommunikation V2

Om du vill uppgradera från V1 till V2 krävs tvåstegsuppgraderingar. Följ stegen i den här sekvensen.

  1. Uppgradera V1-tjänsten till V2-tjänsten med hjälp av det här attributet. Den här ändringen ser till att tjänsten lyssnar på V1- och V2-lyssnaren.

    a. Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2" i tjänstmanifestet.

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

    b. Använd följande tilläggsmetod för att skapa en fjärrkommunikationslyssnare.

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

    c. Lägg till ett sammansättningsattribut i fjärrkommunikationsgränssnitt för att använda V1- och V2-lyssnaren och V2-klienten.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
    
    
  2. Uppgradera V1-klienten till en V2-klient med hjälp av V2-klientattributet. Det här steget ser till att klienten använder V2-stacken. Ingen ändring i klientprojektet/-tjänsten krävs. Det räcker att skapa klientprojekt med uppdaterad gränssnittssammansättning.

  3. Det här är valfritt. Använd V2-lyssnarattributet och uppgradera sedan V2-tjänsten. Det här steget ser till att tjänsten bara lyssnar på V2-lyssnaren.

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

Använda V2-stacken för fjärrkommunikation (gränssnittskompatibel)

V2-stacken för fjärrkommunikation (gränssnittskompatibel) kallas V2_1 och är den senaste versionen. Den har alla funktioner i V2-fjärrkommunikationsstacken. Dess gränssnittsstack är kompatibel med V1-stacken för fjärrkommunikation, men den är inte bakåtkompatibel med V2 och V1. Om du vill uppgradera från V1 till V2_1 utan att påverka tjänstens tillgänglighet följer du stegen i artikeln Uppgradera från V1 till V2 (gränssnittskompatibel).

Använd ett sammansättningsattribut för att använda V2-stacken (gränssnittskompatibel)

Följ de här stegen om du vill ändra till en V2_1 stack.

  1. Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2_1" i tjänstmanifestet.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Använd metoden för fjärrkommunikationstillägg för att skapa en fjärrkommunikationslyssnare.

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Lägg till ett sammansättningsattribut i fjärrkommunikationsgränssnitt.

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

Inga ändringar krävs i klientprojektet. Skapa klientsammansättningen med gränssnittssammansättningen för att kontrollera att det tidigare sammansättningsattributet används.

Använd explicita fjärrkommunikationsklasser för att skapa en lyssnare/klientfabrik för V2-versionen (gränssnittskompatibel)

Följ de här stegen:

  1. Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2_1" i tjänstmanifestet.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Använd V2-lyssnaren för fjärrkommunikation. Standardresursnamnet för tjänstslutpunkten som används är "ServiceEndpointV2_1". Det måste definieras i tjänstmanifestet.

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

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

Uppgradera från fjärrkommunikation V1 till fjärrkommunikation V2 (gränssnittskompatibel)

Om du vill uppgradera från V1 till V2 (gränssnittskompatibel, så kallad V2_1) krävs tvåstegsuppgraderingar. Följ stegen i den här sekvensen.

Anteckning

När du uppgraderar från V1 till V2 kontrollerar du att Remoting namnområdet har uppdaterats för att använda V2. Exempel: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. Uppgradera V1-tjänsten till V2_1-tjänsten med hjälp av följande attribut. Den här ändringen ser till att tjänsten lyssnar på V1 och V2_1 lyssnare.

    a. Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2_1" i tjänstmanifestet.

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

    b. Använd följande tilläggsmetod för att skapa en fjärrkommunikationslyssnare.

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

    c. Lägg till ett sammansättningsattribut i fjärrkommunikationsgränssnitt för att använda V1, V2_1-lyssnaren och V2_1 klienten.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    
    
  2. Uppgradera V1-klienten till V2_1-klienten med hjälp av V2_1-klientattributet. Det här steget kontrollerar att klienten använder V2_1-stacken. Ingen ändring i klientprojektet/-tjänsten krävs. Det räcker att skapa klientprojekt med uppdaterad gränssnittssammansättning.

  3. Det här är valfritt. Ta bort V1-lyssnarversionen från attributet och uppgradera sedan V2-tjänsten. Det här steget ser till att tjänsten bara lyssnar på V2-lyssnaren.

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

Använda anpassad serialisering med ett omslutet fjärrkommunikationsmeddelande

För ett omslutet fjärrkommunikationsmeddelande skapar vi ett enda omslutet objekt med alla parametrar som ett fält i det. Följ de här stegen:

  1. IServiceRemotingMessageSerializationProvider Implementera gränssnittet för att tillhandahålla implementering för anpassad serialisering. Det här kodfragmentet visar hur implementeringen ser ut.

    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. Åsidosätt standard-serialiseringsprovidern med JsonSerializationProvider för en fjärrkommunikationslyssnare.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[]
        {
            new ServiceInstanceListener((c) =>
            {
                return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new         ServiceRemotingJsonSerializationProvider());
            })
        };
    }
    
  3. Åsidosätt standard-serialiseringsprovidern med JsonSerializationProvider för en fjärrkommunikationsklientfabrik.

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

Nästa steg