كيفية استخدام واجهات برمجة تطبيقات اتصالات الخدمات الموثوقة

Azure Service Fabric كمنصة غير مرتبطة تمامًا حول التواصل بين الخدمات. جميع البروتوكولات والمكدسات مقبولة، من UDP إلى HTTP. الأمر متروك لمطور الخدمة لاختيار كيفية تواصل الخدمات. يوفر إطار عمل تطبيق الخدمات الموثوقة مكدسات اتصال مضمنة بالإضافة إلى واجهات برمجة التطبيقات التي يمكنك استخدامها لإنشاء مكونات الاتصال المخصصة.

إعداد الاتصال بالخدمة

تستخدم واجهة برمجة تطبيقات الخدمات الموثوقة واجهة بسيطة للاتصال بالخدمة. لفتح نقطة نهاية لخدمتك، ما عليك سوى تنفيذ هذه الواجهة:


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

يمكنك بعد ذلك إضافة تنفيذ مستمع الاتصال الخاص بك عن طريق إعادته في تجاوز طريقة الفصل المستندة إلى الخدمة.

للخدمات عديمة الحالة:

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

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

للخدمات ذات الحالة:

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

في كلتا الحالتين، يمكنك إرجاع مجموعة من المستمعين. يسمح استخدام مستمعين متعددين لخدمتك بالاستماع إلى نقاط نهاية متعددة، وربما باستخدام بروتوكولات مختلفة. على سبيل المثال، قد يكون لديك مستمع HTTP ومستمع WebSocket منفصل. يمكنك الترحيل من الاتصال عن بعد غير الآمن إلى الآمن عن طريق تمكين كلا السيناريوهين أولًا من خلال وجود مستمع غير آمن ومستمع آمن. يحصل كل مستمع على اسم، والمجموعة الناتجة من الاسم : العنوان يتم تمثيل الأزواج ككائن JSON عندما يطلب العميل عناوين الاستماع لمثيل خدمة أو قسم.

في خدمة عديمة الجنسية، يقوم التجاوز بإرجاع مجموعة من ServiceInstanceListeners. يحتوي ServiceInstanceListener على دالة لإنشاء ICommunicationListener(C#) / CommunicationListener(Java) ويعطيها اسمًا. بالنسبة للخدمات ذات الحالة، ترجع التجاوز مجموعة من ServiceReplicaListeners. هذا يختلف قليلًا عن نظيره عديم الحالة، لأن لديه خيار ServiceReplicaListener لفتح نسخة طبق الأصل على نسخ متماثلة ثانوية ICommunicationListener. لا يمكنك فقط استخدام مستمعي اتصال متعددين في إحدى الخدمات، ولكن يمكنك أيضًا تحديد المستمعين الذين يقبلون الطلبات على النسخ المتماثلة الثانوية والمستمعين الذين يستمعون فقط إلى النسخ المتماثلة الأساسية.

على سبيل المثال، يمكن أن يكون لديك ServiceRemotingListener الذي يتلقى مكالمات RPC فقط على النسخ المتماثلة الأساسية، ومستمع مخصص ثان يتلقى طلبات القراءة على النسخ المتماثلة الثانوية عبر HTTP:

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

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

ملاحظة

عند إنشاء مستمعين متعددين لخدمة ما، يجب إعطاء كل مستمع اسمًا فريدًا.

وأخيرًا، صف نقاط النهاية المطلوبة للخدمة في بيان الخدمة ضمن القسم الخاص بنقاط النهاية.

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

يمكن لمستمع الاتصال الوصول إلى موارد نقطة النهاية المخصصة له من CodePackageActivationContext في ServiceContext. يمكن للمستمع بعد ذلك بدء الاستماع للطلبات عند فتحه.

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

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

ملاحظة

تعد موارد نقطة النهاية شائعة في حزمة الخدمة بأكملها، ويتم تخصيصها بواسطة Service Fabric عند تنشيط حزمة الخدمة. قد تشترك النسخ المتماثلة متعددة للخدمة المستضافة في نفس ServiceHost في نفس المنفذ. هذا يعني أن مستمع الاتصال يجب أن يدعم مشاركة المنفذ. الطريقة الموصى بها للقيام بذلك هي أن يستخدم مستمع الاتصال معرف القسم ومعرف النسخة المتماثلة/ المثيل عند إنشاء عنوان الاستماع.

تسجيل عنوان الخدمة

يتم تشغيل خدمة نظام تسمى خدمة التسمية على مجموعات Service Fabric. خدمة التسمية هي مسجل للخدمات وعناوينها التي يستمع إليها كل مثيل أو نسخة طبق الأصل من الخدمة. عند اكتمال طريقة OpenAsync(C#) / openAsync(Java) لـICommunicationListener(C#) / CommunicationListener(Java)، يتم تسجيل قيمة الإرجاع الخاصة به في خدمة التسمية. قيمة الإرجاع هذه التي يتم نشرها في خدمة التسمية هي سلسلة يمكن أن تكون قيمتها أي شيء على الإطلاق. قيمة السلسلة هذه هي ما يراه العملاء عندما يطلبون عنوانا للخدمة من خدمة التسمية.

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 واجهات برمجة التطبيقات التي تسمح للعملاء والخدمات الأخرى بطلب هذا العنوان حسب اسم الخدمة. هذا مهم لأن عنوان الخدمة ليس ثابتًا. يتم نقل الخدمات في المجموعة لأغراض موازنة الموارد وتوافرها. هذه هي الآلية التي تسمح للعملاء بحل عنوان الاستماع للخدمة.

ملاحظة

للحصول على جولة كاملة حول كيفية كتابة مستمع اتصال، راجع خدمات Service Fabric Web API مع استضافة OWIN الذاتية لـC#، بينما بالنسبة إلى Java، يمكنك كتابة تنفيذ خادم HTTP الخاص بك، راجع مثال تطبيق EchoServer في https://github.com/Azure-Samples/service-fabric-java-getting-started.

التواصل مع خدمة

توفر واجهة برمجة تطبيقات الخدمات الموثوقة المكتبات التالية لكتابة العملاء الذين يتواصلون مع الخدمات.

دقة نقطة نهاية الخدمة

تتمثل الخطوة الأولى للاتصال بخدمة في حل عنوان نقطة نهاية لقسم أو مثيل الخدمة التي تريد التحدث إليها. الفئة المساعدة ServicePartitionResolver(C#) / FabricServicePartitionResolver(Java) هي بدائية أساسية تساعد العملاء على تحديد نقطة النهاية للخدمة في وقت التشغيل. في مصطلحات نسيج الخدمة، يشار إلى عملية تحديد نقطة نهاية الخدمة باسم دقة نقطة نهاية الخدمة.

للاتصال بالخدمات داخل نظام مجموعة، يمكن إنشاء ServicePartitionResolver باستخدام الإعدادات الافتراضية. هذا هو الاستخدام الموصى به لمعظم الحالات:

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

للاتصال بالخدمات في مجموعة مختلفة، يمكن إنشاء ServicePartitionResolver باستخدام مجموعة من نقاط نهاية بوابة المجموعة. لاحظ أن نقاط نهاية البوابة هي مجرد نقاط نهاية مختلفة للاتصال بنفس المجموعة. على سبيل المثال:

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");

بدلًا من ذلك، يمكن إعطاء ServicePartitionResolver وظيفة لإنشاء FabricClient لاستخدامها داخليًا:

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

public interface CreateFabricClient {
    public FabricClient getFabricClient();
}

FabricClient هو الكائن المستخدم للاتصال بمجموعة Service Fabric لعمليات الإدارة المختلفة على المجموعة. هذا مفيد عندما تريد المزيد من التحكم في كيفية تفاعل محلل أقسام الخدمة مع مجموعتك. FabricClient يقوم بالتخزين المؤقت داخليًا ويكون إنشاؤه مكلفا بشكل عام، لذلك من المهم إعادة استخدام المثيلات FabricClient قدر الإمكان.

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

ثم يتم استخدام طريقة حل لاسترداد عنوان خدمة أو قسم خدمة للخدمات المقسمة.

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

يمكن حل عنوان خدمة بسهولة باستخدام ServicePartitionResolver، ولكن هناك حاجة إلى مزيد من العمل لضمان إمكانية استخدام العنوان الذي تم حله بشكل صحيح. يحتاج العميل إلى اكتشاف ما إذا كانت محاولة الاتصال قد فشلت بسبب خطأ عابر ويمكن إعادة محاولتها (على سبيل المثال، تم نقل الخدمة أو كانت غير متوفرة مؤقتًا)، أو خطأ دائم (على سبيل المثال، تم حذف الخدمة أو لم يعد المورد المطلوب موجودًا). يمكن أن تنتقل مثيلات الخدمة أو النسخ المتماثلة من عقدة إلى أخرى في أي وقت لأسباب متعددة. قد يكون عنوان الخدمة الذي تم حله من خلال ServicePartitionResolver قديمًا بحلول الوقت الذي يحاول فيه رمز العميل الاتصال. في هذه الحالة مرة أخرى، يحتاج العميل إلى إعادة حل العنوان. يشير ResolvedServicePartition السابق إلى أن المحلل يحتاج إلى المحاولة مرة أخرى بدلًا من مجرد استرداد عنوان مخزن مؤقتًا.

عادة، لا يلزم أن تعمل التعليمات البرمجية للعميل مع ServicePartitionResolver مباشرة. يتم إنشاؤه وتمريره إلى مصانع عملاء الاتصالات في واجهة برمجة تطبيقات الخدمات الموثوقة. تستخدم المصانع المحلل داخليًا لإنشاء كائن عميل يمكن استخدامه للتواصل مع الخدمات.

عملاء الاتصالات والمصانع

تقوم مكتبة مصنع الاتصالات بتنفيذ نمط إعادة محاولة نموذجي لمعالجة الأخطاء يجعل إعادة محاولة الاتصالات بنقاط نهاية الخدمة التي تم حلها أسهل. توفر مكتبة المصنع آلية إعادة المحاولة في أثناء توفير معالجات الأخطاء.

يحدد ICommunicationClientFactory(C#) / CommunicationClientFactory(Java) الواجهة الأساسية التي ينفذها مصنع عميل الاتصالات الذي ينتج عملاء يمكنهم التحدث إلى خدمة Service Fabric. يعتمد تنفيذ CommunicationClientFactory على مكدس الاتصالات المستخدم من قبل خدمة Service Fabric حيث يريد العميل التواصل. توفر واجهة برمجة تطبيقات الخدمات الموثوقة CommunicationClientFactoryBase<TCommunicationClient>. يوفر هذا تطبيقًا أساسيًا لواجهة CommunicationClientFactory وينفذ المهام المشتركة بين كافة مكدسات الاتصال. (تتضمن هذه المهام استخدام ServicePartitionResolver لتحديد نقطة نهاية الخدمة). عادة ما يقوم العملاء بتنفيذ فئة CommunicationClientFactoryBase المجردة للتعامل مع المنطق الخاص بمكدس الاتصال.

يتلقى عميل الاتصال عنوانًا ويستخدمه للاتصال بخدمة. يمكن للعميل استخدام أي بروتوكول يريده.

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
     */
}

مصنع العميل هو المسؤول الأول عن إنشاء عملاء الاتصالات. بالنسبة للعملاء الذين لا يحتفظون باتصال مستمر، مثل عميل HTTP، يحتاج المصنع فقط إلى إنشاء العميل وإرجاعه. يجب أيضًا التحقق (ValidateClient(string endpoint, MyCommunicationClient client)) من صحة البروتوكولات الأخرى التي تحافظ على اتصال مستمر، مثل بعض البروتوكولات الثنائية، من قبل المصنع لتحديد ما إذا كان الاتصال يحتاج إلى إعادة إنشاء أم لا.

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) {
    }
}

وأخيرا، يكون معالج الاستثناء مسؤولا عن تحديد الإجراء الذي يجب اتخاذه عند حدوث استثناء. يتم تصنيف الاستثناءات إلى قابلة لإعادة المحاولةوغير قابلة لإعادة المحاولة.

  • يتم ببساطة إعادة طرح الاستثناءات غير القابلة لإعادة المحاولة إلى المتصل.
  • يتم تصنيف الاستثناءات القابلة لإعادة المحاولة إلى عابرة وغير عابرة.
    • الاستثناءات العابرة هي تلك التي يمكن ببساطة إعادة تجربتها دون إعادة حل عنوان نقطة نهاية الخدمة. وستشمل هذه المشاكل مشاكل الشبكة العابرة أو استجابات أخطاء الخدمة بخلاف تلك التي تشير إلى عدم وجود عنوان نقطة نهاية الخدمة.
    • الاستثناءات غير العابرة هي تلك التي تتطلب إعادة حل عنوان نقطة نهاية الخدمة. وتشمل هذه الاستثناءات التي تشير إلى تعذر الوصول إلى نقطة نهاية الخدمة، مما يشير إلى أن الخدمة قد انتقلت إلى عقدة مختلفة.

يتخذ TryHandleException قرارًا بشأن استثناء معين. إذا كان لا يعرف ما هي القرارات التي يجب اتخاذها بشأن استثناء، فيجب أن يرجع false. إذا كان يعرف القرار الذي يجب اتخاذه، فيجب عليه تعيين النتيجة وفقًا لذلك وأن يرجع 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;

    }
}

تجميع جميع العناصر

مع كون ICommunicationClient(C#) / CommunicationClient(Java) وICommunicationClientFactory(C#) / CommunicationClientFactory(Java) وIExceptionHandler(C#) / ExceptionHandler(Java) مبنية حول بروتوكول اتصال، يقوم ServicePartitionClient(C#) / FabricServicePartitionClient(Java) بجمعها معًا ويوفر حلقة معالجة الأخطاء وحل عنوان قسم الخدمة حول هذه المكونات.

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.
       */
   });

الخطوات التالية