如何使用Apache BookKeeper构建分布式数据库-02

在本系列文章中,我想分享一些基本的架构概念,这些架构概念涉及无共享架构的分布式数据库的可能结构。在第一部分中,我们了解了如何将数据库设计为复制状态机。

我们有一簇机器,它们不共享磁盘,它们仅通过网络连接。我们有一个记录表,我们机器的状态就是表的内容。

对表的每个修改(包括INSERT,UPDATE和DELETE操作)都被写入到预写日志中,然后应用于数据的本地副本。

在任何时候,组中只有一台计算机被选为领导者,而客户端仅通过向该计算机发出请求来执行写入和读取操作,只有该节点才能修改表的内容。

其他机器是所谓的跟随者,它们不断跟踪日志:它们从日志中读取操作,然后将它们应用到自己的本地副本中,其顺序完全与领导者编写的顺序相同。

Apache BookKeeper

最初,Apache BookKeeper被设计为Hadoop HDFS Namenode的分布式预写日志,作为Apache Zookeeper项目的一部分,但很快它就开始了自己的独立产品生涯。

它具有支持复制状态机所需的大多数功能,我们感兴趣的主要功能是:

分散式架构:所有逻辑都在富客户端模型上运行,BookKeeper服务器只是数据容器,这使我们可以完全扩展。
不共享存储模式:客户端仅使用网络,不共享磁盘;服务器彼此之间不认识。
支持防护:BookKeeper保证只有一台机器能够写入日志。
最后添加的已确认协议:BookKeeper允许读者始终遵循该日志。
在丢失的存储节点上自动重新复制数据:在丢失机器和网络分区的情况下进行自我修复。
让我们浏览所有这些关键功能,我们将看到我们的数据库如何能够应对分布式系统的所有挑战。

avatar

Rich client model

Leader节点运行BookKeeper编写器(WriteHandle),并创建一个ledger。 ledger是我们日志的只写部分。 可以将其打开以仅写一次,并且可以根据需要读取多次。 一旦写入者将其关闭或死亡,就无法将更多数据追加到ledger中。

在创建时,您需要设置有关复制的三个参数:

  • Ensemble size (ES):将存储ledger的数量
  • Write quorum size (WQ):每个entry的份数
  • Ack quorum size (AQ):在认为写入成功之前要确认的所需副本数

例如,如果每个条目的ES = 3,WQ = 2和AQ = 1,BookKeeper将:

  • 传播副本超过3 bookies
  • 为每个entry写2个副本
  • 仅等待1确认,以声明要写入的entry是成功的

如果您的bookies很少(例如3),建议您从2–2–2开始,这是一个很好的折衷,可以保证每个entry至少有两个副本。
使ES> WQ被称为条带化,这有助于提高性能,因为写入和读取被分散到更多的客户。

writer对bookie故障做出反应,并选择新bookies用作存储,这种机制称为集成更改,如果您有足够的bookie,这对于应用程序是完全透明的,并且您不必在意这种情况。

在每次整体更改时,我们都会开始ledger的新部分,reader可以观察ledger元数据的变化,并能够自动连接到新的bookies。

Fencing

在我们的复制状态机模型中,只有一个领导者可以执行对表状态的更改。 它的领导角色必须得到其他所有人的支持,例如,您可以在ZooKeeper中使用一些领导者选举秘诀,但这不足以保证数据的整体一致性。

从理论上讲,您应该实现某种低级别的分布式共识协议(例如ZooKeeper中的ZAB),但是这会过分杀人,它确实很慢。

BookKeeper解决了这些问题。

当节点开始充当领导者时,它会对应该由前一领导者打开的每个ledger执行“恢复读取”。

此操作将连接到包含该ledger数据的每个Bookie,并将每个ledger标记为已隔离,并且如果前一个leader仍活着,则它将在下一次写操作中收到一个特定的写错误,表明他已被隔离。

BookKeeper处理各种极端情况,例如恢复期间的网络错误或多个并发恢复操作。

您可以确保只有一台计算机可以成功恢复,然后它可以开始对数据库状态进行新的更改。

但是BookKeeper只处理ledger,您必须将建立您的预写日志的ledger列表存储在某处。 此辅助元数据存储也必须处理某种类型的防护。

一种选择是将该列表存储在ZooKeeper上,并利用其内置的分布式比较和设置功能来处理要向活动ledger列表添加新ledger的并发领导者。 请参阅BookKeeper教程,以获取有关如何处理此故事部分的示例。

BookKeeper提供了一个更高级别的API,DistributedLog,它为您完成了这一部分,并为BookKeeper低级别API添加了许多内置功能。

Last add confirmed protocol

现在,每个节点仅使用Zookeeper和BookKeeper以便与其他对等方进行通信。 让我们看看关注者如何知道领导者正在取得进展,现在该寻找新entry了。

每次writer将entry添加到ledger时,它也会写出最大的条目ID,该ID已由bookies确认成功存储,我们将此ID称为“Last-Add-Confirmed entry”(LAC)。

通常,writer在发送写内容方面比在书本上保持并发送确认消息的速度要快于书本,因此即使writer不认为这些内容是持久性的,entry也可能可供reader使用。

这是非常危险的,因为以这种方式,跟随者节点可能具有将来仍未被领导者接受的数据视图。

为了解决这种情况,BookKeeper readers只能读取直到LAC的entry。 您可以确保reader始终落后于writer。

读者通过读取ledger的元数据并询问与ledger关联的bookies,可以在读取过程中获得此LAC。

avatar

让我们看一下BookKeeper新用户遇到的一个通常棘手的情况:ledger将entryX和X-1写为LAC,因此读者最多只能看到X-1,如果没有其他条目被写入,则追随者将无法选出最新的leader。 这在生产中并不是真正的问题,尤其是在重负载下。但尤其是第一次使用BookKeeper时,很难理解。

您必须设法解决此问题:

  • 如果您没有写任何东西,请定期写一个虚拟entry
  • 使用ExplicitLAC功能,这基本上是从常规搭载机制中存储辅助LAC指针

有许多方法可以绕过LAC协议,但是本博客未介绍它们,因为它们在我们的用例中没有用。

Close a ledger

BookKeeper已针对高吞吐量进行了优化,但最重要的功能是关于一致性保证,而这主要是关于使用ZooKeeper和内置防护机制的元数据管理。

一个关键点是必须定义对reader可见的实际有效条目系列,尤其是最后一个条目的ID。

由于写入可能会失败,网络也可能会失败,因此有多种机制在起作用,但最后,写入或恢复过程将密封此有效条目ID的范围。

我们称此操作为“关闭”ledger,基本上是关于将ledger的最终状态写入Zookeeper,而这种情况是在“关闭” WriteHandle时发生的。 关闭ledger后,reader可以读取最后写入的entry。

Replication in case of lost bookies

当您丢失一bookie时,BookKeeper能够通过检测故障来强制执行原始的复制因子(写入仲裁大小),并再次复制应该存储在失效的bookie上的数据。

可以使用BookKeeper工具手动完成此操作,但是也可以由自动恢复守护程序执行。

总结

将BookKeeper用作分布式预写日志是一个不错的选择,因为它可以处理分布式系统的许多方面。 如果要编写自己的日志,只有在为时已晚时,您才会陷入很多困境。

因此,BookKeeper从一开始就被设计为一个高性能的存储系统,它具有许多有关本地磁盘存储管理,网络使用和JVM性能的改进和技巧。

在本系列的下一部分中,您将学习HerdDB如何将Apache BookKeeper用作预写日志并填补该故事的空白:如何存储本地数据,协调副本并执行检查点。

参考资料

https://streamnative.io/blog/tech/2020-04-14-distributed-database-bk2/