领导人选举模式
通过协调合作,在分布式应用程序的任务实例集合执行的操作,选举一个实例作为承担管理的其他实例责任的领导者。这个模式可以有助于确保任务实例不互相冲突,导致争用共享资源,或与其他的任务实例正在执行的工作无意中干扰。
背景和问题
一个典型的云应用包括行动协调的方式很多任务。这些任务都可以是实例运行相同的代码和需要访问相同的资源,或者它们可能是可并行工作,以执行复杂计算的各个部分。
任务实例可能为多的时间自主运行,但它也可能是必要的,以协调各实例的操作,以确保它们不发生冲突,导致争用共享资源,或无意中妨碍工作,其他的任务实例正在执行。例如:
- 在基于云的系统,该系统实现了横向扩展,同一个任务的多个实例可以与每个实例服务于不同的用户同时运行。如果这些实例写入到共享资源,也可能是必要的,以协调它们的操作,以防止每个实例从盲目地覆盖由他人进行的更改。
- 如果任务正在执行复杂的计算以并行的单个元素,其结果将需要被聚合时,他们都完成了。
由于任务实例都是同行,没有天生的领导者,能够充当协调员或聚合器。
解决方案
单个任务实例应选充当领导者,这种情况下应协调其他从属任务实例的操作。如果所有的任务实例都运行相同的代码,他们可能都能够充当领导者。因此,选举过程必须谨慎管理,以防止两个或多个实例接管领导者的角色在同一时间。
该系统必须选择的领导者提供了一个坚固的机构。这种机制必须能够应付诸如网络中断或进程故障事件。在许多解决方案中,下属的工作情况监控的领导者,通过某种类型的心跳机制,或通过轮询。如果指定的领导者意外终止或网络故障使得领导者不通下属的工作情况,有必要为他们选出了新的领导人。
有可用于选举的领导者之间的一组任务在分布式环境中,包括几个策略:
- 与排名最低的实例或进程ID选择任务实例。
- 竞速获得一个共享的分布式互斥。第一个任务实例获取该互斥锁处于领先地位。然而,系统必须保证,如果领导者终止或变得与系统的其余部分断开,该互斥被释放,以允许另一个任务实例成为领导者。
- 实施的共同领导人选举算法,如恶霸算法或环算法之一。这些算法是相对简单的,但也有一些更复杂的技术提供。这些算法假定每个候选参选具有唯一的 ID,并且,它们可以用可靠的方式在其他候选人进行通信。
问题和注意事项
在决定如何实现这个模式时,请考虑以下几点:
- 选出一个领导者的过程应该是弹性的瞬时和永久失效。
- 必须能够检测到当领导失败,或变成不可用(可能是由于通讯故障)。在这是需要这种检测的速度将取决于系统。有些系统可能能够发挥作用了一会儿没有一个领导者,在此期间造成的领导人变得不可用瞬时故障可能已被排除。在其他情况下,可能有必要立即检测领袖失败并引发新的选举。
- 在实现自动缩放水平的系统中,如果系统鳞背面和关闭一些计算资源的领导者可能被终止。
- 使用一个共享的分布式互斥引入外部服务,提供了互斥锁的可用性依赖。该服务可以构成一个单点故障。如果此服务应该以任何理由变得不可用时,系统将无法选出一个领导者。
- 使用一个专用进程的领导者是一个比较简单的方法。然而,如果该过程失败,可能有显著延迟而被重新启动,并且将得到的延迟可能影响其他进程的性能和响应时间,如果他们正在等待领导人来协调的动作。
- 实施的领导人选举算法之一手动为调整和优化代码的最大灵活性。
何时使用这个模式
使用此模式时,在分布式应用程序的任务,比如云托管解决方案,需要认真协调,也没有天生的领导者。
注意: 避免使领导者在系统中的瓶颈。领导者的目的是协调的附属任务完成的工作,它不一定有机会参加这项工作本身,尽管它应该是有能力这样做,如果任务没有当选领导人。
这种模式可能不适合:
- 如果有一个天生的领导者或专用的过程,可以随时充当领导者。例如,有可能实现一个单进程,其协调该任务的实例。如果这个过程失败或变得不健康,系统可以将其关闭并重新启动它。
- 如果任务之间的协调,可以很容易地通过使用更轻便的机构来实现的。例如,如果几个任务实例仅仅需要对共享资源的访问协调,一个最好的解决办法可能是使用乐观或悲观锁来控制访问该资源。
- 如果一个第三方的解决方案是比较合适的。例如,微软的 Azure HDInsight 服务(基于 Apache Hadoop 的)使用所提供的 ApacheZookeeper 协调的 map / reduce 的汇总任务和汇总数据的服务。它也可以安装并在Azure的虚拟机配置动物园管理员,并将其集成到自己的解决方案,或使用可从微软开放技术的动物园管理员预置的虚拟机映像。欲了解更多信息,请参阅 Apache 的动物园管理员在微软的 Azure 在微软开放技术的网站。
例子
在列入可用于本指南中的示例代码中 LeaderElection 解决方案 DistributedMutex 项目展示了如何使用租赁在 Azure 存储 BLOB 提供了一种机制,实现共享的分布式互斥。此互斥锁可以用来选择在 Azure 云服务的领导者之间的一组角色的实例。第一个角色实例获得租约当选的领导人,并保持领先直至其租赁或直到它无法续租。其他角色实例可以继续监视在领导不再可用的情况下将 BLOB 租赁。
注意
一个 BLOB 租赁是在一个 blob 的排他写锁。单个 BLOB 可以是一整租的在任何一个时间点的问题。角色实例可以请求租约在指定的斑点,而且将被授予租约,如果没有其他租赁在同一个斑点,是由这个或任何其他作用,比如举行,否则将抛出一个异常。
为了减少一个故障角色实例保留无限期租用的可能性,指定了一辈子的租约。当此期满后,租赁变为可用。然而,当一个角色实例持有的租赁也可以请求租约到期,并且将被授予租约的时间再延长。角色实例可以不断重复这一过程,如果它希望保留租约。
有关如何租用一个 blob 的详细信息,请参阅租赁斑点(REST API)在 MSDN 上。
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 上述方法调用以下代码示例来实际获得的租赁所示的 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.TryAquireLeaseOrWait(leaseManager, token);
if (!string.IsNullOrEmpty(leaseId))
{
// Create a new linked cancellation token source so that if either the
// original token is cancelled or the lease cannot be renewed, the
// leader task can be cancelled.
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 cannot 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 could
// not renew the lease) then cancel the other task.
await CancelAllWhenAnyCompletes(leaderTask, renewLeaseTask, leaseCts);
}
}
}
}
...
}
该 KeepRenewingLease 方法是使用 BlobLeaseManager 对象续租另一个 helper 方法。该 CancelAllWhenAnyCompletes 方法取消指定为前两个参数的任务。
图 1 示出了 BlobDistributedMutex 类的功能。
图1 - 使用 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,该数据将不能被访问,除非该角色实例是领导者和持有的 blob租约。