Удаленное взаимодействие в Reliable Services с помощью C#

Для служб, которые не привязаны к определенному протоколу обмена данными или стеку, например веб-API, Windows Communication Foundation и другим, платформа Reliable Services предоставляет механизм удаленного взаимодействия для быстрой и простой настройки удаленного вызова процедур. В этой статье рассматривается, как настраивать удаленные вызовы процедур для служб на C#.

Настройка удаленного доступа в службе

Чтобы настроить для службы удаленный доступ, достаточно выполнить два простых шага:

  1. Создание интерфейса для реализации в службе. Этот интерфейс определяет методы, которые будут доступны для удаленного вызова процедур в службе. Эти методы должны быть асинхронными методами, возвращающими задачи. Интерфейс должен реализовать Microsoft.ServiceFabric.Services.Remoting.IService , чтобы показать, что служба имеет интерфейс удаленного взаимодействия.
  2. Используйте прослушиватель удаленного взаимодействия в службе. Прослушиватель удаленного взаимодействия — это реализация ICommunicationListener, которая предоставляет возможности удаленного взаимодействия. Пространство имен Microsoft.ServiceFabric.Services.Remoting.Runtime содержит метод расширения CreateServiceRemotingInstanceListeners для служб без отслеживания и с отслеживанием состояния, который позволяет создать прослушиватель удаленного взаимодействия с использованием соответствующего стандартного транспортного протокола.

Примечание

Пространство имен Remoting доступно как отдельный пакет NuGet Microsoft.ServiceFabric.Services.Remoting.

Например, приведенная ниже служба без отслеживания состояния предоставляет один метод для получения "Hello World" посредством удаленного вызова процедуры.

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

Примечание

Аргументы и возвращаемые данные в интерфейсе службы могут иметь простые, сложные или настраиваемые типы, но они должны поддерживать сериализацию с помощью .NET DataContractSerializer.

Вызов удаленных методов службы

Примечание

Если используется несколько секций, serviceProxy.Create() необходимо предоставить соответствующий ServicePartitionKey. Это не требуется для сценария с одной секцией.

Вызов методов в службе с помощью стека удаленного взаимодействия осуществляется с помощью локального прокси-сервера для службы через класс Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy . Метод ServiceProxy создает локальный прокси-сервер, используя тот же интерфейс, который реализует служба. С помощью этого прокси-сервера можно удаленно вызвать методы в интерфейсе.


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

string message = await helloWorldClient.HelloWorldAsync();

Платформа удаленного взаимодействия распространяет исключения, созданные службой, на клиент. Поэтому при использовании метода ServiceProxy клиент отвечает за обработку исключений, которые создает служба.

Время существования прокси службы

Создание прокси службы не требует больших ресурсов, поэтому вы можете создавать такие объекты в любых количествах. Экземпляры прокси службы можно использовать повторно. Если удаленный вызов процедуры создает исключение, вы по-прежнему можете использовать тот же экземпляр прокси-сервера. Каждый объект прокси-сервера службы содержит клиент обмена данными для отправки сообщений по сети. При запуске удаленных вызовов проводятся внутренние проверки для определения работоспособности этого клиента. В зависимости от результата проверки клиент может быть создан повторно. Поэтому, если возникает исключение, вам не нужно заново создавать ServiceProxy.

Время существования фабрики прокси-сервера службы

ServiceProxyFactory — это фабрика, которая создает экземпляры прокси-сервера для различных интерфейсов удаленного взаимодействия. Если для создания прокси-сервера вы используете API ServiceProxyFactory.CreateServiceProxy, платформа создает отдельный прокси-сервер службы. При необходимости переопределить свойства IServiceRemotingClientFactory имеет смысл создать фабрику вручную. Создание фабрики — ресурсоемкая операция. Фабрика прокси службы поддерживает внутренний кэш для клиента обмена данными. Мы рекомендуем кэшировать фабрику прокси-сервера службы на максимально возможный период времени.

Обработка исключений удаленного взаимодействия

Все исключения удаленного взаимодействия, порождаемые API службы, отправляются обратно в клиент как AggregateException. Исключения удаленного взаимодействия должны поддерживать сериализацию с помощью DataContract. В противном случае API прокси-сервера создает исключение ServiceException с ошибкой сериализации.

Прокси-сервер службы обрабатывает все исключения отработки отказа в той секции службы, для которой он создан. Он повторно разрешает конечные точки в случае исключений отработки отказа (повторяющихся исключений) и повторяет вызов к правильной конечной точке. Число повторных попыток для исключений отработки отказа не ограничено. Если порождаются временные исключения, прокси-сервер повторяет вызов.

Параметры повтора по умолчанию определяются OperationRetrySettings.

Пользователь может настроить эти значения, передав объект OperationRetrySettings в конструктор ServiceProxyFactory.

Использование стека удаленного взаимодействия версии 2

Пакет NuGet для удаленного взаимодействия версии 2.8 позволяет использовать стек удаленного взаимодействия версии 2. Стек удаленного взаимодействия версии 2 работает лучше. Также он предоставляет дополнительные функции, например пользовательскую сериализацию, и больше подключаемых API. Код шаблона по-прежнему использует стек удаленного взаимодействия версии 1. Удаленное взаимодействие версии 2 несовместим с версией 1 (предыдущий стек удаленного взаимодействия). Чтобы это не повлияло на доступность службы, соблюдайте инструкции из руководства по обновлению с версии 1 до версии 2.

Можно использовать следующий подход для включения стека версии 2.

Использование атрибута сборки для перехода на стек версии 2

Этот шаг изменяет код шаблона, добавляя атрибут сборки для использования стека версии 2.

  1. Измените ресурс конечной точки с "ServiceEndpoint" на "ServiceEndpointV2" в манифесте службы.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Используйте метод расширения Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners, чтобы создать прослушивателей удаленного взаимодействия (одинаково для версии 1 и 2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Пометьте сборку с удаленными интерфейсами атрибутом FabricTransportServiceRemotingProvider.

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

Вносить изменения в проект клиента не требуется. Скомпилируйте сборку клиента со сборкой интерфейса, чтобы гарантировать использование упомянутого выше атрибута сборки.

Использования явных классов версии 2 для перехода на стек версии 2

Вместо атрибута сборки можно использовать явные классы версии 2, чтобы включить стек версии 2.

Этот шаг изменяет код шаблона, добавляя явные классы версии 2 для использования стека версии 2.

  1. Измените ресурс конечной точки с "ServiceEndpoint" на "ServiceEndpointV2" в манифесте службы.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Используйте FabricTransportServiceRemotingListener из пространства имен Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Используйте FabricTransportServiceRemotingClientFactory из пространства имен Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client, чтобы создать клиенты.

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

Обновление службы удаленного взаимодействия с версии 1 до версии 2

Чтобы обновить версию 1 до версии 2, выполните двухэтапное обновление. Соблюдайте порядок действий в этой процедуре.

  1. Обновите службу версии 1 до версии 2 с помощью указанного атрибута. Такое изменение гарантирует, что служба будет ожидать передачу данных от прослушивателей версии 1 и 2.

    a. Добавьте в манифест службы ресурс конечной точки с именем ServiceEndpointV2.

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

    b. Используйте приведенный ниже метод расширения, чтобы создать прослушиватель удаленного взаимодействия.

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

    c. Добавьте атрибут сборки в интерфейсы удаленного взаимодействия, чтобы использовать прослушиватели версий 1 и 2 и клиент версии 2.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
    
    
  2. Обновите клиент версии 1 до версии 2 с помощью атрибута клиента версии 2. Этот шаг гарантирует, что клиент будет использовать стек версии 2. Никаких изменений в проект или службу клиента вносить не требуется. Достаточно выполнить сборку проектов клиента с обновленной сборкой интерфейса.

  3. Это необязательный шаг. Используйте атрибут прослушивателя версии 2 и обновите службу версии 2. Этот шаг гарантирует, что служба будет ожидать передачу данных только от прослушивателя версии 2.

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

Использование стека удаленного взаимодействия версии 2 (с совместимым интерфейсом)

Стек удаленного взаимодействия версии 2 (совместимый с интерфейсом) называется V2_1 и является самой актуальной версией. Он имеет все признаки стека удаленного взаимодействия версии 2. Стек его интерфейса совместим со стеком удаленного взаимодействия версии 1, но не обладает обратной совместимостью с версиями 1 и 2. Чтобы выполнить обновление версии 1 до версии 2_1, не влияя на доступность службы, следуйте действиям, описанным в этой статье.

Использование атрибута сборки для перехода на стек удаленного взаимодействия версии 2 (с совместимым интерфейсом)

Выполните следующие действия, чтобы перейти на стек V2_1.

  1. Добавьте в манифест службы ресурс конечной точки с именем ServiceEndpointV2_1.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Используйте метод расширения удаленного взаимодействия, чтобы создать прослушиватель удаленного взаимодействия.

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Добавьте атрибут сборки в интерфейсы удаленного взаимодействия.

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

Вносить изменения в проект клиента не требуется. Скомпилируйте сборку клиента со сборкой интерфейса, чтобы гарантировать использование упомянутого выше атрибута сборки.

Использование явных классов удаленного взаимодействия для создания прослушивателя и фабрики клиента для версии 2 (с совместимым интерфейсом)

Выполните следующие действия.

  1. Добавьте в манифест службы ресурс конечной точки с именем ServiceEndpointV2_1.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Используйте прослушиватель удаленного взаимодействия версии 2. Используемое имя ресурса конечной точки службы по умолчанию — ServiceEndpointV2_1. Оно должно быть определено в манифесте службы.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 var settings = new FabricTransportRemotingListenerSettings();
                 settings.UseWrappedMessage = true;
                 return new FabricTransportServiceRemotingListener(c, this,settings);
    
             })
         };
     }
    
  3. Используйте фабрику клиента версии 2.

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

Обновление службы удаленного взаимодействия с версии 1 до версии 2 (с совместимым интерфейсом).

Чтобы обновить версию 1 до версии 2 (с совместимым интерфейсом, или версия 2_1), выполните двухэтапное обновление. Соблюдайте порядок действий в этой процедуре.

Примечание

При обновлении с версии 1 на 2 убедитесь, что пространство имен Remoting обновлено для использования версии 2. Пример: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. Обновите службу версии 1 до версии 2_1 с помощью указанного ниже атрибута. Это изменение гарантирует, что служба будет ожидать передачи данных от прослушивателей версии 1 и 2_1.

    a. Добавьте в манифест службы ресурс конечной точки с именем ServiceEndpointV2_1.

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

    b. Используйте приведенный ниже метод расширения, чтобы создать прослушиватель удаленного взаимодействия.

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

    c. Добавьте атрибут сборки в интерфейсы удаленного взаимодействия, чтобы использовать прослушиватель версии 1 и 2_1 и клиент версии 2_1.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    
    
  2. Обновите клиент версии 1 до версии 2_1 с помощью атрибута клиента версии 2_1. Это гарантирует, что клиент будет использовать стек версии 2_1. Никаких изменений в проект или службу клиента вносить не требуется. Достаточно выполнить сборку проектов клиента с обновленной сборкой интерфейса.

  3. Это необязательный шаг. Удалите из атрибута прослушивателя версии 1 и обновите службу версии 2. Этот шаг гарантирует, что служба будет ожидать передачу данных только от прослушивателя версии 2.

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

Использование настраиваемой сериализации с упакованным сообщением удаленного взаимодействия

В упакованном сообщении удаленного взаимодействия мы создаем отдельный упакованный объект, в поле которого хранятся все параметры. Выполните следующие действия.

  1. Реализуйте интерфейс IServiceRemotingMessageSerializationProvider, чтобы обеспечить пользовательскую сериализацию. В этом фрагменте кода показан пример такой реализации.

    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. Переопределите поставщик сериализации по умолчанию, указав JsonSerializationProvider в качестве прослушивателя удаленного взаимодействия.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[]
        {
            new ServiceInstanceListener((c) =>
            {
                return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new         ServiceRemotingJsonSerializationProvider());
            })
        };
    }
    
  3. Переопределите поставщик сериализации по умолчанию, указав JsonSerializationProvider в качестве фабрики клиента удаленного взаимодействия.

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

Дальнейшие действия