Шаблон выборов лидера

хранилище BLOB-объектов Azure

Координируйте действия, выполняемые группой взаимосвязанных экземпляров в распределенном приложении, путем выбора одного экземпляра в качестве лидера, который берет на себя ответственность за управление остальными. Это может помочь убедиться, что экземпляры не конфликтуют друг с другом, не вызывают спор за общие ресурсы или ненамеренно вмешиваются в работу, выполняемую другими экземплярами.

Контекст и проблема

Обычное облачное приложение имеет множество задач, выполняемых согласованно. Эти задачи могут быть экземплярами, выполняющими один и тот же код и требующими доступа к тем же ресурсам, или они могут работать параллельно для выполнения отдельных частей сложного вычисления.

Экземпляры задач могут выполняться отдельно в течение большей части времени, но может также потребоваться координация действий каждого экземпляра, чтобы убедиться, что они не конфликтуют, не вызывают конфликты между общими ресурсами или случайно не вмешиваются в работу других экземпляров задач.

Рассмотрим пример.

  • В облачной системе, реализующей горизонтальное масштабирование, несколько экземпляров одной задачи могут выполняться одновременно с каждым экземпляром, обслуживающим другого пользователя. Если эти экземпляры записываются в общий ресурс, необходимо координировать свои действия, чтобы предотвратить перезапись изменений, внесенных другими экземплярами.
  • Если задачи выполняют отдельные элементы сложного вычисления параллельно, результаты должны быть агрегированы при их завершении.

Все экземпляры задач являются одноранговыми узлами, поэтому нет явного лидера, чтобы выступать координатором или агрегатором.

Решение

Один экземпляр задачи должен быть выбран в качестве лидера, и этот экземпляр должен координировать действия других подчиненных экземпляров задач. Если все экземпляры задач выполняют один и тот же код, каждый из них может выступать в качестве лидера. Таким образом, процесс выборов должен быть тщательно управляем, чтобы предотвратить одновременное принятие двух или более экземпляров на должность лидера.

Система должна обеспечить надежный механизм выбора лидера. Этот метод должен справиться с такими событиями, как сбои сети или сбои процессов. Во многих решениях подчиненные экземпляры задач отслеживают лидера с помощью определенного типа метода пульса или опроса. Если назначенный лидер неожиданно прекращает работу, или сбой в сети делает лидера недоступным, и поэтому экземплярам подчиненных задач необходимо выбрать нового лидера.

Существует несколько стратегий выбора лидера среди набора задач в распределенной среде, в том числе:

  • Гонки для получения общего распределенного мьютекса. Первый экземпляр задачи, который получает мьютекс, становится ведущим. Однако система должна убедиться, что, если лидер завершает работу или теряет связь с остальной частью системы, мьютекс освобождается, что позволит другому экземпляру задачи стать лидером. Эта стратегия показана в следующем примере.
  • ** Реализация одного из распространенных алгоритмов выборов лидера, таких как Bully Algorithm, алгоритм консенсуса Raft или алгоритм Чанга и Робертса. Эти алгоритмы предполагают, что каждый кандидат на выборах имеет уникальный идентификатор, и что он может взаимодействовать с другими кандидатами надежно.

Проблемы и рекомендации

При принятии решения о реализации этого шаблона следует учитывать следующие моменты:

  • Процесс выбора лидера должен быть устойчивым к временным и постоянным сбоям.
  • Необходимо иметь возможность определить, когда лидер потерпел сбой или иным образом стал недоступен (например, из-за сбоя связи). Насколько быстро обнаружение необходимо, зависит от системы. Некоторые системы могут работать в течение короткого времени без лидера, в течение которого может быть исправлен временный сбой. В других случаях может потребоваться обнаружить ошибку лидера немедленно и запустить новые выборы.
  • В системе, реализующей горизонтальное автомасштабирование, лидер может быть завершен, если система масштабируется назад и отключает некоторые вычислительные ресурсы.
  • Использование общего распределенного мьютекса вводит зависимость от внешней службы, которая предоставляет мьютекс. Служба представляет собой одну точку сбоя. Если он становится недоступным по какой-либо причине, система не сможет выбрать лидера.
  • Использование одного выделенного процесса в качестве ведущего компонента — понятный подход. Тем не менее, если процесс завершается сбоем, во время его перезапуска может возникнуть значительный задержка. Результирующая задержка может повлиять на производительность и время отклика других процессов, если они ожидают, пока лидер будет координировать операцию.
  • Реализация одного из алгоритмов выборов лидера вручную обеспечивает максимальную гибкость для настройки и оптимизации кода.
  • Избегайте превращения лидера в узкое место в системе. Цель лидера заключается в координации работы подчинённых задач, и ему не обязательно участвовать в этой работе, хотя оно должно уметь это делать, если задача не выбрана в роли лидера.

Когда следует использовать этот шаблон

Используйте этот шаблон, когда задачи в распределенном приложении, например облачное решение, нуждаются в тщательной координации и не существует естественного лидера.

Этот шаблон может оказаться не полезным, если:

  • Существует естественный лидер или выделенный процесс, который всегда может выступать в качестве лидера. Например, можно реализовать однотонный процесс, который координирует экземпляры задач. Если этот процесс завершается сбоем или становится неработоспособным, система может завершить работу и перезапустить ее.
  • Координация между задачами может быть достигнута с помощью более упрощенного метода. Например, если нескольким экземплярам задач просто нужен согласованный доступ к общему ресурсу, лучше использовать оптимистическую или пессимистичную блокировку для управления доступом.
  • Стороннее решение, например Apache Zookeeper , может быть более эффективным решением.

Проектирование рабочей нагрузки

Архитектор должен оценить, как шаблон выборов лидера можно использовать в проектировании рабочей нагрузки для решения целей и принципов, описанных в основных принципах Azure Well-Architected Framework. Рассмотрим пример.

Столб Как этот шаблон поддерживает цели основных компонентов
Решения по проектированию надежности помогают рабочей нагрузке стать устойчивой к сбоям и обеспечить восстановление до полнофункционального состояния после сбоя. Этот шаблон снижает влияние сбоев узлов, надежно перенаправляя работу. Она также реализует переключение на резервный ресурс с помощью алгоритмов консенсуса в случае сбоя лидера.

- RE:05 ИЗБЫТОЧНОСТЬ
- RE:07 Самовосстановление

Как и при любом решении по проектированию, рассмотрите любые компромиссы в отношении целей других аспектов, вызванные этим шаблоном.

Пример

Пример выбора лидера на GitHub показывает, как использовать аренду блоба хранилища Azure для предоставления механизма реализации общего распределенного мьютекса. Этот мьютекс можно использовать для выбора лидера среди множества доступных рабочих экземпляров. Первый экземпляр для приобретения аренды выбирается лидером и остается лидером, пока он не выпустит аренду или не сможет продлить аренду. Другие рабочие экземпляры могут продолжать отслеживать аренду blob-объектов в случае, если ведущий больше недоступен.

Аренда BLOB-объектов — это монопольная блокировка записи для большого двоичного объекта. Один большой двоичный объект может быть предметом только одной аренды в любой момент времени. Рабочий экземпляр может запрашивать аренду для указанного большого двоичного объекта, и он будет предоставлен в аренду, если другой рабочий экземпляр не содержит аренды над тем же большим двоичным объектом. В противном случае запрос вызовет исключение.

Чтобы избежать ситуации, когда неисправный экземпляр лидера сохраняет аренду на неопределенный срок, укажите срок действия аренды. После истечения срока действия аренда становится вновь доступной. Однако, пока экземпляр обладает арендой, он может запросить продление, и аренда будет продлена на дополнительный срок. Экземпляр лидера может постоянно повторять этот процесс, если он хочет сохранить аренду. Дополнительные сведения о том, как арендовать объект BLOB, см. \"Аренда объекта BLOB" (REST API).

Класс 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 является потенциальной единой точкой отказа. Если служба BLOB-объектов становится недоступной или недоступна, лидер не сможет продлить аренду, а другой рабочий экземпляр не сможет получить аренду. В этом случае экземпляр рабочей роли не сможет выступать в качестве лидера. Однако служба BLOB предназначена для обеспечения устойчивости, поэтому полный сбой службы BLOB считается крайне маловероятным.
  • Если задача, выполняемая лидером, застопорится, лидер может продолжить продление аренды, не позволяя любому другому рабочему экземпляру получить аренду и взять на себя должность лидера для координации задач. В реальном мире работоспособность лидера должна проверяться с частыми интервалами.
  • Процесс выборов недетерминирован. Вы не можете сделать никаких предположений о том, какой рабочий экземпляр получит аренду объекта BLOB и станет ведущим.
  • Большой двоичный объект, используемый в качестве целевого объекта аренды BLOB-объектов, не должен использоваться для других целей. Если рабочий экземпляр пытается хранить данные в этом blob-хранилище, эти данные не будут доступны, если рабочий экземпляр не является лидером и не держит аренду на этот BLOB.

Дальнейшие действия

При реализации этого шаблона также может быть важно следующее руководство.