نمط اختيار المسؤول الرئيسي

Azure Blob Storage

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

السياق والمشكلة

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

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

على سبيل المثال:

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

جميع مثيلات المهام هي أقران، لذلك لا يوجد مسؤول رئيسي طبيعي يمكنه العمل كمنسق أو مجمع.

حل

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

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

هناك استراتيجيات متعددة لانتخاب قائد بين مجموعة من المهام في بيئة موزعة، بما في ذلك:

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

المسائل والاعتبارات

راع النقاط التالية عند تحديد كيفية تنفيذ هذا النمط:

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

موعد استخدام النمط

استخدم هذا النمط عندما تحتاج المهام في تطبيق موزع، مثل حل مستضاف على السحابة، إلى تنسيق دقيق ولا يوجد مسؤول رئيسي طبيعي.

قد لا يكون هذا النمط مفيداً إذا:

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

تصميم حمل العمل

يجب على المهندس المعماري تقييم كيفية استخدام نمط الانتخابات القيادية في تصميم حمل العمل الخاص بهم لمعالجة الأهداف والمبادئ التي تغطيها ركائز Azure Well-Architected Framework. على سبيل المثال:

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

- RE:05 التكرار
- RE:07 الشفاء الذاتي

كما هو الحال مع أي قرار تصميم، ضع في اعتبارك أي مفاضلات ضد أهداف الركائز الأخرى التي يمكن إدخالها مع هذا النمط.

مثال

يوضح نموذج "Leader Election" على GitHub كيفية استخدام عقد إيجار على كائن ثنائي كبير الحجم ل Azure Storage لتوفير آلية لتنفيذ حركة مشتركة موزعة. يمكن استخدام هذا الطفرة لانتخاب قائد بين مجموعة من مثيلات العامل المتوفرة. يتم انتخاب المثيل الأول للحصول على عقد الإيجار الزعيم ويبقى الزعيم حتى تحرر عقد الإيجار أو لا تتمكن من تجديد عقد الإيجار. يمكن أن تستمر مثيلات العامل الأخرى في مراقبة تأجير الكائن الثنائي كبير الحجم في حالة عدم توفر القائد.

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

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

BlobDistributedMutex تحتوي الفئة في المثال C# أدناه على RunTaskWhenMutexAcquired الطريقة التي تمكن مثيل العامل من محاولة الحصول على عقد تأجير عبر كائن ثنائي كبير الحجم محدد. يتم تمرير تفاصيل الكائن الثنائي كبير الحجم (الاسم والحاوية وحساب التخزين) إلى الدالة الإنشائية في عنصر BlobSettings عند إنشاء العنصر BlobDistributedMutex (هذا العنصر هو بنية بسيطة مضمنة في عينة التعليمات البرمجية). تقبل الدالة Task الإنشائية أيضا أن يشير إلى التعليمات البرمجية التي يجب تشغيل مثيل العامل إذا حصل بنجاح على التأجير عبر الكائن الثنائي كبير الحجم ويتم اختيار القائد. لاحظ أن التعليمات البرمجية التي تتعامل مع التفاصيل منخفضة المستوى للحصول على عقد الإيجار يتم تنفيذها في فئة مساعد منفصلة تسمى BlobLeaseManager.

public class BlobDistributedMutex
{
  ...
  private readonly BlobSettings blobSettings;
  private readonly Func<CancellationToken, Task> taskToRunWhenLeaseAcquired;
  ...

  public BlobDistributedMutex(BlobSettings blobSettings,
           Func<CancellationToken, Task> taskToRunWhenLeaseAcquired, ... )
  {
    this.blobSettings = blobSettings;
    this.taskToRunWhenLeaseAcquired = taskToRunWhenLeaseAcquired;
    ...
  }

  public async Task RunTaskWhenMutexAcquired(CancellationToken token)
  {
    var leaseManager = new BlobLeaseManager(blobSettings);
    await this.RunTaskWhenBlobLeaseAcquired(leaseManager, token);
  }
  ...

يستدعي الأسلوب RunTaskWhenMutexAcquired في عينة التعليمات البرمجية أعلاه الأسلوب RunTaskWhenBlobLeaseAcquired الموضح في نموذج التعليمات البرمجية التالي للحصول فعلياً على عقد الإيجار. يتم تشغيل الأسلوب RunTaskWhenBlobLeaseAcquired بشكل غير متزامن. إذا تم الحصول على عقد الإيجار بنجاح، يتم انتخاب مثيل العامل القائد. الغرض من taskToRunWhenLeaseAcquired المفوض هو تنفيذ العمل الذي ينسق مثيلات العامل الأخرى. إذا لم يتم الحصول على عقد الإيجار، فقد تم انتخاب مثيل عامل آخر كقائد ويبقى مثيل العامل الحالي تابعا. لاحظ أن الأسلوب TryAcquireLeaseOrWait هو أسلوب مساعد يستخدم العنصر BlobLeaseManager للحصول على عقد الإيجار.

  private async Task RunTaskWhenBlobLeaseAcquired(
    BlobLeaseManager leaseManager, CancellationToken token)
  {
    while (!token.IsCancellationRequested)
    {
      // Try to acquire the blob lease.
      // Otherwise wait for a short time before trying again.
      string? leaseId = await this.TryAcquireLeaseOrWait(leaseManager, token);

      if (!string.IsNullOrEmpty(leaseId))
      {
        // Create a new linked cancellation token source so that if either the
        // original token is canceled or the lease can't be renewed, the
        // leader task can be canceled.
        using (var leaseCts =
          CancellationTokenSource.CreateLinkedTokenSource(new[] { token }))
        {
          // Run the leader task.
          var leaderTask = this.taskToRunWhenLeaseAcquired.Invoke(leaseCts.Token);
          ...
        }
      }
    }
    ...
  }

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

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

  private async Task RunTaskWhenBlobLeaseAcquired(
    BlobLeaseManager leaseManager, CancellationToken token)
  {
    while (...)
    {
      ...
      if (...)
      {
        ...
        using (var leaseCts = ...)
        {
          ...
          // Keep renewing the lease in regular intervals.
          // If the lease can't be renewed, then the task completes.
          var renewLeaseTask =
            this.KeepRenewingLease(leaseManager, leaseId, leaseCts.Token);

          // When any task completes (either the leader task itself or when it
          // couldn't renew the lease) then cancel the other task.
          await CancelAllWhenAnyCompletes(leaderTask, renewLeaseTask, leaseCts);
        }
      }
    }
  }
  ...
}

KeepRenewingLeaseالأسلوب هو أسلوب مساعد آخر يستخدم العنصر BlobLeaseManager لتجديد عقد الإيجار. يلغي الأسلوب CancelAllWhenAnyCompletes المهام المحددة كأول معلمتين. يوضح الرسم التخطيطي التالي استخدام الفئة BlobDistributedMutex لاختيار مسؤول رئيسي وتشغيل مهمة تنسق العمليات.

يوضح الشكل 1 وظائف فئة BlobDistributedMutex

يوضح مثال التعليمات البرمجية BlobDistributedMutex التالي كيفية استخدام الفئة داخل مثيل عامل. تحصل هذه التعليمة البرمجية على عقد تأجير عبر كائن ثنائي كبير الحجم مسمى MyLeaderCoordinatorTask في حاوية عقد الإيجار Azure Blob Storage، ويحدد أن التعليمات البرمجية المحددة في MyLeaderCoordinatorTask الأسلوب يجب أن تعمل إذا تم اختيار مثيل العامل القائد.

// Create a BlobSettings object with the connection string or managed identity and the name of the blob to use for the lease
BlobSettings blobSettings = new BlobSettings(storageConnStr, "leases", "MyLeaderCoordinatorTask");

// Create a new BlobDistributedMutex object with the BlobSettings object and a task to run when the lease is acquired
var distributedMutex = new BlobDistributedMutex(
    blobSettings, MyLeaderCoordinatorTask);

// Wait for completion of the DistributedMutex and the UI task before exiting
await distributedMutex.RunTaskWhenMutexAcquired(cancellationToken);

...

// Method that runs if the worker instance is elected the leader
private static async Task MyLeaderCoordinatorTask(CancellationToken token)
{
  ...
}

لاحظ النقاط التالية حول عينة الحل:

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

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

قد تكون الإرشادات التالية مناسبة أيضاً عند تنفيذ هذا النمط: