Bagikan melalui


Remoting layanan di C# dengan Layanan Andal

Untuk layanan yang tidak terkait dengan protokol atau tumpukan komunikasi tertentu, seperti Web API, Windows Communication Foundation (WCF), atau lainnya, kerangka kerja Reliable Service menyediakan mekanisme remoting untuk menyiapkan panggilan prosedur jarak jauh untuk layanan dengan cepat dan mudah. Artikel ini membahas cara menyiapkan panggilan prosedur jarak jauh untuk layanan yang ditulis dengan C#.

Menyiapkan jarak jauh pada layanan

Anda dapat menyiapkan jarak jauh untuk layanan dengan dua langkah sederhana:

  1. Buat antarmuka untuk diimplementasikan oleh layanan Anda. Antarmuka ini akan menentukan metode yang akan tersedia untuk panggilan prosedur jarak jauh pada layanan Anda. Metode tersebut harus berupa metode asinkron yang mengembalikan tugas. Antarmuka harus mengimplementasikan Microsoft.ServiceFabric.Services.Remoting.IService untuk memberi sinyal bahwa layanan memiliki antarmuka jarak jauh.
  2. Gunakan listener remoting di layanan Anda. Listener remoting adalah implementasi ICommunicationListener yang memberikan kemampuan jarak jauh. Namespace Microsoft.ServiceFabric.Services.Remoting.Runtime berisi metode ekstensi CreateServiceRemotingInstanceListeners untuk layanan stateless dan stateful yang dapat digunakan untuk membuat listener remoting menggunakan protokol transportasi remoting default.

Catatan

Namespace Remoting tersedia sebagai paket NuGet terpisah yang disebut Microsoft.ServiceFabric.Services.Remoting.

Misalnya, layanan stateless berikut mengekspos satu metode untuk mendapatkan "Halo Dunia" melalui panggilan prosedur jarak jauh.

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

Catatan

Argumen dan jenis pengembalian dalam antarmuka layanan dapat menjadi jenis sederhana, kompleks, atau kustom, tetapi harus dapat diserialisasikan dengan .NET DataContractSerializer.

Metode layanan jarak jauh panggilan

Catatan

Jika Anda menggunakan lebih dari satu partisi, ServiceProxy.Create() harus diberikan ServicePartitionKey yang sesuai. Ini tidak diperlukan untuk satu skenario partisi.

Memanggil metode pada layanan dengan menggunakan tumpukan remoting dilakukan menggunakan proksi lokal ke layanan melalui kelas Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy. Metode ServiceProxy membuat proksi lokal dengan menggunakan antarmuka yang sama dengan yang diterapkan layanan. Dengan proksi tersebut, Anda bisa memanggil metode pada antarmuka dari jarak jauh.


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

string message = await helloWorldClient.HelloWorldAsync();

Kerangka kerja remoting menyebarluaskan pengecualian yang dilemparkan ke layanan kepada klien. Hasilnya, ketika ServiceProxy digunakan, klien bertanggung jawab untuk menangani pengecualian yang dilemparkan oleh layanan.

Masa pakai proksi layanan

Pembuatan layanan adalah operasi yang ringan, sehingga Anda dapat membuat sebanyak yang Anda butuhkan. Instans proksi Layanan dapat digunakan kembali selama instans tersebut diperlukan. Jika panggilan prosedur jarak jauh melempar pengecualian, Anda masih dapat menggunakan kembali instans proksi yang sama. Setiap proksi layanan berisi klien komunikasi yang digunakan untuk mengirim pesan melalui kawat. Saat melakukan panggilan jarak jauh, pemeriksaan internal dilakukan untuk menentukan apakah klien komunikasi valid. Berdasarkan hasil pemeriksaan tersebut, klien komunikasi akan dibuat ulang jika diperlukan. Oleh karena itu, jika pengecualian terjadi, Anda tidak perlu membuat ulang ServiceProxy.

Masa pakai pabrik proksi layanan

ServiceProxyFactory adalah pabrik yang membuat instans proksi untuk antarmuka remoting yang berbeda. Jika Anda menggunakan API ServiceProxyFactory.CreateServiceProxy untuk membuat proksi, kerangka kerja akan membuat proksi layanan singleton. Hal ini berguna untuk membuatnya secara manual ketika Anda perlu menimpa properti IServiceRemotingClientFactory. Pembuatan pabrik adalah operasi yang mahal. Pabrik proxy layanan mempertahankan cache internal klien komunikasi. Praktik terbaik adalah dengan menyinggahkan pabrik proksi layanan selama mungkin.

Penanganan pengecualian remoting

Semua pengecualian jarak jauh yang dilemparkan oleh API layanan dikirim kembali ke klien sebagai AggregateException. Pengecualian jarak jauh harus dapat diserialisasikan oleh DataContract. Jika tidak, API proksi melempar ServiceException dengan kesalahan serialisasi di dalamnya.

ServiceProxy menangani semua pengecualian failover untuk partisi layanan yang dibuatnya. Ini menyelesaikan kembali titik akhir jika ada pengecualian failover (pengecualian non-sementara) dan mencoba kembali panggilan dengan titik akhir yang benar. Jumlah percobaan ulang untuk pengecualian failover tidak terbatas. Jika pengecualian sementara terjadi, proksi akan melakukan panggilan.

Parameter percobaan kembali default disediakan oleh OperationRetrySettings.

Seorang pengguna dapat mengonfigurasi nilai-nilai ini dengan melewati objek OperationRetrySettings ke konstruktor ServiceProxyFactory.

Menggunakan tumpukan remoting V2

Pada versi 2.8 dari paket remoting NuGet, Anda memiliki opsi untuk menggunakan tumpukan remoting V2. Tumpukan remoting V2 berperforma lebih baik. Ini juga menyediakan fitur seperti serialisasi kustom dan API yang lebih dapat dicolokkan. Kode templat terus menggunakan tumpukan remoting V1. Remoting V2 tidak kompatibel dengan V1 (tumpukan remoting sebelumnya). Ikuti instruksi dalam artikel Tingkatkan dari V1 ke V2 untuk menghindari dampak pada ketersediaan layanan.

Pendekatan berikut tersedia untuk mengaktifkan tumpukan V2.

Gunakan atribut assembly untuk menggunakan tumpukan V2

Langkah-langkah ini mengubah kode templat untuk menggunakan tumpukan V2 dengan menggunakan atribut assembly.

  1. Ubah sumber daya titik akhir dari "ServiceEndpoint" ke "ServiceEndpointV2" dalam manifes layanan.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Gunakan metode ekstensi Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners untuk membuat listener remoting (sama untuk V1 dan V2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Tandai assembly yang berisi antarmuka remoting dengan atribut FabricTransportServiceRemotingProvider.

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

Tidak ada perubahan kode yang diperlukan dalam proyek klien. Buat perakitan klien dengan assembly antarmuka untuk memastikan bahwa atribut assembly yang sebelumnya ditampilkan digunakan.

Menggunakan kelas V2 eksplisit untuk menggunakan tumpukan V2

Sebagai alternatif untuk menggunakan atribut assembly, tumpukan V2 juga dapat diaktifkan menggunakan kelas V2 eksplisit.

Langkah-langkah ini mengubah kode templat untuk menggunakan tumpukan V2 dengan menggunakan kelas V2 eksplisit.

  1. Ubah sumber daya titik akhir dari "ServiceEndpoint" ke "ServiceEndpointV2" dalam manifes layanan.

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

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Gunakan FabricTransportServiceRemotingClientFactory dari namespace Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client untuk membuat klien.

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

Tingkatkan dari remoting V1 ke remoting V2

Untuk meningkatkan dari V1 ke V2, diperlukan peningkatan dua langkah. Ikuti langkah-langkah dalam urutan ini.

  1. Tingkatkan layanan V1 ke layanan V2 dengan menggunakan atribut ini. Perubahan ini memastikan bahwa layanan tersebut mendengarkan listener V1 dan V2.

    a. Tambahkan sumber daya titik akhir dengan nama "ServiceEndpointV2" dalam manifes layanan.

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

    b. Gunakan metode ekstensi berikut untuk membuat listener remoting.

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

    c. Tambahkan atribut assembly pada antarmuka remoting untuk menggunakan listener V1 dan V2 dan klien V2.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
    
    
  2. Tingkatkan klien V1 ke klien V2 menggunakan atribut klien V2. Langkah ini memastikan klien menggunakan tumpukan V2. Tidak diperlukan perubahan dalam proyek/layanan klien. Membangun proyek klien dengan assembly antarmuka yang diperbarui sudah cukup.

  3. Langkah ini bersifat opsional. Gunakan atribut listener V2, lalu tingkatkan layanan V2. Langkah ini memastikan bahwa layanan hanya mendengarkan listener V2.

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

Gunakan tumpukan remoting V2 (kompatibel antarmuka)

Tumpukan remoting V2 (kompatibel antarmuka) dikenal sebagai V2_1 dan merupakan versi terbaru. Ia memiliki semua fitur tumpukan remoting V2. Tumpukan antarmukanya kompatibel dengan tumpukan V1 remoting, tetapi tidak kompatibel mundur dengan V2 dan V1. Untuk meningkatkan dari V1 ke V2_1 tanpa memengaruhi ketersediaan layanan, ikuti langkah-langkah dalam artikel Meningkatkan dari V1 ke V2 (kompatibel antarmuka).

Gunakan atribut assembly untuk menggunakan tumpukan remoting V2 (kompatibel antarmuka)

Ikuti langkah-langkah ini untuk mengubah ke tumpukan V2_1.

  1. Tambahkan sumber daya titik akhir dengan nama "ServiceEndpointV2_1" dalam manifes layanan.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Gunakan metode ekstensi remoting untuk membuat listener remoting.

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Tambahkan atribut assembly pada antarmuka remoting.

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

Tidak ada perubahan yang diperlukan dalam proyek klien. Buat assembly klien dengan assembly antarmuka untuk memastikan bahwa atribut assembly sebelumnya sedang digunakan.

Gunakan kelas remoting eksplisit untuk membuat pabrik listener/klien untuk V2 (kompatibel antarmuka)

Ikuti langkah-langkah berikut:

  1. Tambahkan sumber daya titik akhir dengan nama "ServiceEndpointV2_1" dalam manifes layanan.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. Gunakan listener remoting V2. Nama sumber daya titik akhir layanan default yang digunakan adalah "ServiceEndpointV2_1." File harus ditentukan dalam manifes layanan.

    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 the pabrik klien V2.

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

Meningkatkan dari remoting V1 ke remoting V2 (kompatibel antarmuka)

Untuk meningkatkan dari V1 ke V2 (kompatibel antarmuka, V2_1, diperlukan peningkatan dua langkah. Ikuti langkah-langkah dalam urutan ini.

Catatan

Saat peningkatan dari V1 ke V2, pastikan namespace Remoting telah diperbarui untuk menggunakan V2. Contoh: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. Tingkatkan layanan V1 ke layanan V2_1 menggunakan atribut berikut. Perubahan ini memastikan bahwa layanan mendengarkan listener V1 dan V2_1.

    a. Tambahkan sumber daya titik akhir dengan nama "ServiceEndpointV2_1" dalam manifes layanan.

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

    b. Gunakan metode ekstensi berikut untuk membuat listener remoting.

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

    c. Tambahkan atribut assembly pada antarmuka remoting untuk menggunakan listener V1, V2_1, dan klien V2_1.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    
    
  2. Tingkatkan klien V1 ke klien V2_1 menggunakan klien V2_1. Langkah ini memastikan bahwa klien menggunakan tumpukan V2_1. Tidak diperlukan perubahan dalam proyek/layanan klien. Membangun proyek klien dengan assembly antarmuka yang diperbarui sudah cukup.

  3. Langkah ini bersifat opsional. Hapus versi listener V1 dari atribut, lalu tingkatkan layanan V2. Langkah ini memastikan bahwa layanan hanya mendengarkan listener V2.

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

Menggunakan serialisasi kustom dengan pesan yang dibungkus ulang

Untuk pesan yang dibungkus dengan remoting, kami membuat satu objek yang dibungkus dengan semua parameter sebagai bidang di dalamnya. Ikuti langkah-langkah berikut:

  1. Terapkan antarmuka IServiceRemotingMessageSerializationProvider guna menyediakan implementasi untuk serialisasi kustom. Cuplikan kode ini menunjukkan seperti apa implementasinya.

    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. Ganti penyedia serialisasi default dengan JsonSerializationProvider untuk listener remoting.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[]
        {
            new ServiceInstanceListener((c) =>
            {
                return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new         ServiceRemotingJsonSerializationProvider());
            })
        };
    }
    
  3. Ganti penyedia serialisasi default dengan JsonSerializationProvider untuk pabrik klien remoting.

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

Langkah berikutnya