Azure设计模式之领导者选举

领导选举模式


选择一个实例作为其他实例的负责人来协调分布式应用程序中的实例。有助于确保实例不会相互冲突,产生共享资源的争用,或者干扰到正在执行操作的实例。


问题背景
云应用程序中有许多任务以协调的方式运行着。这些任务可能执行着相同的代码并访问相同资源的实例,或作为复杂计算的一部分并行执行。任务实例可能会在很长一段时间内单独运行,但也可能会需要协调工作的介入,以确保它们不会发生冲突、导致共享资源争用或干扰到正在执行任务的实例。


例子
在云应用中,需要进行水平扩展,这样同一任务的多个实例可以同时运行,并且每个实例为不同的用户服务。如果这些实例写入共享资源,则必须对它们进行协调,以防止每个实例所做的更改被覆盖。如果任务正在并行地执行一项复杂的计算,则结果需要在全部完成时进行聚合。由于每个任务实例都是对等的,因此缺少一个可以充当协调员或聚合器的领导者。


解决方案
应选择某任务实例作为领导者,此实例负责对其他实例的工作进行协调。如果所有任务实例都运行相同的代码,则每个都可以充当领导者。因此,必须认真管理选举进程,以防止两个或两个以上的实例同时接管领导角色。


系统必须提供一种健壮机制来选择领导者。这种方案必须处理诸如网络中断或进程失败之类的事件。在许多解决方案中,从通过某种形式的心跳或轮询来监视领导者。如果领导者被意外终止,或者网络故障使得领导者无法访问任务实例, 则必须选择一个新的领导者。


在分布式环境中,在一组任务中选择一个领导者有几种策略,其中包括:
选择最低级别进程ID的任务实例。
自然竞争来获取共享的分布式互斥体。第一个获取互斥体的任务实例就是领导者。但是,系统必须要保证,如果领导者异常终止或断开与系统其余部分的连接,则自动释放互斥体以允许另一个任务实例成为领导者。
可以使用常见的领导者选举算法来实现,如Bully算法(http://www.cs.colostate.edu/~cs551/CourseNotes/Synchronization/BullyExample.html)或ring算法(http://www.cs.colostate.edu/~cs551/CourseNotes/Synchronization/RingElectExample.html)。这些算法都假定选举中的每个候选者都有一个唯一的ID,并可以可靠地与其他候选人进行通信。


问题和注意事项
在决定如何实现此模式时,请考虑以下几点:
选举领导人的过程应该有足够的弹性来适应暂时和持续的(服务)失败情形。
必须有机制来检测到领导者运行失败或已变得不可用(例如由于通信故障)。检测机制的频率因系统而异。某些系统可能在没有领导者的情况下能够运行很短的时间,在此期间可能需要修复故障即可;而在其他情况下,可能需要立即检测到领导失败,并触发新的选举措施。


在实现了自动水平伸缩的系统中,如果系统减少实例并关闭某些计算资源,则领导者可能被终止。如果在分布式环境中使用了共享互斥提体进制,会对提供了互斥体的外部服务产生依赖。这个服务成为了单点故障。如果由于某种原因而变得不可用,系统将无法选出一个领导者。


使用单一的专用进程作为领导者是一种直接的做法。但是,如果该进程失败,则在重新启动时可能会出现严重的延迟。如果等待领导者协调一个操作,产生的延迟可能会影响其他进程的性能和响应时间。可以考虑手动实现一种领导者选举算法,提供优化的空间。


何时使用此模式
当分布式应用程序中的任务(如云托管解决方案)中的实例需要仔细协调并且没有自然的领导者时,可以使用此模式。应避免使领导者成为系统的瓶颈。领导者目的是协调下级任务的工作,它不一定必须参与这项工作本身-尽管它能够这样做,那是在该实例不是领导者的情况下。


此模式在以下情况下可能不有用:
系统中有一个自然的领导者,或有专门的过程可以充当领导者。
例如,可能实现一个用于协调任务实例的单例进程。如果这个过程失败,系统可以关闭并重新启动。
在系统中可以使用更轻量级的方法来完成任务之间的协调。例如,如果协调工作只是为了控制几个任务实例对共享资源访问,则更好的解决方案是使用乐观或悲观锁定来控制。
使用第三方解决方案。例如,微软的AzureHDInsight服务(基于apach的Hadoop)使用apacheZookeeper提供的服务来协调执行使用map reduce进行数据采集的任务。


例子
下面LeaderElection解决方案中的DistributedMutex项目(演示此模式的示例在GitHub(https://github.com/mspnp/cloud-design-patterns/tree/master/leader-election)上可以找到)演示了如何在在Azureblob存储中使用lease(租约)对象来实现分布式环境下共享互斥体机制。这种机制可用于在Azure云服务一组实例中选择一个领导者。获得lease的第一个实例被选为领导者,直到租约到期或无法续约。在此期间的其他实例可以持续监视blob租约,以防领导者不可用。


blob的lease对象是对blob使用独写锁。每个blob同时只能是一个lease实例的subject。实例可以对指定blob请求租约,如果没有其他角色实例在blob上持有租约,则它将被授予租约。否则, 请求将引发异常。


有时实例因为内部错误而无限期持有租约,为了避免这种情况,需要指定租约的生存期。一旦过期,租约变为可用(公开于其他实例)。但当实例持有租约时,它可以请求续约,并将租约再授予一段时间。如果实例希望保留租约,可以连续重复此过程。有关如何租用blob的详细信息,请参阅blob的REST API。


以下c#示例中的BlobDistributedMutex类包含RunTaskWhenMutexAquired方法,使角色实例能够获取指定的blob的租约。在创建BlobDistributedMutex对象时,blob(名称、容器和存储帐户)的详细信息将被传递给BlobSettings对象中的构造函数(此对象在示例代码中是一个简单的结构)。一旦被领导者选中,此构造函数还要接收并运行实例需要执行的任务。注意,用于处理获取租约细节的代码逻辑在BlobLeaseManager辅助类中实现。

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


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


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

 上面代码示例中的RunTaskWhenMutexAquired方法调用下面的代码中的RunTaskWhenBlobLeaseAcquied方法来获取lease对象。RunTaskWhenBlobLeaseAcquired方法异步运行。如果成功获取lease对象,则实例被选为领导者。taskToRunWhenLeaseAcquired委托的目的是协调其他实例。如果未获得租约,则另一个实例被选为领导者,当前实例保持其成员角色。注意,TryAcquireLeaseOrWait方法是一个帮助方法,它使用BlobLeaseManager对象来获取lease对象。
  
  
  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.TryAquireLeaseOrWait(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.taskToRunWhenLeaseAquired.Invoke(leaseCts.Token);
          ...
        }
      }
    }
    ...
  }
  


领导者启动的任务也会异步运行。在运行时,下面代码示例中的RunTaskWhenBlobLeaseAquired方法会定期尝试续约。这有助于确保该实例保持领导角色。在这个解决方案中,续约请求之间的延迟小于租约指定的时间间隔,以防止另一个实例(在续约前)被选为领导。如果由于某原因续约失败,则该任务将被取消。如果续约失败或任务被取消(可能是由于实例关闭),租约被释放。此时,同一个或另一个实例可能被选为领导者。下面代码中显示了该过程的部分逻辑。


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类来选择一个领导者并运行执行协调操作的任务。



以下代码演示了如何在工作角色中使用BlobDistributedMutex类。在开发环境存储器的租约容器中获取一个名为MyLeaderCoordinatorTask的blob的租约对象,并指定在该实例当选领导者时,MyLeaderCoordinatorTask中哪个回调方法会被执行,。


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

以下是关于这种解决方案的一些要点:
blob是一个潜在的单点故障。如果blob服务不可用,或无法访问,则领导者将无法续约,其他实例也无法获得租约。在这种情况下,任何角色实例都不能被选为领导者。如果在设计时考虑到将blob设计弹性可伸缩的,blob服务的完全失败被认为是几乎不可能的。

如果由领导者执行的任务终止,可能会尝试续约,防止任何其他角色实例获得租约接管领导角色并执行协调任务。在应用场景中,领导者服务的健康情况应该经常被检查。

选举过程是不确定的。不能假设哪个实例将获得blob租约并成为领导者。用作blob租约目标的blob不应该被用于任何其他用途。如果实例试图将数据存储在此blob中,则除非该实例是领导者并持有blob租约,否则将无法访问数据。


相关阅读
在实现此模式时,以下指南也可能是相关的:
一个示例程序。(https://github.com/mspnp/cloud-design-patterns/tree/master/leader-election)
自动伸缩(https://docs.microsoft.com/en-us/azure/architecture/best-practices/auto-scaling)。当应用程序的负载变化时,可以启动和停止实例。自动伸缩机制可以帮助维护吞吐量和性能在高峰处理的时间。


分区计算指南(https://msdn.microsoft.com/library/dn589773.aspx)。这篇文章介绍了如何将任务分配给云服务中的主机,以达到运行成本最小化,同时维护服务的可伸缩性、性能、可用性和安全性。


基于任务的异步模式。(https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap)


Bully算法示例。(http://www.cs.colostate.edu/~cs551/CourseNotes/Synchronization/BullyExample.html)


Ring算法示例。(http://www.cs.colostate.edu/~cs551/CourseNotes/Synchronization/RingElectExample.html)


apache Curator : apache zookeeper的客户端类库。(http://curator.apache.org/)


MSDN上有关租用Blob(使用REST API)的介绍。(https://docs.microsoft.com/en-us/rest/api/storageservices/Lease-Blob?redirectedfrom=MSDN)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值