Komunikacja zdalna usługi w języku C# za pomocą usług Reliable Services

W przypadku usług, które nie są powiązane z określonym protokołem komunikacyjnym lub stosem, takim jak internetowy interfejs API, Windows Communication Foundation lub inne, platforma Reliable Services zapewnia mechanizm komunikacji zdalnej umożliwiający szybkie i łatwe konfigurowanie zdalnych wywołań procedur dla usług. W tym artykule omówiono sposób konfigurowania zdalnych wywołań procedur dla usług napisanych w języku C#.

Konfigurowanie komunikacji zdalniej w usłudze

Komunikacja zdalna usługi można skonfigurować w dwóch prostych krokach:

  1. Utwórz interfejs do zaimplementowania usługi. Ten interfejs definiuje metody, które są dostępne dla zdalnego wywołania procedury w usłudze. Metody muszą zwracać metody asynchroniczne. Interfejs musi implementować Microsoft.ServiceFabric.Services.Remoting.IService , aby zasygnalizować, że usługa ma interfejs komunikacji zdalniej.
  2. Użyj odbiornika komunikacji zdalniej w usłudze. Odbiornik komunikacji zdalniej to implementacja ICommunicationListener , która zapewnia możliwości komunikacji zdalniej. Microsoft.ServiceFabric.Services.Remoting.Runtime Przestrzeń nazw zawiera metodę CreateServiceRemotingInstanceListeners rozszerzenia dla usług bezstanowych i stanowych, których można użyć do utworzenia odbiornika komunikacji wirtualnej przy użyciu domyślnego protokołu transportu komunikacji wirtualnej.

Uwaga

Remoting Przestrzeń nazw jest dostępna jako oddzielny pakiet NuGet o nazwie Microsoft.ServiceFabric.Services.Remoting.

Na przykład następująca usługa bezstanowa uwidacznia pojedynczą metodę pobierania "Hello world" za pośrednictwem zdalnego wywołania procedury.

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

Uwaga

Argumenty i typy zwracane w interfejsie usługi mogą być dowolnymi prostymi, złożonymi lub niestandardowymi typami, ale muszą być serializowane przez moduł datacontractSerializer platformy .NET.

Wywoływanie metod usługi zdalnej

Uwaga

Jeśli używasz więcej niż jednej partycji, element ServiceProxy.Create() musi być podany jako odpowiedni klucz ServicePartitionKey. Nie jest to wymagane w scenariuszu z jedną partycją.

Wywoływanie metod w usłudze przy użyciu stosu komunikacji telefonicznej odbywa się przy użyciu lokalnego serwera proxy do usługi za pośrednictwem Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy klasy . Metoda ServiceProxy tworzy lokalny serwer proxy przy użyciu tego samego interfejsu, który implementuje usługa. Za pomocą tego serwera proxy można zdalnie wywoływać metody w interfejsie.


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

string message = await helloWorldClient.HelloWorldAsync();

Platforma komunikacji zdalnie propaguje wyjątki zgłaszane przez usługę do klienta. W związku z tym, gdy ServiceProxyjest używany, klient jest odpowiedzialny za obsługę wyjątków zgłaszanych przez usługę.

Okres istnienia serwera proxy usługi

Tworzenie serwera proxy usługi to uproszczona operacja, dzięki czemu można utworzyć dowolną liczbę operacji. Wystąpienia serwera proxy usługi mogą być ponownie używane tak długo, jak są potrzebne. Jeśli wywołanie procedury zdalnej zgłasza wyjątek, nadal można ponownie użyć tego samego wystąpienia serwera proxy. Każdy serwer proxy usługi zawiera klienta komunikacji używanego do wysyłania komunikatów za pośrednictwem przewodu. Podczas wywoływania wywołań zdalnych wykonywane są wewnętrzne kontrole w celu określenia, czy klient komunikacji jest prawidłowy. Na podstawie wyników tych testów klient komunikacji jest tworzony ponownie w razie potrzeby. W związku z tym, jeśli wystąpi wyjątek, nie trzeba ponownie tworzyć ServiceProxy.

Okres istnienia fabryki serwera proxy usługi

ServiceProxyFactory to fabryka, która tworzy wystąpienia serwera proxy dla różnych interfejsów komunikacji wirtualnej. Jeśli używasz interfejsu API ServiceProxyFactory.CreateServiceProxy do tworzenia serwera proxy, platforma tworzy pojedynczy serwer proxy usługi. Warto utworzyć jedną ręcznie, gdy trzeba zastąpić właściwości IServiceRemotingClientFactory. Tworzenie fabryki jest kosztowną operacją. Fabryka serwerów proxy usługi utrzymuje wewnętrzną pamięć podręczną klienta komunikacji. Najlepszym rozwiązaniem jest buforowanie fabryki serwera proxy usługi tak długo, jak to możliwe.

Obsługa wyjątków komunikacji zdalniej

Wszystkie wyjątki zdalne zgłaszane przez interfejs API usługi są wysyłane z powrotem do klienta jako AggregateException. Wyjątki zdalne powinny być serializowane przez usługę DataContract. Jeśli tak nie jest, interfejs API serwera proxy zgłasza wyjątek ServiceException z błędem serializacji w nim.

Serwer proxy usługi obsługuje wszystkie wyjątki trybu failover dla partycji usługi, dla których została utworzona. Ponownie rozpoznaje punkty końcowe, jeśli istnieją wyjątki trybu failover (wyjątki inne niż przejściowe) i ponawia próbę wywołania przy użyciu poprawnego punktu końcowego. Liczba ponownych prób dla wyjątków trybu failover jest nieskończona. Jeśli wystąpią wyjątki przejściowe, serwer proxy ponawia próbę wywołania.

Domyślne parametry ponawiania są dostarczane przez element OperationRetrySettings.

Użytkownik może skonfigurować te wartości, przekazując obiekt OperationRetrySettings do konstruktora ServiceProxyFactory.

Korzystanie ze stosu komunikacji wirtualnej w wersji 2

Od wersji 2.8 pakietu komunikacji wirtualnej NuGet możesz użyć stosu komunikacji wirtualnej V2. Stos komunikacji wirtualnej V2 działa lepiej. Udostępnia również funkcje, takie jak serializacja niestandardowa i bardziej podłączane interfejsy API. Kod szablonu nadal używa stosu komunikacji wirtualnej V1. Komunikacja zdalna V2 nie jest zgodna z wersją 1 (poprzedni stos komunikacji równorzędnej). Postępuj zgodnie z instrukcjami w artykule Uaktualnianie z wersji 1 do wersji 2 , aby uniknąć wpływu na dostępność usługi.

Dostępne są następujące podejścia umożliwiające włączenie stosu w wersji 2.

Używanie atrybutu zestawu do używania stosu w wersji 2

Te kroki zmieniają kod szablonu, aby używać stosu w wersji 2 przy użyciu atrybutu zestawu.

  1. Zmień zasób punktu końcowego z "ServiceEndpoint" na "ServiceEndpointV2" w manifeście usługi.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners Użyj metody rozszerzenia, aby utworzyć odbiorniki komunikacji wirtualnej (równe zarówno dla wersji 1, jak i V2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Oznacz zestaw zawierający interfejsy komunikacji zdalniej za pomocą atrybutu FabricTransportServiceRemotingProvider .

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

W projekcie klienta nie są wymagane żadne zmiany kodu. Skompiluj zestaw klienta z zestawem interfejsu, aby upewnić się, że wcześniej wyświetlony atrybut zestawu jest używany.

Używanie jawnych klas w wersji 2 do korzystania ze stosu w wersji 2

Alternatywą dla używania atrybutu zestawu jest również włączenie stosu w wersji 2 przy użyciu jawnych klas V2.

Te kroki zmieniają kod szablonu, aby używać stosu w wersji 2 przy użyciu jawnych klas V2.

  1. Zmień zasób punktu końcowego z "ServiceEndpoint" na "ServiceEndpointV2" w manifeście usługi.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Użyj elementu FabricTransportServiceRemotingListener z Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime przestrzeni nazw.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Użyj elementu FabricTransportServiceRemotingClientFactory z Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client przestrzeni nazw, aby utworzyć klientów.

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

Uaktualnianie z komunikacji zdalniej w wersji 1 do komunikacji zdalniej w wersji 2

Aby uaktualnić z wersji 1 do wersji 2, wymagane są dwuetapowe uaktualnienia. Wykonaj kroki opisane w tej sekwencji.

  1. Uaktualnij usługę V1 do usługi w wersji 2 przy użyciu tego atrybutu. Ta zmiana zapewnia, że usługa nasłuchuje na odbiorniku w wersji 1 i 2.

    a. Dodaj zasób punktu końcowego o nazwie "ServiceEndpointV2" w manifeście usługi.

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

    b. Użyj następującej metody rozszerzenia, aby utworzyć odbiornik komunikacji zdalniej.

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

    c. Dodaj atrybut zestawu w interfejsach komunikacji wirtualnej, aby używać odbiornika V1 i V2 oraz klienta V2.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
    
    
  2. Uaktualnij klienta w wersji 1 do klienta w wersji 2 przy użyciu atrybutu klienta w wersji 2. Ten krok gwarantuje, że klient korzysta ze stosu w wersji 2. Nie jest wymagana żadna zmiana w projekcie/usłudze klienta. Kompilowanie projektów klienckich przy użyciu zaktualizowanego zestawu interfejsu jest wystarczające.

  3. Ta czynność jest opcjonalna. Użyj atrybutu odbiornika w wersji 2, a następnie uaktualnij usługę w wersji 2. Ten krok zapewnia, że usługa nasłuchuje tylko na odbiorniku w wersji 2.

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

Korzystanie ze stosu komunikacji wirtualnej V2 (zgodnej z interfejsem)

Stos komunikacji wirtualnej V2 (zgodny z interfejsem) jest znany jako V2_1 i jest najbardziej aktualną wersją. Ma wszystkie funkcje stosu komunikacji wirtualnej v2. Jego stos interfejsu jest zgodny ze stosem komunikacji wirtualnej V1, ale nie jest zgodny z poprzednimi wersjami w wersji 2 i V1. Aby uaktualnić z wersji 1 do V2_1 bez wpływu na dostępność usługi, wykonaj kroki opisane w artykule Uaktualnianie z wersji 1 do wersji 2 (zgodne z interfejsem).

Używanie atrybutu zestawu do używania stosu komunikacji wirtualnej V2 (zgodnej z interfejsem)

Wykonaj następujące kroki, aby przejść do stosu V2_1.

  1. Dodaj zasób punktu końcowego o nazwie "ServiceEndpointV2_1" w manifeście usługi.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Użyj metody rozszerzenia remoting, aby utworzyć odbiornik komunikacji zdalnie.

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Dodaj atrybut zestawu w interfejsach komunikacji sieciowej.

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

W projekcie klienta nie są wymagane żadne zmiany. Skompiluj zestaw klienta przy użyciu zestawu interfejsu, aby upewnić się, że jest używany poprzedni atrybut zestawu.

Używanie jawnych klas komunikacji wirtualnej do tworzenia fabryki odbiornika/klienta dla wersji 2 (zgodnej z interfejsem)

Wykonaj następujące kroki:

  1. Dodaj zasób punktu końcowego o nazwie "ServiceEndpointV2_1" w manifeście usługi.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Użyj odbiornika komunikacji równorzędnej w wersji 2. Używana domyślna nazwa zasobu punktu końcowego usługi to "ServiceEndpointV2_1". Musi być zdefiniowany w manifeście usługi.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 var settings = new FabricTransportRemotingListenerSettings();
                 settings.UseWrappedMessage = true;
                 return new FabricTransportServiceRemotingListener(c, this,settings);
    
             })
         };
     }
    
  3. Użyj fabryki klienta w wersji 2.

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

Uaktualnianie z komunikacji wirtualnej V1 do komunikacji równorzędnej w wersji 2 (zgodne z interfejsem)

Do uaktualnienia z wersji 1 do wersji 2 (zgodnej z interfejsem, znanej jako V2_1), wymagane są uaktualnienia dwuetapowe. Wykonaj kroki opisane w tej sekwencji.

Uwaga

Podczas uaktualniania z wersji 1 do wersji 2 upewnij się, że Remoting przestrzeń nazw jest aktualizowana do korzystania z wersji 2. Przykład: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. Uaktualnij usługę V1 do usługi V2_1 przy użyciu następującego atrybutu. Ta zmiana zapewnia, że usługa nasłuchuje w wersji 1 i odbiornika V2_1.

    a. Dodaj zasób punktu końcowego o nazwie "ServiceEndpointV2_1" w manifeście usługi.

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

    b. Użyj następującej metody rozszerzenia, aby utworzyć odbiornik komunikacji zdalnie.

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

    c. Dodaj atrybut zestawu w interfejsach komunikacji wirtualnej, aby używać odbiornika V1, V2_1 i klienta V2_1.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    
    
  2. Uaktualnij klienta w wersji 1 do klienta V2_1 przy użyciu atrybutu klienta V2_1. Ten krok zapewnia, że klient korzysta z stosu V2_1. Nie jest wymagana żadna zmiana projektu/usługi klienta. Kompilowanie projektów klienckich ze zaktualizowanym zestawem interfejsu jest wystarczające.

  3. Ta czynność jest opcjonalna. Usuń wersję odbiornika v1 z atrybutu, a następnie uaktualnij usługę V2. Ten krok zapewnia, że usługa nasłuchuje tylko na odbiorniku w wersji 2.

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

Używanie niestandardowej serializacji z komunikatem owiniętym za pomocą komunikacji zdalnie

W przypadku komunikatu opakowanego za pomocą komunikacji zdalnie tworzymy pojedynczy obiekt opakowany ze wszystkimi parametrami jako polem w nim. Wykonaj następujące kroki:

  1. Zaimplementuj interfejs w IServiceRemotingMessageSerializationProvider celu zapewnienia implementacji niestandardowej serializacji. Ten fragment kodu pokazuje, jak wygląda implementacja.

    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. Zastąpij domyślnego dostawcę serializacji za JsonSerializationProvider pomocą odbiornika komunikacji zdalnie.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[]
        {
            new ServiceInstanceListener((c) =>
            {
                return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new         ServiceRemotingJsonSerializationProvider());
            })
        };
    }
    
  3. Zastąpij domyślnego dostawcę serializacji za JsonSerializationProvider pomocą usługi remotingu fabryki klienta.

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

Następne kroki