数据库数据复制技术入门

19-01-07 banq
                   

复制数据库数据可以使我们的应用程序更快,并增加我们对故障的容忍度,但是有很多不同的选项可供选择,每个选项都带有成本付出。如果我们不了解我们使用的工具是如何工作的,以及它们提供的保证是什么(或者更重要的是,不提供),那么很难做出正确的选择,这就是我想在这里探讨的内容。本文研究所有可用的选项以及数据库复制设置中涉及的所有内容都可能是压倒性的,涉及几十年关于该主题的文献。

光在真空中以299,792 km / s的速度行进。即使我们假设我们的要求以这样的速度行驶,并且它们以直线行驶,但是对于世界旅行来说仍然需要133ms。

无论我们如何快速执行查询,如果数据库位于北美,数据仍然需要一直送往亚洲,然后亚洲办公室的人员才能使用这些数据。很明显,我们必须将这些数据提供给离它们更近的地方,所以复制任务就开始了。

复制什么和为什么复制

当我们说要复制某些内容时,就意味着我们希望在多个位置保留相同数据的副本。在数据库的情况下,这可能意味着整个数据库的副本,这是最常见的情况,或者只是它的某些部分(例如一组表)。我们通常通过网络连接将在多个位置保留数据,这是我们大多数头痛的根源,您将会看到一些问题。

这样做的原因:

  • 您希望将数据与用户保持近距离,以便节省数据旅行时间。请记住,无论您的数据库有多快,数据仍然需要从启动请求的计算机传输到数据库所在的服务器,然后再返回。您可以优化数据库,但无法优化物理定律。
  • 您希望扩展服务请求的计算机数量。在某些时候,单个服务器将无法处理它需要服务的客户端数量。在这种情况下,拥有多个具有相同数据的数据库可帮助您为更多客户提供服务 这就是我们所说的水平缩放(而不是 垂直缩放,这意味着拥有更强大的机器)。
  • 如果发生故障(即将发生),您希望安全。想象一下,您将数据存储在单个数据库服务器中并且该服务器着火,然后会发生什么?我确定你有某种备份(对吗?!),但你的备份将a)需要一些时间来恢复,b)可能至少要几个小时。不酷。拥有副本意味着您可以在解决火灾情况时开始将请求发送到此服务器,也许没有人会注意到发生了一些不好的事情。

强制性的CAP介绍

CAP定理是由Eric Brewer在2000年引入的,所以这不是一个新想法。首字母缩略词代表Consistency,Availability和Partition Tolerance,它基本上说,鉴于分布式系统中的这三个属性,你需要选择其中的2个(即你不能拥有全部3个)。实际上,这意味着您需要在不可避免的分区发生时在一致性和可用性之间进行选择。

一致性:在CAP定义中,一致性意味着集群中的所有节点(例如,所有数据库服务器,领导者和副本)在任何给定时间点都会看到相同的数据。实际上,这意味着如果您在同一时间查询任何数据库服务器,您将得到相同的结果。

可用性:这意味着即使我们无法保证它具有最新数据,读取和写入也将始终成功。在实践中,这意味着我们仍然可以使用我们的一个数据库,即使这个数据库无法与其他数据库交互,因此可能没有收到最新的更新。

分区容差:这意味着即使存在网络分区,您的系统也将继续工作。网络分区意味着群集中的节点无法相互通信。

为什么我要谈论这个?好吧,因为根据您所采用的路线,您将有不同的权衡,有时有利于一致性和有时可用性。

CAP定理在分布式系统讨论中的价值有多大值得商榷,但我认为记住在处理网络分区时几乎总是交换可用性的一致性(反之亦然)是有用的。

关于延迟

延迟是请求等待处理的时间(它是 潜在的)。我们的目标是尽可能降低延迟。当然,即使延迟很低,我们仍然可以有很长的响应时间(例如,如果查询需要很长时间才能运行),但这是一个不同的问题。

当我们复制我们的数据库时,我们可以通过缩短此请求需要传输的距离和/或增加我们的容量来减少延迟,因此请求无需等待,因为繁忙的服务器可以处理它。

异步复制

当我们谈论的复制,我们基本上是说,当我写一些数据到一个指定节点A,这个相同的数据也需要在节点B(也许C和D和E和...)写入,但我们需要决定此复制是如何发生的,以及我们需要什么保证。与往常一样,这都是权衡利弊。让我们探讨一下我们的选择。

第一个选项是在收到消息的节点成功写入数据后立即向客户端发送确认, 然后将此消息发送到副本(可能存在也可能不存在)。

这看起来很棒,我们没有注意到任何性能影响,因为复制发生在后台,在我们已经得到响应之后,如果副本已经死或慢,我们甚至都不会注意到它,因为数据已经发回给客户。一切很好。

异步复制存在(至少)两个主要问题。首先是我们正在削弱我们的持久性保证,另一个是我们暴露于复制滞后中。我们将在稍后讨论复制滞后,让我们首先关注持久性问题。

我们的问题是,如果收到此写入请求的节点在将这种更改复制到副本之前发生了失败,则即使我们向客户端发送了确认,数据也会丢失。

你可能会问自己

“但是在那一刻发生失败的可能性是多少?!”

如果是这样的话,我建议你改为问

“ 如果当时发生失败会有什么后果?”

是的,承担风险可能完全没问题,但在处理金融交易的典型例子中,或许最好是付出代价以获得更强的担保。但是代价是多少?

同步复制

正如您所料,同步复制基本上意味着我们将 首先复制数据,然后向客户端发送确认。因此,当客户得到确认时,我们可以确保数据被复制并且安全(好吧,它绝不是100%安全的,理论上我们所有的数据中心都可以同时爆炸,但它足够安全)。

我们需要支付的代价是:性能和可用性。

性能损失是由于我们需要等待这些可能很慢的复制节点做完他们的事情并向我们发送确认。由于这些副本通常在地理上分布,并且可能彼此相距很远,因此这比我们想要等待的时间更长。

第二个问题是可用性。如果其中一个副本(记住,我们可以有很多!)已关闭或由于某种原因我们无法访问它,我们根本无法写入任何数据。你应该总是计划失败,并且网络分区比我们想象的更常见,因此根据可以访问的所有副本执行任何写入对我来说似乎不是一个好主意

不是8,不是80

有一些中间方式:一些数据库和复制工具允许我们定义一些要同步复制的关注者,而其他人只使用异步方法。这有时称为半同步复制。

例如,Postgres您可以定义一个调用的配置,synchronous_standby_names 以指定哪些副本将同步接收更新,而其他副本将只是异步接收它们。

单领导复制

最常见的复制拓扑是拥有一个领导者,然后将更改变动复制到所有关注者。

在此设置中,客户端始终将写入(在数据库INSERT,UPDATE和DELETE查询的情况下 )发送给领导者,而不是发送给它的跟随者。但是,这些跟随关注者可以响应来自客户端的读取查询。

拥有单一领导者的主要好处是我们避免了由并发写入引起的冲突。所有客户端都在写入同一台服务器,因此协调更容易。如果我们允许客户端同时写入2个不同的服务器,我们需要以某种方式解决如果它们都尝试使用不同的值更改相同的对象时会发生的冲突(稍后会详细介绍)。

那么,如果我们决定采用单一领导方法,我们需要记住哪些问题?第一个是我们需要确保只有一个节点能够处理所有写入。虽然我们可以在整个集群中拆分读取工作,但所有写入都将转移到单个服务器,如果您的应用程序非常密集,则可能会出现问题。但请记住,大多数应用程序读取的数据比写入的数据多得多,因此您需要分析这是否真的是一个问题。

另一个问题是您需要支付写入的延迟成本。还记得文章开头亚洲的同事案例吗?好吧,当他们想要更新某些数据时,该查询仍然需要在获得响应之前到达全球。

最后,虽然下面这个问题对于单一领导者复制来说并不是一个真正的问题,但您需要考虑领导节点死亡时会发生什么。整个系统是否会停止工作?它是否仅适用于读取(来自副本),但不适用于写入?是否有选举新领导者的流程(即将其中一个副本推广到领导者身份)?这个选举过程是自动化还是需要有人告诉系统谁是城里的新的领导者?

乍一看,似乎最好的方法是只有自动故障转移策略,它将选出一个新的领导者,一切都会继续工作。不幸的是,这说起来容易做起来难。

自动故障转移的挑战

我们需要问的第一个问题是:我们怎样才能确定领导者已经死了?答案是:我们可能无法确定。

在任何分布式系统中一样,不可能将慢速响应与死亡节点区分开来。数据库通常使用超时来决定(例如,如果我在20秒内没有收到您的回复,那么您已经死了!)。

这通常足够好,但肯定不完美。如果您等待更多,则不太可能将节点识别为错误,但是也需要更多时间来启动故障转移过程,同时您的系统可能无法使用。另一方面,如果您没有给它足够的时间,您可能会启动一个不必要的故障转移过程。所以这是第一个挑战。

挑战二:你需要决定谁是新的领导者。你有所有这些追随者,生活在一个无政府状态,他们需要以某种方式就谁应该成为新的领导者达成一致。例如,一个相对简单(至少在概念上)接近它以具有预定义的后继节点,其将在原始领导者死亡时假设领导者位置。或者,您可以选择具有最新更新的节点(例如,更靠近领导者的节点),以最大限度地减少数据丢失。无论你决定选择新的领导者,所有节点仍然需要就该决定达成一致,这是困难的部分。这被称为共识问题,并且可能非常棘手。

好吧,你发现领导者真的已经死了并且选择了一个新的领导者,现在你需要以某种方式告诉客户开始向这个新的领导者发送写入,而不是死者。这是一个请求路由问题,我们也可以从几个不同的角度来处理它。例如,您可以允许客户端向任何节点发送写入,并让这些节点将此请求重定向到领导者。或者,您可以拥有一个接收此消息的路由层,并将它们重定向到相应的节点。

如果您使用异步复制,则新的领导者可能没有来自前一个领导者的所有数据。在这种情况下,如果旧的领导者复活(可能只是网络故障或服务器重启)并且新领导者在此期间收到了相互冲突的更新,我们如何处理这些冲突?一种常见的方法是放弃这些冲突(使用最后写入 - 获胜方法),但这也可能是危险的(以此Github问题(从2012年开始)为例)。

我们也可以有一个有趣的(好吧,也许它不是那么有趣,当它在生产中发生)的情况,前一个领导者回来并认为它仍然是领导者。这被称为裂脑,可能导致奇怪的情况。

如果两个领导者都开始接受写入而我们还没有准备好处理冲突,则可能会丢失数据。

某些系统具有防护机制,如果检测到有多个领导者,则会强制关闭一个节点。这种方法被称为STONITH“头部中的另一个节点”。

这也是当有一个网络分区时我们最终会看到两个孤立的集群,每个集群都有自己的领导者,因为这个集群的每个部分都看不到另一个集群,因此认为它们都已经死了。

如您所见,自动故障转移并不简单。有很多事情需要考虑,因此有时人手动执行此过程会更好。当然,如果您的领导者数据库在晚上7点死亡并且没有人随叫随到,那么可能不是等到明天早上的最佳解决方案,因此,一如既往地权衡利弊。

​​​​​​​待续:

数据库复制技术之二:多领导者复制

数据库复制技术之三:最终一致性

                   

4