Comunicação remota de serviços em C# com serviços confiáveis

Para serviços que não estão vinculados a um protocolo ou pilha de comunicação específica, como uma API da Web, o Windows Communication Foundation ou outros, a estrutura de Serviços Confiáveis fornece um mecanismo de comunicação remota para configurar rápida e facilmente chamadas de procedimento remoto para serviços. Este artigo descreve como configurar chamadas de procedimento remoto para serviços escritos com C#.

Configurar a comunicação remota em um serviço

Você pode configurar a comunicação remota para um serviço em duas etapas simples:

  1. Crie uma interface para o seu serviço implementar. Essa interface define os métodos que estão disponíveis para uma chamada de procedimento remoto em seu serviço. Os métodos devem ser métodos assíncronos de retorno de tarefa. A interface deve ser implementada Microsoft.ServiceFabric.Services.Remoting.IService para sinalizar que o serviço tem uma interface remota.
  2. Use um ouvinte remoto em seu serviço. Um ouvinte remoto é uma ICommunicationListener implementação que fornece recursos de comunicação remota. O Microsoft.ServiceFabric.Services.Remoting.Runtime namespace contém o método CreateServiceRemotingInstanceListeners extension para serviços stateless e stateful que podem ser usados para criar um ouvinte remoto usando o protocolo de transporte remoto padrão.

Nota

O Remoting namespace 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 qualquer tipo simples, complexo ou personalizado, mas eles devem ser capazes de ser serializados pelo .NET DataContractSerializer.

Chamar métodos de serviço remoto

Nota

Se você estiver usando mais de uma partição, o ServiceProxy.Create() deve ser fornecido o ServicePartitionKey apropriado. Isso não é necessário para um cenário de partição única.

Chamar métodos em um serviço usando a pilha de comunicação remota é feito usando 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 usando a mesma interface que o serviço implementa. Com esse proxy, você pode chamar métodos na interface remotamente.


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

string message = await helloWorldClient.HelloWorldAsync();

A estrutura de comunicação remota propaga exceções lançadas pelo serviço para o cliente. Como resultado, quando ServiceProxyé usado, o cliente é responsável por lidar com as exceções lançadas pelo serviço.

Tempo de vida do proxy de serviço

A criação de proxy de serviço é uma operação leve, para que você possa criar quantos precisar. As instâncias de proxy de serviço podem ser reutilizadas pelo tempo que forem necessárias. Se uma chamada de procedimento remoto gerar uma exceção, você ainda poderá reutilizar a mesma instância de proxy. Cada proxy de serviço contém um cliente de comunicação usado para enviar mensagens por fio. Ao invocar chamadas remotas, verificações internas são realizadas 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. Portanto, se ocorrer uma exceção, não será necessário recriar ServiceProxyo .

Vida útil da fábrica de proxy de serviço

ServiceProxyFactory é uma fábrica que cria instâncias de proxy para diferentes interfaces de comunicação remota. Se você usar a API ServiceProxyFactory.CreateServiceProxy para criar um proxy, a estrutura criará um proxy de serviço singleton. É útil criar um manualmente quando você precisa substituir as propriedades IServiceRemotingClientFactory. A criação de fábricas é uma operação cara. Uma fábrica de proxy de serviço mantém um cache interno do cliente de comunicação. Uma prática recomendada é armazenar em cache a fábrica de proxy de serviço pelo maior tempo possível.

Tratamento remoto de exceções

Todas as exceções remotas lançadas pela API de serviço são enviadas de volta ao cliente como AggregateException. As exceções remotas devem poder ser serializadas por DataContract. Se não estiverem, a API de proxy lançará ServiceException com o erro de serialização.

O proxy de serviço lida com todas as exceções de failover para a partição de serviço para a qual foi criado. Ele resolve novamente os pontos de extremidade se houver exceções de failover (exceções não transitórias) e tenta novamente a chamada com o ponto de extremidade correto. O número de novas tentativas para exceções de failover é indefinido. Se ocorrerem exceções transitórias, o proxy tentará novamente a chamada.

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

Um usuário pode configurar esses valores passando o objeto OperationRetrySettings para o construtor ServiceProxyFactory.

Use a pilha V2 de comunicação remota

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

As abordagens a seguir estão disponíveis para habilitar a pilha V2.

Use um atributo assembly para usar a pilha V2

Estas etapas alteram o código do modelo para usar a pilha V2 usando um atributo assembly.

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

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Use o Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners método de extensão para criar ouvintes remotos (iguais para V1 e V2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Marque o assembly que contém as interfaces de comunicação remota com um FabricTransportServiceRemotingProvider atributo.

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

Nenhuma alteração de código é necessária no projeto cliente. Crie o assembly do cliente com o assembly da interface para certificar-se de que o atributo assembly mostrado anteriormente seja usado.

Use classes V2 explícitas para usar a pilha V2

Como alternativa ao uso de um atributo assembly, a pilha V2 também pode ser habilitada usando classes V2 explícitas.

Estas etapas alteram o código do modelo para usar a pilha V2 usando classes V2 explícitas.

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

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Use FabricTransportServiceRemotingListener do Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime namespace.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Use FabricTransportServiceRemotingClientFactory do Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client namespace para criar clientes.

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

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

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

  1. Atualize o serviço V1 para o serviço V2 usando este atributo. Essa alteração garante que o serviço ouça no ouvinte V1 e V2.

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

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

    b. Use o seguinte método de extensão para criar um ouvinte remoto.

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

    c. Adicione um atributo assembly em interfaces remotas para usar o ouvinte 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 usando o atributo de cliente V2. Esta etapa garante que o cliente use a pilha V2. Nenhuma alteração no projeto/serviço do cliente é necessária. Construir projetos de cliente com montagem de interface atualizada é suficiente.

  3. Este passo é opcional. Use o atributo de ouvinte V2 e atualize o serviço V2. Esta etapa garante que o serviço esteja ouvindo apenas no ouvinte V2.

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

Use a pilha de comunicação remota V2 (compatível com interface)

A pilha V2 (compatível com interface) é conhecida como V2_1 e é a versão mais atualizada. Ele tem todos os recursos da pilha de comunicação remota V2. Sua pilha de interface é compatível com a pilha V1 remota, mas não é compatível com V2 e V1. Para atualizar de V1 para V2_1 sem afetar a disponibilidade do serviço, siga as etapas no artigo Atualizar de V1 para V2 (compatível com interface).

Use um atributo assembly para usar a pilha V2 (compatível com interface) de comunicação remota

Siga estas etapas para mudar para uma pilha de V2_1.

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

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Use o método de extensão remota para criar um ouvinte remoto.

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

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

Nenhuma alteração é necessária no projeto cliente. Crie o assembly do cliente com o assembly da interface para certificar-se de que o atributo assembly anterior está sendo usado.

Use classes de comunicação remota explícitas para criar uma fábrica de ouvintes/clientes para a versão V2 (compatível com interface)

Siga estes passos:

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

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Use o ouvinte V2 remoto. O nome do recurso de ponto de extremidade de serviço padrão usado é "ServiceEndpointV2_1". Deve ser definido no manifesto de 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. Use a fábrica do cliente V2.

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

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

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

Nota

Ao atualizar de V1 para V2, verifique se o Remoting namespace está atualizado para usar V2. Exemplo: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. Atualize o serviço V1 para V2_1 serviço usando o seguinte atributo. Essa alteração garante que o serviço esteja escutando na V1 e no ouvinte V2_1.

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

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

    b. Use o seguinte método de extensão para criar um ouvinte remoto.

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

    c. Adicione um atributo assembly em interfaces remotas para usar o cliente V1, V2_1 ouvinte 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 usando o atributo V2_1 client. Esta etapa garante que o cliente esteja usando a pilha de V2_1. Nenhuma alteração no projeto/serviço do cliente é necessária. Construir projetos de cliente com montagem de interface atualizada é suficiente.

  3. Este passo é opcional. Remova a versão do ouvinte V1 do atributo e, em seguida, atualize o serviço V2. Esta etapa garante que o serviço esteja ouvindo apenas no ouvinte V2.

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

Usar serialização personalizada com uma mensagem remota encapsulada

Para uma mensagem com comunicação remota, criamos um único objeto encapsulado com todos os parâmetros como um campo. Siga estes passos:

  1. Implemente a IServiceRemotingMessageSerializationProvider interface para fornecer implementação para serialização personalizada. Este trecho de código mostra a aparência 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 provedor de serialização padrão por JsonSerializationProvider para um ouvinte remoto.

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

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

Próximos passos