本文翻译自《Distributed Locks with Apache Pulsar》。
原文链接:https://theboreddev.com/distr...
译者简介
魏伟 kivenwei,7 年研发工作经历, 主要经历了 SOA、微服务架构。熟悉云计算领域的开源软件发展和 CNCF、云原生生态,目前就职于中广核智能科技有限责任公司。

有时,软件工程师经常面临最具挑战性的工作就是如何确保我们的分布式应用程序中每次只有一个组件在执行相应的计算。

例如,应用程序中三个运行节点,并且我们需要每天运行一个计划任务,如何保证仅其中一个节点可以触发运行任务呢?如果我们在此任务中向客户发送电子邮件并在三个节点上运行任务,则我们的客户可能会收到 3 次此电子邮件。事与愿违, 那么如何解决此问题呢?

有人可能会说:“简单,只运行一个节点!”。

其实,没那么容易。在大多数情况下,我们必须确保服务具有足够的可用性级别,而仅运行一个节点意味着如果出现问题,服务将不可用。

我们真正需要的方案是选择某个“主节点”来负责此任务。另一个需要考虑的因素是如果我们的主节点出现故障,则必须立即将此任务委托给其中一个备用节点执行任务以避免中断。

让我们来看看,我们想要实现的效果。如下图:

在操作上,需要找出一种简单的方法来“选举”负责执行任务的主节点,其它节点将耐心等待轮到它执行的情况。因此这些备用节点将处于所谓的“休眠”状态;只有在主节点故障或无响应时,备用节点才会被唤醒。

如何解决这个问题

在一些场景中,会倾向于用一些相当复杂的实现去确保只有其中一个节点执行任务。

现在一些数据库引擎支持的“CAS”原子操作,可能是快速解决这个问题的合理方法。我们利用数据库中一项特性即可解决问题,而无需自己重新实现。但是如果数据库不支持这样的原子操作呢?

问题会变得更复杂,因为每个节点都会尝试竞争以获得锁,但是两个节点可以同时获得锁的值为“free”且都成功设置了值,而不会注意到另一个节点也“获得锁”。这意味着不仅有一个节点,而是两个节点都将执行此任务,例如向客户发送电子邮件,

然而,即使在数据库中支持 CAS 操作,也需要提供一些机制来确保如果我们的主节点故障后备用节点来接管:一个类似于心跳的进程,不断检查状态并在节点出现故障时采取相应措施。然而,这种方案较为耗时耗精力,理想情况下,我们希望利用完善的并经过全面测试的产品。

如果你已经部署了 Pulsar,那么这正是我们使用 Apache Pulsar 这类产品来解决这一问题的原因。Kafka 也可以实现类似的解决方案,但这篇文章主要聚焦于 Apache Pulsar。

使用 Apache Pulsar 实现分布式锁

那么如何去利用 Apache Pulsar 的优势呢?Pulsar 提供了一种叫做 failover(灾备)的订阅机制,它主要实现了一个 leader 选举机制。

如何充分利用这种选举机制来保证计划任务只执行一次呢?

无需深入了解实现的具体细节,因为这在很大程度上取决于使用场景,可以想出一个简单的方法来实现这一点。具体如下:

基于心跳事件的自动启动调度器

一个实现方法是启动一个消费者并开始监听心跳事件,然后立即开始发送心跳事件。消费者将使用 failover(灾备)订阅来订阅主题。因此,只有一个节点能够启动调度程序。如果主节点故障,那么其他备用节点将接管并立即启动任务。下图是实现的思路:

在这个例子中,我们有一个主题来管理分布式锁,每个消费者定期向这个主题发送心跳,同时使用 failover(灾备)订阅主题。只有其中一个节点会成为主节点并处理心跳事件。如果主节点还没有启动邮件调度器,它会在收到第一个心跳后立即启动;对于其余的心跳,只要调度程序在运行,它们就会被忽略。

如果主节点故障后将会发生什么?具体看下图:

在主节点故障的情况下,Pulsar 的灾备(failover)订阅会检测到该节点已经故障,备用节点将接管。在图中,左侧的备用节点将收到心跳,触发任务执行的开始。一旦以前的主节点恢复,它将作为备用节点再次“休眠”。

结论

我们一般不建议只为了实现分布式锁就采用任何新技术,通常寻找并利用当前产品的优势可以节省时间、降低操作的复杂性。

当然,用户也可以在其他系统上构建自己的分布式锁,但这需要时间,而且也容易出错。但是在 Pulsar 的实现过程中,该特性经过其他工程师的彻底测试后可用且可靠,可为用户节省宝贵的时间,减少操作上的问题。

关注公众号「Apache Pulsar」,获取干货与动态

加入 Apache Pulsar 中文交流群 👇🏻


ApachePulsar
192 声望939 粉丝

Apache软件基金会顶级项目,下一代云原生分布式消息系统