مشاركة عبر


خدمة الاتصال عن بُعد في C# مع Reliable Services

بالنسبة للخدمات غير المرتبطة ببروتوكول اتصال أو مكدس ذاكرة مؤقتة معين مثل واجهة برمجة تطبيقات الويب أو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.

على سبيل المثال، تعرض الخدمة عديمة الحالة التالية أسلوباً واحداً للحصول على "مرحباً بالعالم" عبر استدعاء إجراء عن بُعد.

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.

استدعاء أساليب الخدمة عن بُعد

إشعار

إذا كنت تستخدم أكثر من قسم واحد، يجب توفير ServicePartitionKey المناسب لـ ServiceProxy.Create(). هذا غير مطلوب لسيناريو قسم واحد.

أساليب استدعاء خدمة باستخدام مكدس الاتصال عن بُعد باستخدام وكيل محلي للخدمة من خلال الفئة 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 هو مصنع يُنشئ مثيلات وكيل لواجهات الاتصال عن بُعد المختلفة. إذا كنت تستخدم واجهة برمجة التطبيقات ServiceProxyFactory.CreateServiceProxy لإنشاء وكيل، فسيقوم إطار العمل بإنشاء وكيل خدمة قاعدة بيانات أحادية. من المفيد إنشاء واحد يدوياً عندما تحتاج إلى منع خصائص IServiceRemotingClientFactory. إنشاء المصنع هو عملية مُكلفة. يحتفظ مصنع وكيل الخدمة بذاكرة تخزين مؤقت داخلية لعميل الاتصال. أفضل ممارسة هي ذاكرة تخزين مؤقت مصنع وكيل الخدمة لأطول فترة ممكنة.

معالجة استثناءات الاتصال عن بُعد

تُرسل جميع الاستثناءات البعيدة التي تُطرح بواسطة واجهة برمجة تطبيقات الخدمة مرة أخرى إلى العميل كـ AggregateException. يجب أن تكون الاستثناءات البعيدة قادرة على التسلسل بواسطة DataContract. إذا لم تكن كذلك، فإن واجهة برمجة تطبيقات الوكيل تطرح ServiceException مع خطأ إنشاء التسلسل فيها.

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

توفير معلمات إعادة المحاولة الافتراضية بواسطة OperationRetrySettings.

يمكن للمستخدم تكوين هذه القيم عن طريق تمرير عنصر OperationRetrySettings إلى الدالة الإنشائية ServiceProxyFactory.

استخدام مكدس الاتصال عن بُعد V2

اعتباراً من الإصدار 2.8 من حزمة الاتصال عن بُعد NuGet، لديك خيار استخدام مكدس الاتصال عن بُعد V2. يعمل مكدس الاتصال عن بُعد V2 بشكل أفضل. كما يوفر ميزات مثل إنشاء تسلسل مخصص والمزيد من واجهات برمجة التطبيقات ذات قابلية للتوصيل. تستمر التعليمة البرمجية للقالب في استخدام مكدس الاتصال عن بُعد V1. لا يتوافق مكدس الاتصال عن بُعد V2 مع V1 (مكدس الاتصال عن بعُد السابق). اتبع الإرشادات الواردة في المقالة الترقية من V1 إلى V2 لتجنب التأثيرات على توفر الخدمة.

تتوفر النُهج التالية لتمكين مكدس V2.

استخدام سمة تجميع لاستخدام مكدس V2

تُغير هذه الخطوات التعليمات البرمجية للقالب لاستخدام مكدس V2 باستخدام سمة تجميع.

  1. تغيير مورد نقطة النهاية من "ServiceEndpoint" إلى "ServiceEndpointV2" في بيان الخدمة.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. استخدم أسلوب التوسيع Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners لإنشاء مستمعين اتصال عن بُعد (متساوين لـV1 وV2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. ضع علامة على التجميع الذي يحتوي على واجهات الاتصال عن بُعد باستخدام سمة FabricTransportServiceRemotingProvider.

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

لا يلزم إجراء تغييرات التعليمات البرمجية في مشروع العميل. أنشئ تجميع العميل مع تجميع الواجهة للتأكد من استخدام سمة التجميع الموضحة مسبقاً.

استخدام فئات V2 صريحة لاستخدام مكدس V2

كبديل لاستخدام سمة التجميع، يمكن أيضاً تمكين مكدس V2 باستخدام فئات V2 الصريحة.

تُغير هذه الخطوات التعليمات البرمجية للقالب لاستخدام مكدس V2 باستخدام فئات V2 صريحة.

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

الترقية من V1 عن بُعد إلى V2 عن بُعد

للترقية من V1 إلى V2، يلزم إجراء ترقيات من خطوتين. اتبع الخطوات بهذا التسلسل.

  1. ترقية خدمة V1 إلى خدمة V2 باستخدام هذه السمة. يتأكد هذا التغيير من أن الخدمة تنصت على مستمع V1 وV2.

    أ. إضافة مورد نقطة نهاية باسم "ServiceEndpointV2" في بيان الخدمة.

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

    ب. استخدم أسلوب التوسيع التالي لإنشاء مستمع اتصال عن بُعد.

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

    جـ. أضف سمة تجميع على واجهات الاتصال عن بُعد لاستخدام مستمع V1 وV2 وعميل V2.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
    
    
  2. ترقية عميل V1 إلى عميل V2 باستخدام سمة عميل V2. تتأكد هذه الخطوة من أن العميل يستخدم مكدس V2. لا يلزم إجراء أي تغيير في مشروع / خدمة العميل. يكفي إنشاء مشاريع العميل مع تجميع واجهة محدثة.

  3. هذه الخطوة اختيارية. استخدم سمة مستمع V2 ثم قم بترقية خدمة V2. تتأكد هذه الخطوة من أن الخدمة تنصت فقط إلى مستمع V2.

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

استخدام مكدس الاتصال عن بُعد V2 (متوافق مع الواجهة)

واجهة V2 (المتوافقة مع الواجهة) مكدس ذاكرة مؤقتة باسم V2_1 وهي تاريخ الإصدار. يحتوي على جميع ميزات مكدس ذاكرة مؤقتة للاتصال عن بُعد V2. مكدس الواجهة الخاص به متوافق مع مكدس الاتصال عن بُعد V1، لكنه غير متوافق مع الإصدارات السابقة مع V2 وV1. للترقية من V1 إلى V2_1 دون التأثير على توفر الخدمة، اتبع الخطوات الواردة في مقالة الترقية من V1 إلى V2 (متوافقة مع الواجهة).

استخدام سمة تجميع لاستخدام مكدس الاتصال عن بُعد V2 (متوافقة مع الواجهة)

اتبع هذه الخطوات للتغيير إلى مكدس 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)]
    
    

لا توجد تغييرات مطلوبة في مشروع العميل. أنشئ تجميع العميل مع تجميع الواجهة للتأكد من استخدام سمة التجميع المُستخدمة مسبقاً.

استخدام فئات الاتصال عن بُعد الصريحة لإنشاء مصنع مستمع/عميل لإصدار V2 (متوافقة مع الواجهة)

اتبع الخطوات التالية:

  1. إضافة مورد نقطة نهاية باسم "ServiceEndpointV2_1" في بيان الخدمة.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2_1" />  
     </Endpoints>
    </Resources>
    
  2. استخدم مستمع الاتصال عن بُعد V2. اسم مورد نقطة نهاية الخدمة الافتراضي المُستخدم هو "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. استخدم مصنع عميل V2.

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

الترقية من الاتصال عن بُعد V1 إلى الاتصال عن بُعد V2(متوافق مع الواجهة)

للترقية من V1 إلى V2 (متوافق مع الواجهة، والمعروف باسم V2_1)، يلزم إجراء ترقيات من خطوتين. اتبع الخطوات بهذا التسلسل.

إشعار

عند الترقية من V1 إلى V2، تأكد من تحديث مساحة الاسم Remoting لاستخدام V2. مثال: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client

  1. ترقية خدمة V1 إلى خدمة V2_1 باستخدام السمة التالية. يتأكد هذا التغيير من أن الخدمة تنصت إلى V1 والمستمع V2_1.

    أ. إضافة مورد نقطة نهاية باسم "ServiceEndpointV2_1" في بيان الخدمة.

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

    ب. استخدم أسلوب التوسيع التالي لإنشاء مستمع اتصال عن بُعد.

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

    جـ. أضف سمة تجميع على واجهات الاتصال عن بُعد لاستخدام V1 ومستمع V2_1 وعميل V2_1.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
    
    
  2. ترقية عميل V1 إلى عميل V2_1 باستخدام سمة عميل V2_1. تتأكد هذه الخطوة من أن العميل يستخدم مكدس V2_1. لا يلزم إجراء أي تغيير في مشروع / خدمة العميل. يكفي إنشاء مشاريع العميل مع تجميع واجهة محدثة.

  3. هذه الخطوة اختيارية. إزالة إصدار المستمع V1 من السمة ثم ترقية خدمة V2. تتأكد هذه الخطوة من أن الخدمة تنصت فقط إلى مستمع V2.

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

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