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

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

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

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

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

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

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

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

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

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пример

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

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

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