리더 선택 패턴

Azure Blob Storage

인스턴스 중 하나를 다른 인스턴스를 관리하는 리더로 선택하여 분산된 애플리케이션에서 공동 작업 인스턴스 컬렉션이 수행하는 작업을 조정합니다. 이렇게 하면 인스턴스가 서로 충돌하거나, 공유 리소스에 대한 경합을 일으키거나, 실수로 다른 인스턴스가 수행 중인 작업을 방해하지 않도록 방지할 수 있습니다.

컨텍스트 및 문제점

일반적인 클라우드 애플리케이션에는 조정된 방식으로 작동하는 많은 작업이 있습니다. 이러한 작업은 모두 동일한 코드를 실행하고 동일한 리소스에 대한 액세스가 필요한 인스턴스이거나 복잡한 계산의 개별 부분을 수행하기 위해 병렬로 함께 작업할 수 있습니다.

태스크 인스턴스는 대부분의 시간 동안 개별적으로 실행될 수 있지만 각 인스턴스의 작업을 조정하여 충돌하거나, 공유 리소스에 대한 경합을 일으키거나, 실수로 다른 태스크 인스턴스가 수행 중인 작업을 방해하지 않도록 해야 할 수도 있습니다.

예시:

  • 수평적 크기 조정을 구현하는 클라우드 기반 시스템에서는 동일한 태스크의 여러 인스턴스가 동시에 실행되고 각 인스턴스가 다른 사용자에게 서비스를 제공할 수 있습니다. 이러한 인스턴스가 공유 리소스에 쓰는 경우 각 인스턴스가 다른 인스턴스의 변경 내용을 덮어쓰지 않도록 작업을 조정해야 합니다.
  • 태스크가 복잡한 계산의 개별 요소를 병렬로 수행하는 경우 모두 완료되면 결과를 집계해야 합니다.

작업 인스턴스는 모두 피어이므로 코디네이터 또는 집계 역할을 할 수 있는 자연 리더가 없습니다.

솔루션

단일 작업 인스턴스가 리더 역할을 하도록 선택되어야 하며, 이 인스턴스는 다른 하위 작업 인스턴스의 작업을 조정해야 합니다. 모든 태스크 인스턴스가 동일한 코드를 실행하는 경우 각각 리더 역할을 할 수 있습니다. 따라서 두 개 이상의 인스턴스가 동시에 리더 직책을 맡는 것을 방지하기 위해 선거 프로세스를 신중하게 관리해야 합니다.

시스템은 리더를 선택하기 위한 강력한 메커니즘을 제공해야 합니다. 이 메서드는 네트워크 중단 또는 프로세스 오류와 같은 이벤트에 대처해야 합니다. 많은 솔루션에서 하위 태스크 인스턴스는 일부 유형의 하트비트 메서드 또는 폴링을 통해 리더를 모니터합니다. 지정된 리더가 예기치 않게 종료되거나 네트워크 오류로 인해 하위 작업 인스턴스에서 리더를 사용할 수 없는 경우 새 리더를 선출해야 합니다.

분산 환경에서 일련의 작업 중 리더를 선출하기 위한 몇 가지 전략은 다음과 같습니다.

  • 순위가 가장 낮은 인스턴스 또는 프로세스 ID를 가진 태스크 인스턴스 선택.
  • 공유 분산 뮤텍스를 확보하기 위해 경합. 뮤텍스를 획득하는 첫 번째 작업 인스턴스는 리더입니다. 그러나 리더가 종료되거나 시스템의 나머지 부분에서 연결이 끊길 경우 뮤텍스가 해제되어 다른 태스크 인스턴스가 리더가 될 수 있도록 해야 합니다.
  • 깡패 알고리즘 또는 링 알고리즘과 같은 일반적인 리더 선거 알고리즘 중 하나를 구현합니다. 이러한 알고리즘은 각 선택 후보에 고유 ID가 있으며 다른 후보와 안정적으로 통신할 수 있다고 가정합니다.

문제 및 고려 사항

이 패턴을 구현할 방법을 결정할 때 다음 사항을 고려하세요.

  • 리더를 선출하는 프로세스는 일시적이고 지속적인 실패에 탄력적이어야 합니다.
  • 리더가 실패했거나 사용할 수 없게 된 경우(예: 통신 오류로 인해) 감지할 수 있어야 합니다. 검색이 얼마나 빨리 필요한지는 시스템에 따라 달라집니다. 일부 시스템은 리더 없이 짧은 시간 동안 작동할 수 있으며 그 동안 일시적인 오류가 수정될 수 있습니다. 다른 경우에는 리더 실패를 즉시 감지하고 새 선거를 트리거해야 할 수도 있습니다.
  • 수평 자동 크기 조정을 구현하는 시스템에서 시스템이 축소하고 일부 컴퓨팅 리소스를 종료하는 경우 리더를 종료할 수 있습니다.
  • 공유 분산 뮤텍스를 사용하면 뮤텍스를 제공하는 외부 서비스에 대한 종속성이 도입됩니다. 서비스는 단일 실패 지점을 구성합니다. 어떤 이유로든 서비스를 사용할 수 없게 되면 시스템에서 리더를 선택할 수 없습니다.
  • 단일 전용 프로세스를 리더로 사용하는 것은 간단한 방법입니다. 그러나 프로세스가 실패하면 다시 시작하는 동안 상당한 지연이 발생할 수 있습니다. 리더가 작업을 조정하기를 기다리는 경우 결과 대기 시간은 다른 프로세스의 성능 및 응답 시간에 영향을 줄 수 있습니다.
  • 리더 선거 알고리즘 중 하나를 수동으로 구현하면 코드를 튜닝하고 최적화하는 데 가장 큰 유연성을 제공합니다.
  • 리더가 시스템의 병목 상태가 되지 않도록 합니다. 리더의 목적은 하위 태스크의 작업을 조정하는 것이며 이 작업 자체에 반드시 참여해야 하는 것은 아닙니다. 단, 태스크가 리더로 선택되지 않은 경우에는 참여할 수 있어야 합니다.

이 패턴을 사용해야 하는 경우

클라우드에 호스트된 솔루션과 같은 분산 애플리케이션의 태스크에 신중한 조정이 필요하고 자연 리더가 없는 경우 이 패턴을 사용합니다.

이 패턴은 다음과 같은 경우 유용하지 않을 수 있습니다.

  • 항상 리더 역할을 할 수 있는 자연 리더 또는 전용 프로세스가 있는 경우. 예를 들어 작업 인스턴스를 조정하는 싱글톤 프로세스를 구현할 수 있습니다. 이 프로세스가 실패하거나 비정상 상태가 되면 시스템이 종료하고 다시 시작할 수 있습니다.
  • 보다 간단한 방법을 사용하여 작업 간의 조정을 수행할 수 있습니다. 예를 들어 단순히 여러 태스크 인스턴스에 공유 리소스에 대한 조정된 액세스가 필요한 경우 더 나은 솔루션은 낙관적 또는 비관적 잠금을 사용하여 액세스를 제어하는 것입니다.
  • 타사 솔루션이 더 적합한 경우. 예를 들어 Microsoft Azure HDInsight 서비스(Apache Hadoop 기반)는 Apache Zookeeper에서 제공하는 서비스를 사용하여 지도를 조정하고 데이터를 수집하고 요약하는 작업을 줄입니다.

워크로드 디자인

설계자는 워크로드 디자인에서 리더 선거 패턴을 사용하여 Azure Well-Architected Framework 핵심 요소에서 다루는 목표와 원칙을 해결하는 방법을 평가해야 합니다. 예시:

핵심 요소 이 패턴이 핵심 목표를 지원하는 방법
안정성 디자인 결정은 워크로드가 오작동에 대한 복원력을 갖도록 하고 오류가 발생한 후 완전히 작동하는 상태로 복구 되도록 하는 데 도움이 됩니다. 이 패턴은 안정적으로 작업을 리디렉션하여 노드 오작동의 영향을 완화합니다. 또한 리더가 오작동할 때 합의 알고리즘을 통해 장애 조치(failover)를 구현합니다.

- RE:05 중복성
- RE:07 자가 치유

디자인 결정과 마찬가지로 이 패턴으로 도입될 수 있는 다른 핵심 요소의 목표에 대한 절충을 고려합니다.

예시

GitHub의 리더 선거 샘플)에서는 Azure Storage Blob에서 임대를 사용하여 공유 분산 뮤텍스를 구현하는 메커니즘을 제공하는 방법을 보여 줍니다. 이 뮤텍스는 사용 가능한 작업자 인스턴스 그룹 중에서 리더를 선출하는 데 사용할 수 있습니다. 임대를 획득하는 첫 번째 인스턴스는 리더로 선출되고 임대를 해제하거나 임대를 갱신할 수 없을 때까지 리더를 다시 기본. 다른 작업자 인스턴스는 리더를 더 이상 사용할 수 없는 경우 Blob 임대를 계속 모니터링할 수 있습니다.

Blob 임대는 Blob에 대한 배타적 쓰기 잠금입니다. 단일 Blob은 언제든지 한 임대의 주체만 될 수 있습니다. 작업자 인스턴스는 지정된 Blob에 대한 임대를 요청할 수 있으며, 다른 작업자 인스턴스가 동일한 Blob에 대한 임대를 보유하지 않는 경우 임대가 부여됩니다. 그렇지 않으면 요청이 예외를 throw합니다.

오류가 발생한 리더 인스턴스가 임대를 무기한 유지하지 않도록 하려면 임대의 수명을 지정합니다. 이 기간이 만료되면 임대를 사용할 수 있게 됩니다. 그러나 인스턴스가 임대를 보유하는 동안 임대 갱신을 요청할 수 있으며 추가 기간 동안 임대가 부여됩니다. 임대를 유지하려는 경우 리더 인스턴스는 이 프로세스를 지속적으로 반복할 수 있습니다. Blob 임대 방법에 대한 자세한 내용은 Blob 임대(REST API)를 참조하세요.

아래 C# 예제의 클래스에는 BlobDistributedMutex 작업자 인스턴스가 지정된 Blob에 대한 임대를 획득할 수 있도록 하는 메서드가 포함되어 RunTaskWhenMutexAcquired 있습니다. Blob(이름, 컨테이너 및 스토리지 계정)의 세부 정보는 개체를 만들 때 개체의 BlobDistributedMutex 생성자에 BlobSettings 전달됩니다(이 개체는 샘플 코드에 포함된 간단한 구조체임). 또한 생성자는 Blob을 통해 임대를 성공적으로 획득하고 리더로 선출될 경우 작업자 인스턴스가 실행해야 하는 코드를 참조하는 코드를 수락 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 사용하는 방법을 보여 줍니다. 이 코드는 임대의 컨테이너 Azure Blob Storage에 명명된 MyLeaderCoordinatorTask Blob을 통해 임대를 획득하고, 작업자 인스턴스가 리더로 선출될 경우 메서드에 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에 데이터를 저장하려고 하면 작업자 인스턴스가 리더이고 Blob 임대를 보유하지 않는 한 이 데이터에 액세스할 수 없습니다.

다음 단계

이 패턴을 구현할 때 다음 지침도 관련이 있을 수 있습니다.