Udostępnij za pośrednictwem


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 udostępnia 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 zdalnie w usłudze

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

  1. Utwórz interfejs, który ma zostać zaimplementowany przez usługę. Ten interfejs definiuje metody, które są dostępne dla zdalnego wywołania procedury w usłudze. Metody muszą zwracać metody asynchroniczne. Interfejs musi zaimplementować Microsoft.ServiceFabric.Services.Remoting.IService , aby zasygnalizować, że usługa ma interfejs komunikacji zdalnie.
  2. Użyj odbiornika komunikacji zdalnie w usłudze. Odbiornik komunikacji zdalnie to implementacja ICommunicationListener , która zapewnia możliwości komunikacji zdalnie. Microsoft.ServiceFabric.Services.Remoting.Runtime Przestrzeń nazw zawiera metodę CreateServiceRemotingInstanceListeners rozszerzenia zarówno dla usług bezstanowych, jak 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ł .NET DataContractSerializer.

Wywoływanie metod usługi zdalnej

Uwaga

Jeśli używasz więcej niż jednej partycji, element ServiceProxy.Create() musi być podany jako odpowiedni element 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ć tyle, ile potrzebujesz. Wystąpienia serwera proxy usługi można używać ponownie 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 zdalnych wywołań są wykonywane 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 to kosztowna operacja. 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 zdalnie

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órej jest tworzona. Spowoduje to ponowne rozwiązanie punktów końcowych, jeśli istnieją wyjątki trybu failover (wyjątki inne niż przejściowe) i ponawia próbę wywołania z poprawnym punktem końcowym. Liczba ponownych prób dla wyjątków trybu failover jest nieokreślony. 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.

Używanie stosu komunikacji równorzędnej w wersji 2

Od wersji 2.8 pakietu remoting NuGet masz możliwość korzystania ze stosu komunikacji równorzędnej w wersji 2. Komunikacja zdalna w wersji 2 stosu 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 równorzędnej w wersji 1. Komunikacja zdalna w wersji 2 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 równorzędnej (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 zdalnie za pomocą atrybutu FabricTransportServiceRemotingProvider .

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

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

Używanie jawnych klas V2 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 równorzędnej w wersji 1 do komunikacji równorzędnej w wersji 2

Aby uaktualnić z wersji 1 do wersji 2, wymagane są uaktualnienia dwuetapowe. 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 odbiornika W wersji 1 i V2.

    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 zdalnie.

    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 zapewnia, że klient używa stosu w wersji 2. Nie jest wymagana żadna zmiana projektu/usługi klienta. Kompilowanie projektów klienckich przy użyciu zaktualizowanego zestawu interfejsu jest wystarczające.

  3. To krok jest opcjonalny. Użyj atrybutu odbiornika w wersji 2, 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, RemotingClientVersion = RemotingClientVersion.V2)]
    

Używanie 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 równorzędnej w wersji 2. Jego stos interfejsu jest zgodny ze stosem komunikacji wirtualnej V1, ale nie jest zgodny z poprzednimi wersjami 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 równorzędnej w wersji 2 (zgodnej z interfejsem)

Wykonaj następujące kroki, aby zmienić stos 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 komunikacji zdalnie, aby utworzyć odbiornik komunikacji zdalnie.

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

     [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 równorzędnej do tworzenia fabryki odbiornika/klienta dla wersji 2 (zgodnej z interfejsem)

Wykonaj te 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 wirtualnej w wersji 2. Używana domyślna nazwa zasobu punktu końcowego usługi to "ServiceEndpointV2_1". Należy go zdefiniować 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)

Aby uaktualnić z wersji 1 do wersji 2 (zgodne z interfejsem znanym 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 przestrzeń nazw została zaktualizowana do używania Remoting 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 równorzędnej, 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 wersji 1 do klienta V2_1 przy użyciu atrybutu klienta V2_1. Ten krok zapewnia, że klient korzysta ze stosu V2_1. Nie jest wymagana żadna zmiana projektu/usługi klienta. Kompilowanie projektów klienckich przy użyciu zaktualizowanego zestawu interfejsu jest wystarczające.

  3. To krok jest opcjonalny. 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 zawiniętym za pomocą komunikacji zdalnie

W przypadku komunikatu z połączeniem wirtualnym tworzymy pojedynczy obiekt opakowany ze wszystkimi parametrami jako polem w nim. Wykonaj te 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ę JsonSerializationProvider serializacji za pomocą polecenia dla 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ę JsonSerializationProvider serializacji za pomocą polecenia dla fabryki klienta komunikacji zdalniej.

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

Następne kroki