跳到内容

GitHub上的MySQL高可用性

GitHub使用MySQL作为所有非git数据的主数据存储,其可用性对GitHup的操作至关重要。网站本身、GitHub的API、身份验证等都需要数据库…

作者

GitHub使用MySQL作为其所有非-吉特,其可用性对GitHub的运营至关重要。网站本身、GitHub的API、身份验证等等都需要数据库访问。我们运行多个MySQL集群,为不同的服务和任务提供服务。我们的集群使用经典的primary-replicas设置,其中集群中的单个节点(初级的)能够接受写入。其余集群节点(复制副本)异步重播主服务器上的更改,并为我们的读取流量提供服务。

主节点的可用性尤其关键。如果没有主写入,集群就无法接受写入:任何需要持久化的写入都无法持久化。任何传入的更改,如提交、问题、用户创建、审阅、新存储库等,都将失败。

为了支持写操作,我们显然需要一个可用的writer节点,即集群的主节点。但同样重要的是,我们需要能够识别,或发现,该节点。

如果出现故障,比如主服务器崩溃,我们必须确保存在新的主服务器,并且能够快速公布其身份。检测故障、运行故障转移和公布新主服务器标识所需的时间构成了总停机时间。

这篇文章展示了GitHub的MySQL高可用性和主服务发现解决方案,它允许我们可靠地运行跨数据中心操作,容忍数据中心隔离,并在出现故障时缩短停机时间。

高可用性目标

本文中描述的解决方案迭代并改进了GitHub以前实现的高可用性(HA)解决方案。随着我们的扩展,我们的MySQL HA策略必须适应变化。我们希望为MySQL和GitHub中的其他服务提供类似的HA策略。

在考虑高可用性和服务发现时,一些问题可以引导您找到合适的解决方案。不完整的列表可能包括:

  • 你能忍受多少停机时间?
  • 碰撞检测的可靠性如何?您能容忍误报(过早故障切换)吗?
  • 故障切换的可靠性如何?它会在哪里失败?
  • 该解决方案在跨数据中心的工作情况如何?在低延迟和高延迟网络上?
  • 该解决方案是否能够容忍完整的数据中心(DC)故障或网络隔离?
  • 哪种机制(如果有的话)可以防止或缓解分机场景(两台服务器声称是给定集群的主服务器,彼此独立且在不知情的情况下接受写入)?
  • 您能承受数据丢失吗?在多大程度上?

为了说明上面的一些内容,让我们首先考虑一下我们之前的HA迭代,以及为什么要更改它。

放弃基于VIP和DNS的发现

在上一次迭代中,我们使用了:

  • 协调器用于检测和故障切换,以及
  • 用于主发现的VIP和DNS。

在该迭代中,客户机使用名称发现了writer节点,例如。mysql-writer-github.net。名称解析为主主机将获取的虚拟IP地址(VIP)。

因此,在正常情况下,客户端只需解析名称,连接到解析的IP,然后在另一端找到主侦听。

考虑一下此复制拓扑,它跨越三个不同的数据中心:

示例3数据中心拓扑

如果主服务器发生故障,则必须升级一个新服务器(其中一个副本)来代替它。

协调器将检测到故障,升级新的主服务器,然后重新分配名称/VIP。客户端实际上并不知道主服务器的身份:他们只有一个名称,该名称现在必须解析为新的主名称。然而,请考虑:

VIP是合作的:它们由数据库服务器自己声明和拥有。要获取或释放VIP,服务器必须发送ARP请求。拥有VIP的服务器必须先释放它,然后新升级的主服务器才能获得它。这会产生一些不希望出现的影响:

  • 有序的故障转移操作将首先联系死主服务器并请求其释放VIP,然后联系新升级的主服务器并要求其获取VIP。如果无法联系到旧主服务器或拒绝释放VIP,该怎么办?考虑到该服务器首先会出现故障场景,因此它不太可能无法及时响应,或者根本无法响应。
    • 我们最终可能会出现一个分裂的系统:两个主机声称拥有相同的VIP。根据最短的网络路径,不同的客户端可以连接到这些服务器中的任何一个。
    • 这里的真相来源于两个独立服务器的合作,而这种设置是不可靠的。
  • 即使旧主节点确实合作,工作流也会浪费宝贵的时间:当我们联系旧主节点时,切换到新主节点会等待。
  • 即使VIP发生变化,现有的客户端连接也不能保证与旧服务器断开连接,而且我们可能仍然会遇到分流。

在我们的部分设置中,VIP受物理位置的约束。它们由交换机或路由器所有。因此,我们只能将VIP重新分配到位于同一位置的服务器上。特别是,在某些情况下,我们无法将VIP分配给在其他数据中心升级的服务器,必须进行DNS更改。

  • DNS更改传播时间较长。客户端在预配置的时间内缓存DNS名称。跨DC故障切换意味着更多的停机时间:使所有客户端都知道新主服务器的身份需要更多的时间。

仅这些限制就足以推动我们寻找新的解决方案,但需要更多考虑的是:

  • 初级选手通过pt-心跳服务,目的是滞后测量和节流控制。这项服务必须在新升级的初选中启动。如果可能,将关闭旧主服务器上的服务。
  • 同样,伪GTID注射由初级管理人员自行管理。它需要启动新的初选,最好停止旧的初选。
  • 新的主目录已设置为可写。旧主菜单将设置为只读(_O),如果可能的话。

这些额外的步骤是导致总停机时间的一个因素,并引入了它们自身的故障和摩擦。

该解决方案奏效了,GitHub成功地完成了MySQL故障切换,并在监控范围内运行良好,但我们希望HA在以下方面有所改进:

  • 不受数据中心限制。
  • 容忍数据中心故障。
  • 删除不可靠的协作工作流。
  • 减少总停机时间。
  • 尽可能进行无损故障切换。

GitHub的HA解决方案:协调器、领事、GLB

我们的新战略,以及附带的改进,解决或减轻了上述许多担忧。在今天的HA设置中,我们有:

  • 协调器运行检测和故障切换。我们使用交叉DC管弦乐队/raft设置如下所示。
  • 哈希科普的领事用于服务发现。
  • GLB/HA代理作为客户端和编写器节点之间的代理层。我们的GLB主管是开源.
  • 选播用于网络路由。
GitHub上的MySQL HA解决方案

新设置将删除VIP和DNS更改。在我们引入更多组件的同时,我们能够解耦组件并简化任务,并且能够利用稳定可靠的解决方案。接下来是细分。

正常流量

在正常情况下,应用程序通过GLB/HAProxy连接到写入节点。

这些应用程序永远不会知道主播的身份。和以前一样,他们使用名字。例如集群1将是mysql-writer-1.github.net。然而,在我们当前的设置中,此名称被解析为选播知识产权。

使用选播,名称在任何地方都解析为相同的IP,但根据客户端的位置,流量路由不同。特别是,在我们的每个数据中心中,我们都有GLB,这是我们的高可用负载平衡器,部署在多个机箱上。到的流量mysql-writer-1.github.net始终路由到本地数据中心的GLB集群。因此,所有客户端都由本地代理提供服务。

我们在HA代理。我们的HAProxy写入程序池:每个MySQL集群一个池,其中每个池正好有一个后端服务器:集群的初级的。所有DC中的所有GLB/HAProxy框都具有完全相同的池,并且它们都指示这些池中完全相同的后端服务器。因此,如果应用程序希望写入mysql-writer-1.github.net,它连接到哪个GLB服务器并不重要。它将始终路由到实际的集群1主节点。

就应用程序而言,发现在GLB结束,并且永远不需要重新发现。将流量路由到正确的目的地完全取决于GLB。

GLB如何知道要将哪些服务器列为后端,以及如何将更改传播到GLB?

通过领事发现

Consul是众所周知的服务发现解决方案,还提供DNS服务。然而,在我们的解决方案中,我们将其用作高可用的键值(KV)存储。

在Consul的KV存储中,我们写下了集群主密钥的身份。对于每个集群,都有一组KV条目指示集群的主节点功能qdn、端口、ipv4、ipv6。

每个GLB/HAProxy节点运行顾问模板:侦听Consul数据更改的服务(在我们的示例中:集群主数据更改)。顾问模板生成有效的配置文件,并能够在更改配置时重新加载HAProxy。

因此,每个GLB/HAProxy盒都会观察到Consul对主节点身份的更改,然后重新配置自身,将新的主节点设置为集群后端池中的单个实体,并重新加载以反映这些更改。

在GitHub,我们在每个数据中心都有Consul设置,并且每个设置都具有高可用性。然而,这些设置彼此独立。它们之间不进行复制,也不共享任何数据。

领事是如何得知变化的,信息是如何跨DC分发的?

配器/木筏

我们运行了一个管弦乐队/raft设置:协调器节点通过以下方式相互通信共识。我们有一两个协调器每个数据中心的节点。

协调器负责故障检测、MySQL故障转移,并将主服务器的更改告知Consul。故障切换由单个管弦乐队/raft引线节点,但改变,集群现在有一个新主节点的消息将传播给所有人协调器节点,通过机制。

作为协调器节点接收到主要更改的消息,它们各自与本地Consul设置通信:它们各自调用KV写入。有多个DC协调器代表将收到多封(相同的)致领事的信函。

整合流程

在主要碰撞场景中:

  • 这个协调器节点检测故障。
  • 这个管弦乐队/raft领导人开始复苏。新的初选得到提升。
  • 管弦乐队/raft向所有人宣传主要变化群集节点。
  • 每个管弦乐队/raft成员收到领导变更通知。他们每个人都会用新主修的身份更新当地领事的KV商店。
  • 每个GLB/HAProxy都有顾问模板正在运行,它观察Consul的KV存储中的变化,并重新配置和重新加载HAProxy。
  • 客户端流量被重定向到新的主服务器。

每个组件都有明确的责任归属,整个设计既解耦又简化。协调器不知道负载平衡器。领事不需要知道信息来自哪里。代理人只关心领事。客户只关心代理。

此外:

  • 没有要传播的DNS更改。
  • 没有TTL。
  • 流动不需要死的初选者的合作。这在很大程度上被忽视了。

其他详细信息

为了进一步保护流量,我们还提供了以下内容:

  • HAProxy配置了一个非常短的硬顶后。当它使用写入池中的新后端服务器重新加载时,它会自动终止与旧主服务器的任何现有连接。
    • 使用硬顶后我们甚至不需要客户的合作,这缓解了一种分裂的局面。值得注意的是,这不是封闭的,而且一些时间在我们消灭旧连接之前先通过。但有一个时间点之后,我们会感到舒适,不会有令人不快的惊喜。
  • 我们并不严格要求领事随时待命。事实上,我们只需要它在故障转移时可用。如果Consul碰巧倒台,GLB将继续以最后已知的值运行,并且不会做出剧烈动作。
  • GLB设置为验证新升级的主服务器的身份。类似于我们的上下文软件MySQL池,在后端服务器上进行检查,以确认它确实是writer节点。如果我们碰巧在领事馆删除了小学校长的身份,没问题;忽略空条目。如果我们在Consul中错误地写下了非主服务器的名称,没问题;GLB将拒绝更新,并以最后一个已知状态继续运行。

我们将在以下几节中进一步解决问题并实现HA目标。

协调器/筏故障检测

协调器使用整体方法检测故障,因此非常可靠。我们没有观察到误报:我们没有过早的故障切换,因此没有不必要的停机时间。

管弦乐队/raft进一步解决了完全直流网络隔离(又称直流围栏)的情况。DC网络隔离可能会导致混乱:该DC内的服务器可以相互通信。它是他们与其他DC隔离的网络,或者是其他跟单信用证正在进行网络隔离?

在一个管弦乐队/raft设置leader节点是运行故障转移的节点。领导者是获得大多数群体(quorum)支持的节点。我们的协调器节点部署是这样的,没有一个数据中心占大多数n-1个DC确实如此。

如果完全隔离直流网络协调器该DC中的节点与其他DC中的对等节点断开连接。因此协调器隔离DC中的节点不能是集群。如果任何这样的节点碰巧是领导者,它就会下台。将从任何其他DC中指派一名新领导。该领导将得到所有其他DC的支持,这些DC能够相互沟通。

因此协调器调用快照的节点将位于网络隔离数据中心之外。如果隔离DC中有一个主电源,协调器将启动故障转移,以将其替换为其中一个可用DC中的服务器。我们通过将决策授权给非隔离DC中的法定人数来缓解DC隔离。

更快的广告

通过尽早公布主要更改,可以进一步缩短总停机时间。如何实现这一点?

什么时候?协调器开始故障转移时,它会观察可升级的服务器组。了解复制规则并遵守提示和限制,它能够就最佳行动方案做出明智的决定。

它可能认识到可用于升级的服务器也是理想候选人,以便:

  • 没有任何东西可以阻止服务器的升级(并且用户可能已经暗示升级时首选此服务器),并且
  • 预计服务器能够将其所有同级作为副本。

在这种情况下协调器首先将服务器设置为可写,然后立即公布服务器的升级(在我们的示例中是向Consul KV写入),即使是在异步开始修复复制树时,此操作通常需要几秒钟的时间。

很可能在我们的GLB服务器完全重新加载时,复制树已经完好无损,但并不是严格要求的。服务器可以很好地接收写操作!

半同步复制

在MySQL中半同步复制在知道更改已发送到一个或多个副本之前,主副本不会确认事务提交。它提供了一种实现无损故障切换的方法:应用于主副本的任何更改都会应用于或等待应用于其中一个副本。

一致性是有代价的:对可用性的风险。如果没有副本确认收到更改,主副本将阻塞,写入将暂停。幸运的是,有一个超时配置,之后主服务器可以恢复到异步复制模式,使写入再次可用。

我们将超时设置为一个合理的较低值:500毫秒。将更改从主DC副本发送到本地DC副本,通常也会发送到远程DC,这已经足够了。通过此超时,我们可以观察到完美的半同步行为(没有异步复制的回退),并且在确认失败的情况下,可以使用非常短的阻塞时间。

我们在本地DC副本上启用半同步,并且在主副本死亡的情况下,我们期望(尽管没有严格执行)无损故障转移。完全DC故障的无损故障切换成本高昂,我们预计不会出现这种情况。

在试验半同步超时时,我们还观察到一种对我们有利的行为:我们能够影响理想候选人在主要故障的情况下。通过在指定服务器上启用半同步,并将其标记为候选人,我们能够减少总停机时间影响失败的结果。在我们的实验我们观察到,我们通常以理想候选人,因此可以快速发布广告。

心跳注射

而不是管理pt-心跳服务于提升/降级的初选,我们选择在任何时候都运行它。这需要一些修补从而使pt-心跳适应服务器更改只读(_O)来回陈述或完全崩溃。

在我们当前的设置中pt-心跳服务在主服务器和副本上运行。在主服务器上,它们生成心跳事件。在副本上,他们确定服务器只读并定期重新检查它们的状态。一旦服务器升级为主服务器,pt-心跳在该服务器上,将服务器标识为可写,并开始注入心跳事件。

协调器所有权委托

我们进一步委托给协调人:

  • 伪GTID注射,
  • 将升级的主服务器设置为可写,清除其复制状态,以及
  • 将旧主服务器设置为只读(_O),如果可能的话。

在所有新总统的事情上,这减少了摩擦。很明显,一个刚刚被提升的初选应该是活跃的、可访问的,否则我们就不会推广它协调器将更改直接应用于升级的主服务器。

局限性和缺点

代理层使应用程序不知道主应用程序的身份,但它也对主应用程序进行了身份屏蔽。所有主要看到的都是来自代理层的连接,并且我们丢失了有关实际连接源的信息。

随着分布式系统的发展,我们仍然有未处理的场景。

值得注意的是,在数据中心隔离场景中,假设主服务器位于隔离的DC中,该DC中的应用程序仍然能够写入主服务器。一旦网络恢复,这可能会导致状态不一致。我们正在通过实施可靠的石头在非常隔离的DC内。和以前一样,一些时间将在关闭初选之前通过,可能会有一段短时间的分裂。完全避免分流的运营成本非常高。

存在更多情况:故障转移时Consul停机;部分直流隔离;其他。我们知道,使用这种性质的分布式系统,不可能关闭所有漏洞,因此我们将重点放在最重要的情况上。

结果

我们的协调器/GLB/Consul设置为我们提供了:

  • 可靠的故障检测,
  • 与数据中心无关的故障切换,
  • 通常是无损故障切换,
  • 数据中心网络隔离支持,
  • 大脑分裂缓解(更多工作正在进行中),
  • 无合作依赖性,
  • 协议双方:10秒和13秒在大多数情况下,占总停机时间的一半。
    • 我们最多可以看到20秒在不太频繁的情况下,占总停机时间的一半,最多25秒在极端情况下。

结论

编排/代理/服务发现范式在一个解耦的体系结构中使用众所周知的可信组件,这使得部署、操作和观察更加容易,并且每个组件可以独立地向上或向下扩展。我们在不断测试我们的设置的同时,继续寻求改进。

从GitHub了解更多信息

工程类

工程类

直接来自GitHub工程团队的帖子。
GitHub环球2024

GitHub环球2024

获取AI、DevEx和安全全球开发者活动十周年门票。
GitHub Copilot公司

GitHub Copilot公司

不要独自飞行。免费试用30天。
在GitHub工作!

在GitHub工作!

查看我们当前的职位空缺。