社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

GitHub 的 MySQL 高可用性实践分享

数据分析与开发 • 6 年前 • 661 次点击  

(点击上方公众号,可快速关注)


英文:shlomi-noach,翻译:开源中国

www.oschina.net/translate/mysql-high-availability-at-github


GitHub 使用 MySQL 作为所有非 git 仓库数据的主要存储, 它的可用性对 GitHub 的访问操作至关重要。GitHub 站点本身、GitHub 的 API、身份验证等等都需要进行数据库访问。我们运行着多个 MySQL 集群来为不同的服务和任务提供支持。我们的集群使用经典的主从配置, 主集群中的某个节点能够接受写入。其余的从集群节点异步同步来自主服务器的更改, 并提供数据的读取服务。


主节点的可用性尤为重要。没有主服务器, 集群无法接受写入:任何需要保留的写入数据都不能持久化保存,任何传入的更改(如提交、问题、用户创建、审阅、新存储库等)都将失败。


为了支持写操作,我们显然需要有一个可用的数据写入节点,一个主集群。但同样重要的是,我们需要能够识别或找到该节点。


在一个写入失败,提示说主节点崩溃的场景中,我们必须确保能启用一个新的主节点,并快速表明其身份。检测故障所需的时间、进行故障转移并公布新的主节点所花费的时间,构成了总的停机时间。


本文将介绍 GitHub 的 MySQL 高可用性和主服务发现解决方案,它使我们能够可靠地运行跨数据中心操作,容忍数据中心隔离,并使得出现故障时耗费的停机时间变得更短。


高可用目标


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


在考虑高可用和服务发现时,有些问题可以引导你找到合适的解决方案。包含但不限于:


  • 你能容忍多长的中断时间?

  • 崩溃检测的可靠性如何?你能容忍错误报告(过早的故障转移)吗?

  • 故障转移的可靠性如何?什么情况下可以失败?

  • 解决方案在跨数据中心的场景下效果如何?在低延迟和高延迟的网络情况下如何?

  • 解决方案是否允许一个完整的数据中心故障或者出现网络隔离?

  • 有没有防止或缓解脑裂(两台服务器都宣称是某个集群的主节点,不知情对方的存在,并且都能接受写操作)的机制。

  • 你能允许数据丢失吗?在多大程度上?


为了说明上面的一些情况,首先让我们讨论一下之前的高可用方案,并说说我们为什么要修改它。


移除基于 VIP 和 DNS 的服务发现


在之前的迭代版本中,我们:


  • 使用 orchestrator 来做检测和故障转移

  • 使用 VIP 和 DNS 做主节点的发现


在这个迭代版本中,客户端使用名字服务(比如 mysql-writer-1.github.net)来发现写节点。名字可以解析为一个虚拟 IP(VIP),这个 VIP 指向主节点。


因此,在正常情况下,客户端只需要解析名称,连接到解析后的 IP上,然后发现主节点也正在另一边监听链接(也就是客户端连上了主节点)。


考虑这个跨越三个不同数据中心的复制拓扑:



当主节点发生故障时,必须在副本集中选出一个服务器,提升为新的主节点。


orchestrator 将会检测到故障,选举出一个新的主节点,然后重新分配 name(名称)和 VIP(虚拟 IP)。客户端实际上并不知道主节点的真实身份:它们只知道 name(名字),而这个名字现在必须解析给新的主节点。不过,需要考虑:


VIP 是需要协作的:它们由数据库服务器自己声明和拥有。为了获得或释放 VIP,服务器必须发送 ARP 请求。拥有 VIP 的服务器必须在新提升的主节点获得 VIP 之前先释放掉。这还有一些额外的影响:


  • 有秩序的故障转移操作会首先通知故障主节点并要求它释放 VIP,然后再通知新提升的主节点并要求它获取 VIP。如果无法通知到原主节点或者拒绝释放 VIP 怎么办?首先要考虑到,该服务器上存在故障场景,它不可能会不及时响应,或根本不响应。

    • 我们最终可能会出现脑裂情况:两个注解同时声称拥有同一个 VIP。根据最短的网络路径,不同的客户端可能会连接到不同的服务器。

    • 事实源于两个独立服务器间的协作,并且这个设置是不可靠的。

  • 即使原主节点确实配合,工作流程也浪费了宝贵的时间:当我们通知原主节点时,切换到新主节点的操作一直在等待。

  • 即使 VIP 发生变化,现有的客户端连接也不能保证与原服务器断开连接,而且我们可能仍然会经历脑裂。


VIP 受限于物理位置。它们属于交换机或者路由器。所以,我们只能将 VIP 重新分配到位于同一位置的服务器上。特别是,当新提升的服务器位于不同的数据中心时,我们无法分配 VIP,只能修改 DNS。


  • 修改 DNS 需要较长的传播时间。根据配置,客户端会缓存 DNS 一段时间。跨数据中心(cross-DC)故障转移则意味着更多的中断时间:为了让所有客户端知晓新主节点的身份,需要花费更长的时间。


仅这些限制,就足以促使我们寻求新的解决方案,但考虑更多的是:


  • 主节点通过 pt-heartbeat 心跳服务进行自行注入,目的是测量延迟和节流。这项服务必须从新提升的主节点开始。如果有可能的话,原主节点的服务将被关闭。

  • 同样的,Pseudo-GTID 注入也是主节点自己管理的。它将从新的主节点开始,并在原主节点结束。

  • 新的主节点被设为可写。如果可能的话,原主节点被设为只读。


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

该解决方案生效了,GitHub 已经成功完成 MySQL 的故障迁移,但我们希望我们的 HA 在以下方面有所改进:


  • 数据中心不可知

  • 允许数据中心出现故障

  • 删除不可靠的协作工作流

  • 减少总的中断时间

  • 尽可能地进行无损故障转移


GitHub 的高可用解决方案:orchestrator, Consul, GLB


我们的新策略,除了附带的改进外,还解决或减轻了上面的许多问题。在今天的高可用设置中,我们有:


  • 使用 orchestrator 来做监测和故障转移。我们使用跨数据中心的 orchestrator/raft 方案,如下图。

  • 使用 Hashicorp 的 Consul 来做服务发现。

  • 使用 GLB/HAProxy 作为客户端和写节点的代理层。

  • 使用选播(anycast)做网络路由。



新的设置将完全删除 VIP 和 DNS 的修改。在我们引入更多组件的同时,我们能够将组件解耦并简化任务,并且能够使用可靠、稳定的解决方案。下面逐一分析。


正常流程


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


应用程序永远不知道主节点的身份。和之前一样,它们使用名字。例如,cluster1 的主节点命名为 mysql-writer-1.github.net。在我们当前的设置中,名字被解析为一个选播(anycast) IP。


使用选播时,名字在任何地方都被解析为相同的 IP,但流量会根据客户端位置的不同进行路由。需要指出的是,在我们的每个数据中心,都有 GLB(我们的高可用负载均衡)被部署在不同的容器中。指向 mysql-writer-1.github.net 的流量总是路由到本地数据中心的 GLB 集群。因此,所有客户端都由本地代理提供服务。


我们在 HAProxy 上运行 GLB。我们的 HAProxy 维护了一个写连接池:每个 MySQL 集群一个连接池,其中每个连接池只有一个后端服务器:集群的主节点。DC 中的所有 GLB/HAProxy 容器都具有相同的连接池,并且它们都指向相同的后端服务器。这样,如果一个应用程序想要写入 mysql-writer-1.github.net,它连接到哪个 GLB 服务器并不重要。它总会被路由到实际的 cluster1 主节点上。


对于应用程序而言,服务发现结束于 GLB,并且不再需要重新发现。就这样,通过 GLB 将流量路由到正确地址。


GLB 如何知道哪些服务器可以作为后端服务器,以及如何将更改传播到 GBL 呢?


Consul 的服务发现


Consul 是著名的服务发现解决方案,它也提供 DNS 服务。然而,在我们的解决方案中,我们用它作为高效的键值存储系统。


在 Consul 的键值存储中,我们写入了集群主控的标识。对于每一个集群,都有一个键值对记录标识集群的主 FQDN,端口,IPV4,IPV6。


每一个 GLB/HAProxy 节点都运行 consul 模板:每一个服务都在监听 consul 数据的变更(这里主要是对集群主控的数据变更)。consul 模板会生成一个有效的配置文件并且当配置变更时,能够自动重载 HAProxy。


因此,Consul 中主控标识的变更会被每一个 GLB/HAProxy 观察到,然后它们立即重新配置它们自己,在集群后端池中设置新的主控作为单一对象,并且进行重载以反映这些变更。


在 GitHub 中,每一个数据中心都有一个 Consul 设置,并且每一个设置都具有高可用性。然而,这些设置又互相独立,它们之间不进行互相复制或数据共享。


那么 Consul 是如何获得变更通知,在交叉数据中心中,信息又是如何分布的呢?


orchestrator/raft


运行一个 orchestrator/raft 设置:orchestrator 节点之间通过 raft 一致性算法进行通信。每一个数据中心有 1~2 个 orchestrator 节点。


orchestrator 负责失败检测,MySQL 故障转移,以及 Consul 主控的变更通知。故障转移通过单个 orchestrator/raft 主导节点进行操作,但是对于主控变更,产生新主控的消息会通过 raft 机制被传播到所有 orchestrator 节点。


一旦 orchestrator 节点接收到主控变更的消息,它们会与自己对应的本地 Consul 设置通信:它们都执行 KV 写操作。具有多个 orchestrator 节点的数据中心会有多个完全相同的 Consul 写操作。


整体流程


在主节点故障的场景中:


  • orchestrator 节点检测到故障。

  • orchestrator/raft 主导节点开始恢复。一个新的主节点被设置为 promoted 状态。

  • orchestrator/raft 向所有 raft 集群节点通知主节点变更。

  • 所有 orchestrator/raft 成员接收到主节点变更的通知。每个成员都向本地 Consul 写入包含新主节点身份的 KV 记录。

  • 每个 GLB/HAProxy 都运行一个 consul 模版,用于监视 Consul KV 存储的变化,并重新配置和重新加载 HAProxy。

  • 客户端流量被重定向到新的主节点上。


每个组件都有明确的责任归属,而且整个设计简单并且解耦。orchestrator 不需要知道负载均衡。Consul 不需要知道这些信息是从哪里来的。代理只关心 Consul,客户端只关心代理。


而且:


  • 没有 DNS 的变更需要传播。

  • 没有 TTL。

  • 整个流程不需要原故障主节点的配合,它在很大程度上已被忽略。


其他细节


为了进一步确保流程的安全,我们还提供了以下内容:


  • 将 HAProxy 的配置项 hard-stop-after 设置为一个非常短的时间。当在写连接池中使用新的后端服务器重新加载时,它会自动终止所有到原主节点的连接。

    • 通过使用 hard-stop-after 配置项,我们甚至不需要客户端的配合,这也就缓解了脑裂的情况。值得注意的是,这并不是绝对的,我们还是需要一些时间来消灭旧连接。但是,在某个时间点之后,我们就会感到舒服,因为不会出现令人厌恶的意外。

  • 我们并不严格要求 Consul 随时可用。实际上,我们只需要它在故障转移期间可用。如果 Consul 恰好这时不可用,GLB 将继续使用已知的信息运作,不采用任何极端的行动。

  • GLB 被用于验证新提升的主节点的身份。类似于我们的 context-aware MySQL pools(上下文感知的 MySQL 线程池),在后端服务器上进行检查,以确保它确实是一个可写的节点。如果我们恰好在 Consul 中删除了主节点的身份,没有问题;空的条目会被忽略。如果我们在 Consul 中错误的写入了一个非主节点的名称,没有问题;GLB 将拒绝更新它并以最后已知的状态继续运行。


我们会在以下章节进一步完成备受关注和期望的高可用目标。


orchestrator/raft 失败检测


orchestrator使用全面方法来检测失败,因此这种方法非常可靠。我们不会观察到误报 —— 因为我们没有进行过早的故障转移,所以也不会产生不必要的中断时间。


通过完全的 DC 网络隔离(又称 DC 栅栏),orchestrator/raft 进一步处理这个问题。一个 DC 网络隔离会引起一些混淆:这个 DC 中的服务器是可以互相通信的。他们是与其他 DC 网络隔离,还是其他 DC 被网络隔离?


在一个 orchestrator/raft 设置中,raft 的 leader 节点就是运行故障转移的那个节点。leader 是取得了大多数节点支持的节点(特定数量)。我们的 orchestrator 节点部署就是这样,没有单一数据中心可以占大多数,任何 n-1 的 DC 也是如此。


在一个完全 DC 网络隔离的事件中,这个 DC 的 orchestrator 节点与其它 DC 中的对应节点失去连接。最终,隔离 DC 中的 orchestrator 节点不能作为 raft 集群的 leader 节点。如果任何这种节点碰巧成为了 leader 节点,它就会退出。一个新的 leader 节点可以从任何一个其他 DC 分配。leader 节点会得到其他所有 DC 的支持,这些 DC 彼此之间可以进行通信。


因此,调用 shots 的 orchestrator 节点将位于网络隔离数据中心之外。一个隔离 DC 应该有一个主服务器,orchestrator 会使用可用 DC 中的其中一个服务器将它替换来初始化故障转移。我们委托非隔离 DC 中的那些节点来做这个决定,以此来缓解 DC 隔离。


更快的公告


通过发出公告说主分支即将修改,可以进一步减少运行停机的总时间。如何实现这个想法?


当 orchestrator 开始进行故障迁移的时候,它会观察可用于升级的服务器队列。在了解自我复制的规则,以及接受提示和限制的情况下,在最好的行动方针中,它能做出基于一定训练的决策。


它可能意识到一个可以升级的服务器也是一个理想的候选策略,例如:


  • 没有什么可以阻止服务器的升级(潜在用户已经暗示服务器优先升级),而且

  • 认为服务器可以将它所有的版本视为复本。


在这个例子中 orchestrator 首先将服务器设置为可写,然后立即公告服务器的升级(我们的例子中是写到了 Consul KV),即使异步开始修复复制树,这种操作通畅会花费更多几秒的运算。


有可能当我们的 GLB 服务器完全重载时,复制树已经完好无损,但是这不是严格要求的。服务器可以接收到写操作!


半同步复制


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


一致性带来的成本是:可用性风险。如果没有副本确认收到更改,主服务器将被阻塞并且写入操作将停止。幸运的是,这里有一个超时设置,在这之后主服务器可以恢复到异步复制模式,使写入操作再次可用。


我们已经把我们的超时设置在一个合理的低值:500ms。将更改从主服务器发送到本地 DC 副本,通常也发送到远程 DC,这个阈值是绰绰有余的。设置这个超时时间之后,我们可以观察到完美的半同步行为(无需回退到异步复制),并且在确认失败的情况下,可以在非常短的阻塞周期内获得让人满意的表现。


我们在本地 DC 副本上启用半同步,并且在主服务器宕机的情况下,我们期望(尽管不严格地执行)无损故障转移。对完整的 DC 故障进行无损故障转移的代价很高昂,我们并不期待这么做。


在试验半同步超时的同时,我们还观察到一种对我们有利的行为:主服务器在发生故障时,我们能够影响最佳候选人的标识。通过在指定的服务器上启用半同步,并将它们标记为候选服务器,我们可以通过影响故障的结果来减少总的停机时间。在我们的试验中,我们观察到,我们通常最终会得到最佳候选服务器,并因此发布快速公告。


心跳注入


我们没有在已提升/已降级的主设备上管理 pt-heartbeat 服务的启动/关闭,作为替代,我们选择随时随地运行它。这需要进行打一些补丁,以便使 pt-heartbeat 可以支持服务器端来回更改它们的 read_only 状态或其完全崩溃。


在我们目前的设置中,在主服务器及其副本上运行 pt-heartbeat 服务。在主服务器上,他们产生心跳事件。在副本服务器上,他们识别到服务器是只读的,并定期重新检查其状态。只要服务器升级为主服务器,该服务器上的 pt-heartbeat 会将服务器标识为可写,并开始注入心跳事件。


orchestrator 所有权委托


我们进一步委托到 orchestrator:


  • 伪-GTID注入

  • 设置被提升的主控作为可写的,清除它的复写状态

  • 如果可能,设置老的主控为只读状态


对于新主控,以上所有这些操作减少了冲突的可能性。一个刚刚被提升的主控应该是在线的并且可接入,否则我们就不应该提升它。然后让 orchestrator 直接应用变更到被晋升的主控上也应该是合理的。


限制和缺点


代理层使得应用程序不知道主服务器的身份,但是对于主服务器它也掩盖了应用程序的身份。所有主服务器看到的连接都来自代理层,我们丢失了关于连接实际来源的信息。


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


值得注意的是,在数据中心隔离场景中,假设主服务器位于 DC 中,DC 中的应用程序仍然能写入主服务器。一旦网络恢复正常,可能会导致状态不一致。我们正努力在非常独立的 DC 中,通过实现一个可靠的 STONITH 来缓解这种脑裂。和以前一样,在将主服务器之前需要花费一些时间,可能出现短暂的脑裂。而避免脑裂的操作成本非常高。


更多的场景存在:故障转移时 Consul 的终端;部分 DC 隔离;其他的。我们知道,使用这种性质的分布式系统不可能消除所有的漏洞,因此,我们将焦点放在最重要的案例上。


结果


orchestrator/GLB/Consul 设置给我们提供以下功能:


  • 可靠的故障检测

  • 数据中心不可知的故障迁移

  • 典型的无损故障迁移

  • 对数据中心网络隔离的支持

  • 缓解脑裂的问题(仍在实现中)

  • 无合作相关的依赖

  • 多数场景下大约 10~13 秒的断电恢复能力。(我们观察到一些场景下最长 20 秒的断电恢复和极端场景下最长 25 秒的情况)


结语


编排/代理/服务发现范例在解耦架构中使用众所周知的可信组件,这使得部署、运维和观察变得更加容易,并且每个组件可以独立扩展或缩减。我们将不断测试我们的设置,以继续寻求改进。



看完本文有收获?请转发分享给更多人

关注「数据分析与开发」,提升数据技能


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/ZTJPITHVbI
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/20673
 
661 次点击