Bagikan melalui


Cara menggunakan API komunikasi Reliable Services

Azure Service Fabric sebagai platform tidak memihak tentang komunikasi antar layanan. Semua protokol dan tumpukan dapat diterima, dari UDP ke HTTP. Terserah pengembang layanan untuk memilih bagaimana layanan harus berkomunikasi. Kerangka kerja aplikasi Reliable Services menyediakan tumpukan komunikasi bawaan serta API yang dapat Anda gunakan untuk membangun komponen komunikasi kustom Anda.

Menyiapkan komunikasi layanan

Reliable Services API menggunakan antarmuka sederhana untuk komunikasi layanan. Untuk membuka titik akhir untuk layanan Anda, cukup terapkan antarmuka ini:


public interface ICommunicationListener
{
    Task<string> OpenAsync(CancellationToken cancellationToken);

    Task CloseAsync(CancellationToken cancellationToken);

    void Abort();
}

public interface CommunicationListener {
    CompletableFuture<String> openAsync(CancellationToken cancellationToken);

    CompletableFuture<?> closeAsync(CancellationToken cancellationToken);

    void abort();
}

Anda kemudian dapat menambahkan implementasi listener komunikasi Anda dengan mengembalikannya dalam pengambil alihan metode kelas berbasis layanan.

Untuk layanan tanpa status:

public class MyStatelessService : StatelessService
{
    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        ...
    }
    ...
}
public class MyStatelessService extends StatelessService {

    @Override
    protected List<ServiceInstanceListener> createServiceInstanceListeners() {
        ...
    }
    ...
}

Untuk layanan stateful:

    @Override
    protected List<ServiceReplicaListener> createServiceReplicaListeners() {
        ...
    }
    ...
public class MyStatefulService : StatefulService
{
    protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
    {
        ...
    }
    ...
}

Dalam kedua kasus, Anda mengembalikan koleksi listener. Menggunakan beberapa listener memungkinkan layanan Anda untuk mendengarkan di beberapa titik akhir, berpotensi menggunakan protokol yang berbeda. Misalnya, Anda mungkin memiliki listener HTTP dan listener WebSocket terpisah. Anda dapat bermigrasi dari tidak aman ke jarak jauh aman dengan mengaktifkan kedua skenario terlebih dahulu dengan memiliki pendengar yang tidak aman dan pendengar yang aman. Setiap listener mendapatkan nama, dan koleksi pasangan nama : alamat yang dihasilkan diwakili sebagai objek JSON ketika klien meminta alamat mendengarkan untuk instans layanan atau partisi.

Dalam layanan stateless, operasi pengambil alihan mengembalikan koleksi ServiceInstanceListeners. ServiceInstanceListener berisi fungsi untuk membuat ICommunicationListener(C#) / CommunicationListener(Java) dan memberinya nama. Untuk layanan stateful, operasi pengambil alihan akan mengembalikan koleksi ServiceReplicaListeners. Ini sedikit berbeda dari rekan tanpa statusnya, karena ServiceReplicaListener memiliki opsi untuk membuka ICommunicationListener di replika sekunder. Anda tidak hanya dapat menggunakan beberapa listener komunikasi dalam layanan, tetapi Anda juga dapat menentukan listener mana yang menerima permintaan pada replika sekunder dan mana yang hanya mendengarkan replika utama.

Misalnya, Anda dapat memiliki ServiceRemotingListener yang hanya menerima panggilan RPC pada replika utama, dan listener kustom kedua yang mengambil permintaan baca pada replika sekunder melalui HTTP:

protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
    return new[]
    {
        new ServiceReplicaListener(context =>
            new MyCustomHttpListener(context),
            "HTTPReadonlyEndpoint",
            true),

        new ServiceReplicaListener(context =>
            this.CreateServiceRemotingListener(context),
            "rpcPrimaryEndpoint",
            false)
    };
}

Catatan

Saat membuat beberapa listener untuk layanan, setiap listener harus diberi nama yang unik.

Terakhir, jelaskan titik akhir yang diperlukan untuk layanan dalam manifes layanan di bawah bagian pada titik akhir.

<Resources>
    <Endpoints>
      <Endpoint Name="WebServiceEndpoint" Protocol="http" Port="80" />
      <Endpoint Name="OtherServiceEndpoint" Protocol="tcp" Port="8505" />
    <Endpoints>
</Resources>

Listener komunikasi dapat mengakses sumber daya titik akhir yang dialokasikan dari CodePackageActivationContext di ServiceContext. Listener kemudian dapat mulai mendengarkan permintaan ketika dibuka.

var codePackageActivationContext = serviceContext.CodePackageActivationContext;
var port = codePackageActivationContext.GetEndpoint("ServiceEndpoint").Port;

CodePackageActivationContext codePackageActivationContext = serviceContext.getCodePackageActivationContext();
int port = codePackageActivationContext.getEndpoint("ServiceEndpoint").getPort();

Catatan

Sumber daya titik akhir umum untuk seluruh paket layanan, dan mereka dialokasikan oleh Service Fabric ketika paket layanan diaktifkan. Beberapa replika layanan yang dihosting di ServiceHost yang sama dapat memiliki port yang sama. Artinya, listener komunikasi harus mendukung berbagi port. Cara yang disarankan untuk melakukannya adalah agar listener komunikasi menggunakan ID partisi dan ID replika/instans ketika menghasilkan alamat yang akan mendengarkan.

Pendaftaran alamat layanan

Layanan sistem yang disebut Layanan Penamaan berjalan pada kluster Service Fabric. Layanan Penamaan adalah registrar untuk layanan dan alamat mereka yang didengarkan oleh setiap instans atau replika layanan. Ketika metode OpenAsync(C#) / openAsync(Java) dari ICommunicationListener(C#) / CommunicationListener(Java) selesai, nilai pengembaliannya akan terdaftar di Layanan Penamaan. Nilai pengembalian ini yang diterbitkan dalam Layanan Penamaan adalah string yang nilainya bisa apa saja. Nilai string ini adalah apa yang klien lihat ketika mereka meminta alamat untuk layanan dari Layanan Penamaan.

public Task<string> OpenAsync(CancellationToken cancellationToken)
{
    EndpointResourceDescription serviceEndpoint = serviceContext.CodePackageActivationContext.GetEndpoint("ServiceEndpoint");
    int port = serviceEndpoint.Port;

    this.listeningAddress = string.Format(
                CultureInfo.InvariantCulture,
                "http://+:{0}/",
                port);

    this.publishAddress = this.listeningAddress.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN);

    this.webApp = WebApp.Start(this.listeningAddress, appBuilder => this.startup.Invoke(appBuilder));

    // the string returned here will be published in the Naming Service.
    return Task.FromResult(this.publishAddress);
}
public CompletableFuture<String> openAsync(CancellationToken cancellationToken)
{
    EndpointResourceDescription serviceEndpoint = serviceContext.getCodePackageActivationContext.getEndpoint("ServiceEndpoint");
    int port = serviceEndpoint.getPort();

    this.publishAddress = String.format("http://%s:%d/", FabricRuntime.getNodeContext().getIpAddressOrFQDN(), port);

    this.webApp = new WebApp(port);
    this.webApp.start();

    /* the string returned here will be published in the Naming Service.
     */
    return CompletableFuture.completedFuture(this.publishAddress);
}

Service Fabric menyediakan API yang memungkinkan klien dan layanan lain meminta alamat ini berdasarkan nama layanan. Ini penting karena alamat layanan tidak statis. Layanan dipindahkan dalam kluster untuk penyeimbangan sumber daya dan tujuan ketersediaan. Ini adalah mekanisme yang memungkinkan klien untuk menyelesaikan alamat mendengarkan layanan.

Catatan

Untuk panduan lengkap tentang cara menulis listener komunikasi, lihat layanan Service Fabric Web API dengan OWIN yang dihosting secara mandiri untuk C #, sedangkan untuk Java Anda dapat menulis implementasi server HTTP Anda sendiri, lihat contoh aplikasi EchoServer di https://github.com/Azure-Samples/service-fabric-java-getting-started.

Berkomunikasi dengan layanan

Reliable Services API menyediakan pustaka berikut untuk menulis klien yang berkomunikasi dengan layanan.

Resolusi titik akhir layanan

Langkah pertama untuk berkomunikasi dengan layanan adalah menyelesaikan alamat titik akhir dari partisi atau instans layanan yang ingin Anda ajak berkomunikasi. Kelas utilitas ServicePartitionResolver(C#) / FabricServicePartitionResolver(Java) adalah panduan dasar yang membantu klien menentukan titik akhir layanan pada runtime. Dalam terminologi Service Fabric, proses penentuan titik akhir layanan disebut sebagai resolusi titik akhir layanan.

Untuk terhubung ke layanan dalam kluster, ServicePartitionResolver dapat dibuat menggunakan pengaturan default. Ini adalah penggunaan yang disarankan untuk sebagian besar situasi:

ServicePartitionResolver resolver = ServicePartitionResolver.GetDefault();
FabricServicePartitionResolver resolver = FabricServicePartitionResolver.getDefault();

Untuk menyambungkan ke layanan di kluster lain, ServicePartitionResolver dapat dibuat dengan set titik akhir gateway kluster. Perhatikan bahwa titik akhir gateway hanyalah titik akhir yang berbeda untuk menyambungkan ke kluster yang sama. Contohnya:

ServicePartitionResolver resolver = new  ServicePartitionResolver("mycluster.cloudapp.azure.com:19000", "mycluster.cloudapp.azure.com:19001");
FabricServicePartitionResolver resolver = new  FabricServicePartitionResolver("mycluster.cloudapp.azure.com:19000", "mycluster.cloudapp.azure.com:19001");

Atau, ServicePartitionResolver dapat diberikan fungsi untuk membuat penggunaan FabricClient secara internal:

public delegate FabricClient CreateFabricClientDelegate();
public FabricServicePartitionResolver(CreateFabricClient createFabricClient) {
...
}

public interface CreateFabricClient {
    public FabricClient getFabricClient();
}

FabricClient adalah objek yang digunakan untuk berkomunikasi dengan kluster Service Fabric untuk berbagai operasi manajemen pada kluster. Ini berguna ketika Anda ingin lebih mengontrol bagaimana pemecah partisi layanan berinteraksi dengan kluster Anda. FabricClient melakukan penembolokan secara internal dan umumnya mahal untuk dibuat, jadi penting untuk menggunakan kembali FabricClient instans sebanyak mungkin.

ServicePartitionResolver resolver = new  ServicePartitionResolver(() => CreateMyFabricClient());
FabricServicePartitionResolver resolver = new  FabricServicePartitionResolver(() -> new CreateFabricClientImpl());

Metode penyelesaiannya kemudian digunakan untuk mengambil alamat layanan atau partisi layanan untuk layanan yang dipartisi.

ServicePartitionResolver resolver = ServicePartitionResolver.GetDefault();

ResolvedServicePartition partition =
    await resolver.ResolveAsync(new Uri("fabric:/MyApp/MyService"), new ServicePartitionKey(), cancellationToken);
FabricServicePartitionResolver resolver = FabricServicePartitionResolver.getDefault();

CompletableFuture<ResolvedServicePartition> partition =
    resolver.resolveAsync(new URI("fabric:/MyApp/MyService"), new ServicePartitionKey());

Alamat layanan dapat diselesaikan dengan mudah menggunakan ServicePartitionResolver, tetapi lebih banyak pekerjaan diperlukan untuk memastikan alamat yang diselesaikan dapat digunakan dengan benar. Klien Anda perlu mendeteksi apakah upaya koneksi gagal karena kesalahan sementara dan dapat dicoba (misalnya, layanan dipindahkan atau sementara tidak tersedia), atau kesalahan permanen (misalnya, layanan telah dihapus atau sumber daya yang diminta tidak lagi ada). Instans atau replika layanan dapat berpindah-pindah dari simpul ke simpul kapan saja karena beberapa alasan. Alamat layanan yang diselesaikan melalui ServicePartitionResolver mungkin basi pada saat kode klien Anda mencoba untuk terhubung. Dalam hal ini lagi klien perlu menyelesaikan kembali alamat. Memberikan indikasi sebelumnya ResolvedServicePartition bahwa resolver perlu mencoba lagi daripada hanya mengambil alamat cache.

Biasanya, kode klien tidak perlu menggunakan ServicePartitionResolver secara langsung. Ini dibuat dan diteruskan ke pabrik klien komunikasi di Reliable Services API. Pabrik menggunakan resolver secara internal untuk menghasilkan objek klien yang dapat digunakan untuk berkomunikasi dengan layanan.

Klien dan pabrik komunikasi

Perpustakaan pabrik komunikasi menerapkan pola coba lagi yang menangani kesalahan yang tipikal, yang memudahkan proses cobalagi koneksi ke endpoint layanan yang diselesaikan. Pustaka pabrik menyediakan mekanisme coba lagi saat Anda menyediakan handler kesalahan.

ICommunicationClientFactory(C#) / CommunicationClientFactory(Java) mendefinisikan antarmuka dasar yang diimplementasikan oleh pabrik klien komunikasi yang menghasilkan klien yang dapat berbicara dengan layanan Service Fabric. Implementasi CommunicationClientFactory bergantung pada tumpukan komunikasi yang digunakan oleh layanan Service Fabric di mana klien ingin berkomunikasi. Reliable Services API menyediakan CommunicationClientFactoryBase<TCommunicationClient>. Ini menyediakan implementasi dasar antarmuka CommunicationClientFactory dan melakukan tugas yang umum untuk semua tumpukan komunikasi. (Tugas ini termasuk menggunakan ServicePartitionResolver untuk menentukan titik akhir layanan). Klien biasanya mengimplementasikan kelas CommunicationClientFactoryBase abstrak untuk menangani logika yang spesifik untuk tumpukan komunikasi.

Klien komunikasi baru saja menerima alamat dan menggunakannya untuk terhubung ke layanan. Klien dapat menggunakan protokol apa pun yang diinginkannya.

public class MyCommunicationClient : ICommunicationClient
{
    public ResolvedServiceEndpoint Endpoint { get; set; }

    public string ListenerName { get; set; }

    public ResolvedServicePartition ResolvedServicePartition { get; set; }
}
public class MyCommunicationClient implements CommunicationClient {

    private ResolvedServicePartition resolvedServicePartition;
    private String listenerName;
    private ResolvedServiceEndpoint endPoint;

    /*
     * Getters and Setters
     */
}

Pabrik klien terutama bertanggung jawab untuk menciptakan klien komunikasi. Untuk klien yang tidak mempertahankan koneksi persisten, seperti klien HTTP, pabrik hanya perlu membuat dan mengembalikan klien. Protokol lain yang mempertahankan koneksi persisten, seperti beberapa protokol biner, juga harus divalidasi (ValidateClient(string endpoint, MyCommunicationClient client)) oleh pabrik untuk menentukan apakah koneksi perlu dibuat kembali.

public class MyCommunicationClientFactory : CommunicationClientFactoryBase<MyCommunicationClient>
{
    protected override void AbortClient(MyCommunicationClient client)
    {
    }

    protected override Task<MyCommunicationClient> CreateClientAsync(string endpoint, CancellationToken cancellationToken)
    {
    }

    protected override bool ValidateClient(MyCommunicationClient clientChannel)
    {
    }

    protected override bool ValidateClient(string endpoint, MyCommunicationClient client)
    {
    }
}
public class MyCommunicationClientFactory extends CommunicationClientFactoryBase<MyCommunicationClient> {

    @Override
    protected boolean validateClient(MyCommunicationClient clientChannel) {
    }

    @Override
    protected boolean validateClient(String endpoint, MyCommunicationClient client) {
    }

    @Override
    protected CompletableFuture<MyCommunicationClient> createClientAsync(String endpoint) {
    }

    @Override
    protected void abortClient(MyCommunicationClient client) {
    }
}

Akhirnya, handler pengecualian bertanggung jawab untuk menentukan tindakan apa yang harus diambil ketika pengecualian terjadi. Pengecualian dikategorikan menjadi dapat dicoba kembali dan tidak dapat dicoba kembali.

  • Pengecualian yang tidak dapat dicoba kembali hanya akan ditampilkan ke pemanggil.
  • Pengecualian yang dapat dicoba kembali lebih lanjut dikategorikan menjadi sementara dan permanen.
    • Pengecualian sementara adalah pengecualian yang dapat dicoba tanpa menyelesaikan kembali alamat titik akhir layanan. Ini akan mencakup masalah jaringan sementara atau respons kesalahan layanan selain yang menunjukkan alamat titik akhir layanan tidak ada.
    • Pengecualian permanen adalah pengecualian yang mengharuskan alamat titik akhir layanan diselesaikan kembali. Ini termasuk pengecualian yang menunjukkan titik akhir layanan tidak dapat dicapai, menunjukkan layanan telah pindah ke simpul yang berbeda.

TryHandleException membuat keputusan tentang pengecualian yang diberikan. Jika tidak tahu keputusan apa yang harus dibuat tentang pengecualian, hal ini harus mengembalikan false. Jika tidak tahu keputusan apa yang harus dibuat, hal ini harus menetapkan hasil yang sesuai dan mengembalikan true.

class MyExceptionHandler : IExceptionHandler
{
    public bool TryHandleException(ExceptionInformation exceptionInformation, OperationRetrySettings retrySettings, out ExceptionHandlingResult result)
    {
        // if exceptionInformation.Exception is known and is transient (can be retried without re-resolving)
        result = new ExceptionHandlingRetryResult(exceptionInformation.Exception, true, retrySettings, retrySettings.DefaultMaxRetryCount);
        return true;


        // if exceptionInformation.Exception is known and is not transient (indicates a new service endpoint address must be resolved)
        result = new ExceptionHandlingRetryResult(exceptionInformation.Exception, false, retrySettings, retrySettings.DefaultMaxRetryCount);
        return true;

        // if exceptionInformation.Exception is unknown (let the next IExceptionHandler attempt to handle it)
        result = null;
        return false;
    }
}
public class MyExceptionHandler implements ExceptionHandler {

    @Override
    public ExceptionHandlingResult handleException(ExceptionInformation exceptionInformation, OperationRetrySettings retrySettings) {

        /* if exceptionInformation.getException() is known and is transient (can be retried without re-resolving)
         */
        result = new ExceptionHandlingRetryResult(exceptionInformation.getException(), true, retrySettings, retrySettings.getDefaultMaxRetryCount());
        return true;


        /* if exceptionInformation.getException() is known and is not transient (indicates a new service endpoint address must be resolved)
         */
        result = new ExceptionHandlingRetryResult(exceptionInformation.getException(), false, retrySettings, retrySettings.getDefaultMaxRetryCount());
        return true;

        /* if exceptionInformation.getException() is unknown (let the next ExceptionHandler attempt to handle it)
         */
        result = null;
        return false;

    }
}

Merangkum semuanya

Dengan ICommunicationClient(C#) / CommunicationClient(Java), ICommunicationClientFactory(C#) / CommunicationClientFactory(Java), dan IExceptionHandler(C#) / ExceptionHandler(Java) dibangun di sekitar protokol komunikasi, ServicePartitionClient(C#) / FabricServicePartitionClient(Java) membungkus semuanya bersama-sama dan menyediakan penanganan kesalahan dan perulangan resolusi alamat partisi layanan di sekitar komponen ini.

private MyCommunicationClientFactory myCommunicationClientFactory;
private Uri myServiceUri;

var myServicePartitionClient = new ServicePartitionClient<MyCommunicationClient>(
    this.myCommunicationClientFactory,
    this.myServiceUri,
    myPartitionKey);

var result = await myServicePartitionClient.InvokeWithRetryAsync(async (client) =>
   {
      // Communicate with the service using the client.
   },
   CancellationToken.None);

private MyCommunicationClientFactory myCommunicationClientFactory;
private URI myServiceUri;

FabricServicePartitionClient myServicePartitionClient = new FabricServicePartitionClient<MyCommunicationClient>(
    this.myCommunicationClientFactory,
    this.myServiceUri,
    myPartitionKey);

CompletableFuture<?> result = myServicePartitionClient.invokeWithRetryAsync(client -> {
      /* Communicate with the service using the client.
       */
   });

Langkah berikutnya