当前位置: 首页 > news >正文

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)

相关文章:

  • 如何从零开始开发一款嵌入式产品(20年的嵌入式经验)
  • Azure设计模式之实例化视图
  • Azure设计模式之管道过滤器模式
  • 用系统函数获取当前季度
  • Azure设计模式之优先消息队列
  • Azure应用部署方式对比
  • BadImageFormatException or An attempt was made to load a program with an incorrect format
  • 获取中国标准时间(由美国官方时间折算)
  • C# 使用ExcelReader 上传excel
  • Nebula3 Render Application Wizard
  • 使用red5+ffmpeg读取ip摄像头流并广播rtmp
  • WCF中使用MSMQ无法传送自定义对象
  • azure设计模式之队列负载均衡
  • 移动MM初探之三:价值链三环节定位剖析
  • Azure设计模式之重试模式
  • 分享的文章《人生如棋》
  • 3.7、@ResponseBody 和 @RestController
  • Android Studio:GIT提交项目到远程仓库
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • Golang-长连接-状态推送
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • JavaScript 基础知识 - 入门篇(一)
  • js递归,无限分级树形折叠菜单
  • laravel 用artisan创建自己的模板
  • SQLServer之创建数据库快照
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 树莓派 - 使用须知
  • 学习JavaScript数据结构与算法 — 树
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • SAP CRM里Lead通过工作流自动创建Opportunity的原理讲解 ...
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #QT(智能家居界面-界面切换)
  • #前后端分离# 头条发布系统
  • (07)Hive——窗口函数详解
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (二)丶RabbitMQ的六大核心
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • .Net core 6.0 升8.0
  • .Net Core 中间件验签
  • .net core使用ef 6
  • .NET delegate 委托 、 Event 事件,接口回调
  • .NET 事件模型教程(二)
  • .net 微服务 服务保护 自动重试 Polly
  • .net(C#)中String.Format如何使用
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换
  • .NET6实现破解Modbus poll点表配置文件
  • .NET单元测试
  • .net开发时的诡异问题,button的onclick事件无效
  • .net实现客户区延伸至至非客户区
  • .Net下的签名与混淆
  • .net用HTML开发怎么调试,如何使用ASP.NET MVC在调试中查看控制器生成的html?