Öncü Seçimi düzeni

Bir örneği, diğer örneklerin yönetilmesinin sorumluluğunu üstlenen bir öncü olarak seçerek, dağıtılmış bir uygulamadaki işbirliği yapan bir örnekler koleksiyonu tarafından gerçekleştirilen eylemleri koordine edin. Bu sayede örnekler arasında çakışmalar yaşanması, paylaşılan kaynaklar hakkında çekişmeler oluşması veya örneklerin yanlışlıkla birbirlerinin yaptıkları işleri engellemesi önlenmiş olur.

Bağlam ve sorun

Tipik bir bulut uygulamasında koordineli bir şekilde yürütülen birçok görev bulunur. Bu görevlerin tümü, aynı kodu çalıştıran ve aynı kaynaklara erişmesi gereken örnekler olabilir veya karmaşık bir hesaplamanın ayrı bölümlerini gerçekleştirmek için paralel olarak birlikte çalışıyor olabilir.

Görev örnekleri, çoğu zaman ayrı ayrı çalışabilir, ancak çakışma olmamasını, paylaşılan kaynaklar için çekişmeye neden olmamasını veya diğer görev örneklerinin gerçekleştirdiği işi yanlışlıkla kesintiye uğramamasını sağlamak için her bir örneğin eylemlerini koordine etmek de gerekebilir.

Örnek:

  • Yatay ölçeklendirme uygulayan bulut tabanlı bir sistemde, aynı anda aynı görevin birden fazla örneği çalışıyor ve her bir örnek farklı bir kullanıcıya hizmet veriyor olabilir. Bu örnekler paylaşılan bir kaynağa yazıyorsa bir örneğin diğer örneklerin yaptığı değişikliklerin üzerine yazmasını engellemek için tüm örneklerin eylemleri koordine edilmelidir.
  • Görevler karmaşık bir hesaplamanın ayrı ayrı öğelerini paralel bir şekilde gerçekleştiriyorsa tüm görevler tamamlandığında sonuçlarının bir araya getirilmesi gerekir.

Tüm görev örnekleri eş düzeyde olduğu için koordine edici veya birleştirici rolünü üstlenecek doğal bir öncü yoktur.

Çözüm

Tek bir görev örneği öncü rolü için seçilmeli ve bu örnek, diğer alt görev örneklerinin eylemlerini koordine etmelidir. Tüm görev örnekleri aynı kodu çalıştırıyorsa her birinin öncü rolünü üstlenmesi mümkündür. Bu nedenle, seçim süreci iki veya daha fazla örneğin aynı anda öncü rolünü üstlenmesini önleyecek şekilde dikkatle yönetilmelidir.

Sistem, öncünün seçilmesine yönelik güçlü bir mekanizma sağlamalıdır. Bu yöntem, ağ kesintileri veya süreç başarısızlıkları gibi olaylarla başa çıkabilmelidir. Birçok çözümde alt görev örnekleri, öncüyü bir tür sinyal yöntemiyle ya da yoklama aracılığıyla izler. Belirlenmiş öncü beklenmedik şekilde sonlandırılırsa veya bir ağ hatası nedeniyle alt görev örnekleri öncüye ulaşamazsa alt görev örneklerinin yeni bir öncü seçmesi gerekir.

Dağıtılmış bir ortamda bir dizi görev arasından bir öncü seçmek için aşağıdaki gibi stratejiler kullanılabilir:

  • En düşük dereceli örnek veya işlem kimliğine sahip görev örneğini seçme.
  • Paylaşılan, dağıtılmış bir mutex’i elde etmek için yarışma. Mutex’i elde eden ilk görev örneği öncü haline gelir. Bununla birlikte, öncü sonlandırılırsa veya sistemin geri kalanıyla bağlantısı kesilirse başka bir görev örneğinin öncü haline gelebilmesine olanak tanımak için sistem mutex’in serbest bırakılmasını sağlamalıdır.
  • Kabadayı Algoritması veya Halka Algoritması gibi yaygın öncü seçimi algoritmalarından birini uygulama. Bu algoritmalar, seçime katılan her adayın benzersiz bir kimliği olduğunu ve diğer adaylarla güvenilir bir şekilde iletişim kurabildiğini varsayar.

Sorunlar ve dikkat edilmesi gerekenler

Bu düzenin nasıl uygulanacağına karar verirken aşağıdaki noktaları göz önünde bulundurun:

  • Öncü seçme sürecinin geçici ve kalıcı hatalara dayanıklı olması gerekir.
  • Öncü başarısız olduğunda veya başka bir şekilde kullanılamaz hale geldiğinde (örneğin bir iletişim hatası nedeniyle) bunun algılanabilmesi gerekir. Bu algılamanın ne kadar hızlı olması gerektiği sisteme bağlıdır. Bazı sistemler bir öncü olmadan kısa bir süre çalışmayı sürdürebilir ve geçici bir hata varsa bu sırada giderilebilir. Diğer bazı durumlarda öncü hatasının hemen algılanması ve yeni bir seçimin tetiklenmesi gerekli olabilir.
  • Yatay otomatik ölçeklendirme uygulayan bir sistemde, sistemin ölçeği küçültülürse ve bilgi işlem kaynaklarının bazıları kapatılırsa öncü sonlandırılabilir.
  • Paylaşılan, dağıtılmış bir mutex kullanılması, bu mutex’i sağlayan dış hizmete bağımlık doğurur. Söz konusu hizmet, bir tek hata noktası haline gelir. Bu hizmetten herhangi bir nedenle yararlanılamazsa sistem bir öncü seçemez.
  • Öncü olarak tek bir adanmış sürecin kullanılması basit bir yaklaşımdır. Ancak bu süreç başarısız olursa sürecin yeniden başlatılması ciddi miktarda gecikmeye yol açabilir. Diğer süreçler öncünün bir işlemi koordine etmesini bekliyorsa, ortaya çıkan gecikme bu süreçlerin performansını ve yanıt sürelerini etkileyebilir.
  • Öncü seçimi algoritmalarından birinin el ile uygulanması, kodun ayarlanması ve iyileştirilmesine yönelik en yüksek esnekliği sağlar.

Bu düzenin kullanılacağı durumlar

Bu düzeni, dağıtılmış bir uygulamadaki (bulutta barındırılan bir çözüm gibi) görevlerin dikkatle koordine edilmesi gerektiğinde ve hiçbir doğal öncü olmadığında kullanın.

Öncüyü sistemde bir performans sorunu haline getirmekten kaçının. Lider amacı, alt görevlerin işini koordine etmekle birlikte bu işin kendisine katılması gerekmez, ancak görev öncü olarak seçili değilse bunu yapabilmelidir.

Bu düzen aşağıdaki durumlarda kullanışlı olmayabilir:

  • Doğal bir öncü ya da her zaman öncü olarak hareket edebilecek adanmış bir işlem var. Örneğin, görev örneklerini koordine eden tekil bir süreç uygulanması mümkün olabilir. Bu süreç başarısız olursa veya durumu bozulursa sistem süreci kapatıp yeniden başlatabilir.
  • Daha basit yapılı bir yöntem kullanılarak görevler arasında koordinasyon sağlanabiliyor. Örneğin, tek gereken şey birkaç görev örneğinin paylaşılan bir kaynağa koordine bir şekilde erişebilmesiyse erişimi denetlemek için iyimser veya kötümser kilitleme kullanılması daha iyi bir çözümdür.
  • Bir üçüncü taraf çözümü daha uygun olacak. Örneğin, Microsoft Azure HDInsight hizmeti (Apache Hadoop tabanlı) eşlemeleri koordine etmek ve veri toplayıp özetleyen görevlerin sayısını azaltmak için Apache Zookeeper tarafından sağlanan hizmetleri kullanır.

Örnek

LeaderElection çözümündeki DistributedMutex projesi (Bu düzeni gösteren bir örnek GitHub’dan edinilebilir.) bir Azure Depolama blobunun kiralanmasını kullanarak paylaşılan, dağıtılmış bir mutex’i uygulamaya yönelik bir mekanizmanın nasıl sağlanacağını gösterir. Bu mutex, bir Azure bulut hizmetindeki bir grup rol örneği arasından bir öncü seçmek üzere kullanılabilir. Kiranın sahibi olan ilk rol örneği öncü seçilir ve kirayı serbest bırakana veya kirayı yenilemesi mümkün olmayana kadar öncü olarak kalır. Diğer rol örnekleri, öncünün kullanılabilir olup olmadığını belirlemek için blob kiralamasını izlemeye devam edebilir.

Blob kiralama, bir bloba ilişkin özel bir yazma kilididir. Bir blobun herhangi bir anda yalnızca bir kiralayanı olabilir. Rol örneği belirli bir blob için kiralama isteğinde bulunabilir. Aynı blobu kiralayan başka bir rol örneği yoksa isteği yapan rol örneğine kiralama izni verilir. Aksi takdirde istek, bir özel durum oluşturur.

Hatalı bir rol örneğinin kirayı süresiz olarak devam ettirmesini önlemek için kiralamanın ömrünü belirtin. Bu süre dolduğunda blob kiralanabilir hale gelir. Ancak, kira bir rol örneğinin üzerindeyken bu rol örneği kiranın yenilenmesini isteyebilir. Böyle bir durumda kira bir süre daha bu örneğe verilir. Rol örneği, kiranın kendisinde kalmasını istiyorsa bu işlemi sürekli olarak yineleyebilir. Blob kiralama hakkında daha fazla bilgi için bkz. Lease Blob (Blob Kiralama) (REST API).

Aşağıdaki örnekte yer alan C# dilindeki BlobDistributedMutex sınıfı, bir rol örneğinin belirtilen bir blobun kirasını elde etmeyi denemesine olanak sağlayan RunTaskWhenMutexAcquired yöntemini içeriyor. Blobun bilgileri (ad, kapsayıcı ve depolama hesabı) BlobDistributedMutex nesnesi oluşturulduğunda (Bu nesne, örnek koda dahil edilmiş basit bir yapıdır.) bir BlobSettings nesnesi içinde oluşturucuya geçirilir. Oluşturucu ayrıca rol örneği blobu başarıyla kiralayıp öncü seçilirse çalıştırması gereken koda başvuran bir Task kabul eder. Kiralama işlemiyle ilgili alt düzey ayrıntıları işleyen kodun BlobLeaseManager adlı ayrı bir yardımcı sınıfta uygulandığını unutmayın.

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

Yukarıdaki kod örneğinde yer alan RunTaskWhenMutexAcquired yöntemi, kirayı almak için aşağıdaki kod örneğinde gösterilen RunTaskWhenBlobLeaseAcquired yöntemini çağırır. RunTaskWhenBlobLeaseAcquired yöntemi zaman uyumsuz olarak çalışır. Rol örneği kiralamayı başarılı bir şekilde gerçekleştirirse öncü seçilmiş olur. taskToRunWhenLeaseAcquired temsilcisinin amacı diğer rol örneklerini koordine eden çalışmaları yapmaktır. Rol örneği kiralamayı başarılı bir şekilde gerçekleştiremezse başka bir rol örneği öncü olarak seçilmiştir ve geçerli rol örneği, alt örnek olarak kalır. TryAcquireLeaseOrWait yönteminin kiralamayı gerçekleştirmek için BlobLeaseManager nesnesini kullanan bir yardımcı yöntem olduğunu unutmayın.

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

Öncü tarafından başlatılan görev de zaman uyumsuz olarak çalışır. Bu görev yürütülürken, aşağıdaki kod örneğinde gösterilen RunTaskWhenBlobLeaseAcquired yöntemi de düzenli aralıklarla kirayı yenilemeyi dener. Böylece rol örneği öncü olarak kalabilir. Başka bir rol örneğinin öncü seçilmesini önlemek için, örnek çözümde yenileme istekleri arasındaki gecikme süresi kira için belirtilen süreden kısadır. Yenileme herhangi bir nedenle başarısız olursa görev iptal edilir.

Kiranın yenilenmesi başarısız olursa veya görev iptal edilirse (büyük olasılıkla rol örneğinin kapatılması nedeniyle) kira serbest bırakılır. Bu durumda, bu veya başka bir rol örneği öncü olarak seçilebilir. Aşağıdaki kod parçası sürecin bu bölümünü göstermektedir.

  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 yöntemi, kirayı yenilemek için BlobLeaseManager nesnesini kullanan bir başka yardımcı yöntemdir. CancelAllWhenAnyCompletes yöntemi, ilk iki parametre olarak belirtilen görevleri iptal eder. Aşağıdaki diyagramda, BlobDistributedMutex sınıfı kullanılarak bir öncü seçilmesi ve işlemleri koordine eden bir görevin çalıştırılması gösterilmektedir.

Şekil 1 BlobDistributedMutex sınıfının işlevlerini göstermektedir

Aşağıdaki kod örneğinde, BlobDistributedMutex sınıfının çalışan rolünde nasıl kullanılacağı gösterilmektedir. Bu kod, geliştirme depolama alanındaki kiranın kapsayıcısında MyLeaderCoordinatorTask adlı bir blobu kiralar ve rol örneği öncü olarak seçilirse MyLeaderCoordinatorTask yönteminde tanımlanan kodun çalıştırılacağını belirtir.

var settings = new BlobSettings(CloudStorageAccount.DevelopmentStorageAccount,
  "leases", "MyLeaderCoordinatorTask");
var cts = new CancellationTokenSource();
var mutex = new BlobDistributedMutex(settings, MyLeaderCoordinatorTask);
mutex.RunTaskWhenMutexAcquired(this.cts.Token);
...

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

Örnek çözüm hakkında aşağıdaki noktalara dikkat edin:

  • Blob olası bir tek hata noktasıdır. Blob hizmeti kullanılamaz veya erişilemez duruma gelirse öncü kirayı yenileyemez ve başka hiçbir rol örneği de blobu kiralayamaz. Bu durumda, hiçbir rol örneği öncü rolünü üstlenemez. Ancak, blob hizmeti dayanıklı olacak şekilde tasarlanmıştır. Bu yüzden, blob hizmetinin tamamen başarısız olma olasılığı son derece düşüktür.
  • Öncü tarafından gerçekleştirilen görev durursa öncü kirayı yenilemeye devam edebilir. Bu durumda, diğer rol örneklerinin kiralama gerçekleştirmesi ve öncü rolünü üstlenerek görevleri koordine etmesi engellenmiş olur. Gerçek dünyada öncünün sistem durumu sık aralıklarla denetlenmelidir.
  • Seçim işlemi belirlenimci değildir. Hangi rol örneğinin blobu kiralayacağı ve öncü haline geleceği hakkında varsayımlar yapamazsınız.
  • Blob kiralamanın hedefi olarak kullanılan blob başka herhangi bir amaçla kullanılmamalıdır. Bir rol örneği bu blobda veri depolamaya kalkarsa ve söz konusu rol örneği öncü değilse yani blobun kirasını elinde bulundurmuyorsa bu verilere erişilemez.

Sonraki adımlar

Bu düzeni uygularken aşağıdaki yönergeler de yararlı olabilir: