Comunicação remota do serviço em C# com o Reliable Services

Para serviços que não estão associados a uma pilha ou protocolo de comunicação específico, como uma API Web, o Windows Communication Foundation ou outros, a arquitetura Reliable Services fornece um mecanismo de comunicação remota para configurar rápida e facilmente chamadas de procedimento remoto para serviços. Este artigo aborda como configurar chamadas de procedimento remoto para serviços escritos com C#.

Configurar comunicação remota num serviço

Pode configurar a comunicação remota para um serviço em dois passos simples:

  1. Crie uma interface para o seu serviço implementar. Esta interface define os métodos disponíveis para uma chamada de procedimento remoto no seu serviço. Os métodos têm de ser métodos assíncronos que devolvem tarefas. A interface tem de implementar Microsoft.ServiceFabric.Services.Remoting.IService para sinalizar que o serviço tem uma interface remota.
  2. Utilize um serviço de escuta remota no seu serviço. Um serviço de escuta remota é uma implementação ICommunicationListener que fornece capacidades remotas. O Microsoft.ServiceFabric.Services.Remoting.Runtime espaço de nomes contém o método CreateServiceRemotingInstanceListeners de extensão para serviços sem estado e com monitorização de estado que podem ser utilizados para criar um serviço de escuta remota através do protocolo de transporte remota predefinido.

Nota

O Remoting espaço de nomes está disponível como um pacote NuGet separado chamado Microsoft.ServiceFabric.Services.Remoting.

Por exemplo, o seguinte serviço sem estado expõe um único método para obter "Hello World" através de uma chamada de procedimento remoto.

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

Nota

Os argumentos e os tipos de retorno na interface de serviço podem ser tipos simples, complexos ou personalizados, mas têm de ser serializados pelo DataContractSerializer do .NET.

Chamar métodos de serviço remoto

Nota

Se estiver a utilizar mais do que uma partição, o ServiceProxy.Create() tem de ser fornecido o ServicePartitionKey adequado. Isto não é necessário para um cenário de partição única.

A chamada de métodos num serviço através da pilha remota é feita através da utilização de um proxy local para o serviço através da Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy classe . O ServiceProxy método cria um proxy local com a mesma interface que o serviço implementa. Com esse proxy, pode chamar métodos na interface remotamente.


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

string message = await helloWorldClient.HelloWorldAsync();

A arquitetura remota propaga exceções emitidas pelo serviço para o cliente. Como resultado, quando ServiceProxyé utilizado, o cliente é responsável por processar as exceções emitidas pelo serviço.

Duração do proxy de serviço

A criação do proxy de serviço é uma operação simples, pelo que pode criar quantas precisar. As instâncias de proxy de serviço podem ser reutilizadas enquanto forem necessárias. Se uma chamada de procedimento remoto emitir uma exceção, ainda pode reutilizar a mesma instância de proxy. Cada proxy de serviço contém um cliente de comunicação utilizado para enviar mensagens por cabo. Ao invocar chamadas remotas, são efetuadas verificações internas para determinar se o cliente de comunicação é válido. Com base nos resultados dessas verificações, o cliente de comunicação é recriado, se necessário. Por conseguinte, se ocorrer uma exceção, não é necessário recriar ServiceProxy.

Duração da fábrica do proxy de serviço

ServiceProxyFactory é uma fábrica que cria instâncias de proxy para diferentes interfaces remotas. Se utilizar a API ServiceProxyFactory.CreateServiceProxy para criar um proxy, a arquitetura cria um proxy de serviço singleton. É útil criar um manualmente quando precisar de substituir as propriedades IServiceRemotingClientFactory. A criação de fábrica é uma operação dispendiosa. Uma fábrica de proxy de serviço mantém uma cache interna do cliente de comunicação. Uma melhor prática é colocar em cache a fábrica de proxy de serviço durante o máximo de tempo possível.

Processamento de exceções remotas

Todas as exceções remotas geradas pela API de serviço são enviadas de volta para o cliente como AggregateException. As exceções remotas devem ser serializadas pelo DataContract. Se não estiverem, a API de proxy gera ServiceException com o erro de serialização.

O proxy de serviço processa todas as exceções de ativação pós-falha para a partição de serviço para a qual é criado. Resolve novamente os pontos finais se existirem exceções de ativação pós-falha (exceções não transitórias) e repetirá a chamada com o ponto final correto. O número de tentativas de exceções de ativação pós-falha é indefinido. Se ocorrerem exceções transitórias, o proxy repetirá a chamada.

Os parâmetros de repetição predefinidos são fornecidos por OperationRetrySettings.

Um utilizador pode configurar estes valores ao transmitir o objeto OperationRetrySettings para o construtor ServiceProxyFactory.

Utilizar a pilha V2 remota

A partir da versão 2.8 do pacote de comunicação remota NuGet, tem a opção de utilizar a pilha V2 remota. A pilha V2 remota tem um melhor desempenho. Também fornece funcionalidades como serialização personalizada e APIs mais pluggable. O código de modelo continua a utilizar a pilha V1 remota. A comunicação remota V2 não é compatível com a V1 (a pilha remota anterior). Siga as instruções no artigo Atualizar da V1 para a V2 para evitar efeitos na disponibilidade do serviço.

As seguintes abordagens estão disponíveis para ativar a pilha V2.

Utilizar um atributo de assemblagem para utilizar a pilha V2

Estes passos alteram o código do modelo para utilizar a pilha V2 com um atributo de assemblagem.

  1. Altere o recurso de ponto final de "ServiceEndpoint" para "ServiceEndpointV2" no manifesto do serviço.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Utilize o Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners método de extensão para criar serviços de escuta remotas (iguais para V1 e V2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Marque a assemblagem que contém as interfaces remotas com um FabricTransportServiceRemotingProvider atributo.

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

Não são necessárias alterações de código no projeto de cliente. Crie a assemblagem do cliente com a assemblagem da interface para se certificar de que o atributo de assemblagem mostrado anteriormente é utilizado.

Utilizar classes V2 explícitas para utilizar a pilha V2

Como alternativa à utilização de um atributo de assemblagem, a pilha V2 também pode ser ativada com classes V2 explícitas.

Estes passos alteram o código do modelo para utilizar a pilha V2 com classes V2 explícitas.

  1. Altere o recurso de ponto final de "ServiceEndpoint" para "ServiceEndpointV2" no manifesto do serviço.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Utilize FabricTransportServiceRemotingListener a Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime partir do espaço de nomes.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Utilize FabricTransportServiceRemotingClientFactory a Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client partir do espaço de nomes para criar clientes.

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

Atualizar da comunicação remota V1 para a comunicação remota V2

Para atualizar da V1 para a V2, são necessárias atualizações de dois passos. Siga os passos nesta sequência.

  1. Atualize o serviço V1 para o serviço V2 com este atributo. Esta alteração garante que o serviço escuta no serviço de escuta V1 e V2.

    a. Adicione um recurso de ponto final com o nome "ServiceEndpointV2" no manifesto do serviço.

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

    b. Utilize o seguinte método de extensão para criar um serviço de escuta remota.

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

    c. Adicione um atributo de assemblagem em interfaces remotas para utilizar o serviço de escuta V1 e V2 e o cliente V2.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
    
    
  2. Atualize o cliente V1 para um cliente V2 com o atributo de cliente V2. Este passo garante que o cliente utiliza a pilha V2. Não é necessária qualquer alteração no projeto/serviço do cliente. A criação de projetos de cliente com assemblagem de interface atualizada é suficiente.

  3. Este passo é opcional. Utilize o atributo de serviço de escuta V2 e, em seguida, atualize o serviço V2. Este passo garante que o serviço está a escutar apenas no serviço de escuta V2.

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

Utilizar a pilha remota V2 (compatível com a interface)

A pilha remota V2 (compatível com a interface) é conhecida como V2_1 e é a versão mais atualizada. Tem todas as funcionalidades da pilha remota V2. A pilha de interface é compatível com a pilha V1 remota, mas não é retrocompatível com V2 e V1. Para atualizar da V1 para a V2_1 sem afetar a disponibilidade do serviço, siga os passos no artigo Atualizar da V1 para a V2 (compatível com a interface).

Utilizar um atributo de assemblagem para utilizar a pilha remota V2 (compatível com a interface)

Siga estes passos para mudar para uma pilha de V2_1.

  1. Adicione um recurso de ponto final com o nome "ServiceEndpointV2_1" no manifesto do serviço.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Utilize o método de extensão remota para criar um serviço de escuta remota.

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Adicione um atributo de assemblagem nas interfaces remotas.

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

Não são necessárias alterações no projeto de cliente. Crie a assemblagem do cliente com a assemblagem da interface para se certificar de que o atributo de assemblagem anterior está a ser utilizado.

Utilizar classes remotas explícitas para criar uma fábrica de serviços de escuta/cliente para a versão V2 (compatível com a interface)

Siga estes passos:

  1. Adicione um recurso de ponto final com o nome "ServiceEndpointV2_1" no manifesto do serviço.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Utilize o serviço de escuta V2 comunicante. O nome do recurso de ponto final de serviço predefinido utilizado é "ServiceEndpointV2_1". Tem de ser definido no manifesto do serviço.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 var settings = new FabricTransportRemotingListenerSettings();
                 settings.UseWrappedMessage = true;
                 return new FabricTransportServiceRemotingListener(c, this,settings);
    
             })
         };
     }
    
  3. Utilize a fábrica de cliente V2.

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

Atualizar da comunicação remota V1 para a comunicação remota V2 (compatível com a interface)

Para atualizar da V1 para a V2 (compatível com a interface, conhecida como V2_1), são necessárias atualizações de dois passos. Siga os passos nesta sequência.

Nota

Ao atualizar de V1 para V2, certifique-se de que o Remoting espaço de nomes é atualizado para utilizar v2. Exemplo: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. Atualize o serviço V1 para V2_1 serviço com o seguinte atributo. Esta alteração garante que o serviço está a escutar na V1 e no serviço de escuta V2_1.

    a. Adicione um recurso de ponto final com o nome "ServiceEndpointV2_1" no manifesto do serviço.

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

    b. Utilize o seguinte método de extensão para criar um serviço de escuta remota.

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

    c. Adicione um atributo de assemblagem em interfaces remotas para utilizar o cliente V1, V2_1 de escuta e V2_1.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    
    
  2. Atualize o cliente V1 para o cliente V2_1 com o atributo V2_1 cliente. Este passo garante que o cliente está a utilizar a pilha de V2_1. Não é necessária qualquer alteração no projeto/serviço do cliente. A criação de projetos de cliente com assemblagem de interface atualizada é suficiente.

  3. Este passo é opcional. Remova a versão do serviço de escuta V1 do atributo e, em seguida, atualize o serviço V2. Este passo garante que o serviço está a escutar apenas no serviço de escuta V2.

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

Utilizar a serialização personalizada com uma mensagem encapsulada remota

Para uma mensagem encapsulada remota, criamos um único objeto moldado com todos os parâmetros como um campo no mesmo. Siga estes passos:

  1. Implemente a IServiceRemotingMessageSerializationProvider interface para fornecer a implementação para serialização personalizada. Este fragmento de código mostra o aspeto da implementação.

    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. Substitua o fornecedor JsonSerializationProvider de serialização predefinido por para um serviço de escuta remota.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[]
        {
            new ServiceInstanceListener((c) =>
            {
                return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new         ServiceRemotingJsonSerializationProvider());
            })
        };
    }
    
  3. Substitua o fornecedor JsonSerializationProvider de serialização predefinido por para uma fábrica de cliente remota.

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

Passos seguintes