我们使用 MySQL 作为所有非 git 存储库数据的主要存储,其可用性对于我们的访问操作至关重要。网站本身、我们的 API、身份验证等都需要数据库访问。我们运行多个 MySQL 集群来支持不同的服务和任务。我们的集群使用经典的主从配置,主集群中的一个节点能够接受写入。其余的从属集群节点异步同步来自主服务器的更改并提供数据读取服务。
主服务器的可用性尤为重要。如果没有主服务器,集群就无法接受写入:任何需要保留的写入数据都无法持久化,并且任何传入的更改(例如提交、问题、用户创建、评论、新存储库等)都将失败。
为了支持写入操作,我们显然需要一个可用于写入数据的节点,即主集群。但同样重要的是,我们需要能够识别或找到该节点。
如果写入失败,表明主服务器崩溃,我们必须确保可以快速启动并识别新的主服务器。检测故障、故障转移和宣布新主服务器所需的时间会增加总停机时间。
本文介绍了 MySQL 高可用性和主服务发现解决方案,该解决方案使我们能够可靠地运行跨数据中心操作,容忍数据中心隔离,并在发生故障时最大限度地减少停机时间。
高可用性目标
本文介绍的解决方案是对 之前实现的高可用性(HA)方案进行迭代和改进。随着规模的扩大,MySQL 的高可用性策略也必须随之改变。我们希望为 MySQL 以及 中的其他服务提供类似的高可用性策略。
在考虑高可用性和服务发现时,有一些问题可以指导您找到正确的解决方案。 它们包括但不限于:
您可以容忍多少停机时间?
崩溃检测有多可靠?你能容忍错误报告(过早故障转移)吗?
故障转移有多可靠?在什么情况下它会失败?
该解决方案在跨数据中心场景下如何工作?在低延迟和高延迟网络情况下如何工作?
该解决方案是否可以容忍整个数据中心故障或网络隔离?
是否有任何机制可以防止或减轻裂脑(两台服务器都声称自己是集群的主节点,彼此都不知道对方的存在,并且可以接受写操作)?
您能容忍数据丢失吗?能容忍到什么程度?
为了说明上述内容,我们首先讨论一下以前的高可用性解决方案以及为什么要修改它。
删除基于 VIP 和 DNS 的服务发现
在之前的迭代中,我们:
使用 VIP 和 DNS 发现主节点
在此迭代中,客户端使用名称服务(例如)来发现写入节点。名称解析为指向主节点的虚拟 IP (VIP)。
因此正常情况下,客户端只需要解析名称,连接到解析后的IP,然后发现主节点也在另一边监听连接(即客户端连接到了主节点)。
考虑一下跨越三个不同数据中心的复制拓扑:
当主节点出现故障时,必须从副本集中选择一个服务器并将其提升为新的主节点。
系统会检测到故障,选举新的主服务器,并重新分配名称和 VIP。客户端实际上并不知道主服务器的真实身份:它们只知道名称,现在必须将其解析为新的主服务器。但是,有一些事情需要考虑:
VIP 是协作性的:它们由数据库服务器自己声明和拥有。要获取或释放 VIP,服务器必须发送 ARP 请求。拥有 VIP 的服务器必须先释放它,然后新晋升的主服务器才能获取它。这有一些额外的效果:
有序的故障转移操作会先通知故障的主节点,要求其释放 VIP,然后再通知新晋升的主节点,要求其获取 VIP。如果原来的主节点无法收到通知或者拒绝释放 VIP,该怎么办?首先,假设服务器出现故障场景,它不可能不及时响应或者根本不响应。
我们最终可能会出现脑裂的情况:两个注释同时声明同一个 VIP。根据最短网络路径,不同的客户端可能会连接到不同的服务器。
事实上,这是两个独立服务器之间的协作,这种设置不可靠。
即使原始主服务器确实合作,工作流程也会浪费宝贵的时间:在我们通知原始主服务器时,切换到新主服务器正在等待。
即使 VIP 发生变化,现有的客户端连接也不能保证与原始服务器断开连接,我们仍然可能会遇到脑裂。
VIP 受限于物理位置。它们属于交换机或路由器。因此,我们只能将 VIP 重新分配给同一位置的服务器。特别是,当新升级的服务器位于不同的数据中心时,我们无法分配 VIP,只能修改 DNS。
DNS 更改需要更长的传播时间。客户端会根据配置缓存 DNS 一段时间。跨 DC 故障转移意味着更多的停机时间:所有客户端需要更长的时间来了解新主服务器的身份。
仅这些限制就足以促使我们寻求新的解决方案,但还有更多需要考虑的:
主节点通过 pt- 服务注入自身,用于测量延迟和节流。此服务必须从新晋升的主节点启动。如果可能,原始主节点的服务将被关闭。
同样,-GTID 注入也是由主节点自己管理的,它会从新的主节点开始,到原来的主节点结束。
新的主节点将变为可写的。如果可能,旧的主节点将变为只读的。
这些额外的步骤会增加总停机时间,并引入自身的故障和摩擦。
该解决方案有效并成功实现了 MySQL 故障转移,但我们希望通过以下方式改进我们的 HA:
不受数据中心限制
容忍数据中心故障
消除不可靠的协作工作流程
减少总体停机时间
尽可能进行无损故障转移
高可用性解决方案:GLB
我们的新策略,除了伴随的改进之外,还解决或缓解了上述许多问题。在今天的高可用性设置中,我们拥有:
用它来进行监控和故障转移。我们在跨数据中心使用 /raft 解决方案,如下所示。
用于服务发现。
使用 GLB/ 作为客户端和写入节点的代理层。
使用 () 进行网络路由。
新的设置将完全移除 VIP 和 DNS 修改。随着我们引入更多组件,我们能够解耦组件并简化任务,并使用可靠且稳定的解决方案。让我们逐一分析它们。
正常流程
通常,应用程序通过 GLB/ 连接到写入节点。
应用程序永远不知道主节点的身份。和以前一样,它们使用名称。例如,的主节点名为。在我们当前的设置中,名称解析为 () IP。
使用任播时,名称在任何地方都会解析为相同的 IP,但流量会根据客户端位置以不同的方式路由。值得注意的是,在我们的每个数据中心,都有一个部署在不同容器中的 GLB(我们的高可用性负载均衡器)。定向流量始终路由到本地数据中心的 GLB 集群。因此,所有客户端都由本地代理提供服务。
我们在 MySQL 上运行 GLB。我们维护一个写入连接池:每个 MySQL 集群一个,每个池只有一个后端服务器:集群的主服务器。DC 中的所有 GLB/容器都有相同的连接池,它们都指向同一个后端服务器。这样,如果应用程序想要写入,它连接到哪个 GLB 服务器并不重要。它将始终被路由到实际的主服务器。
对于应用程序来说,服务发现在 GLB 处结束,无需重新发现。流量通过 GLB 路由到正确的地址。
GLB 如何知道哪些服务器可以作为后端服务器,以及它如何将变化传播到 GBL?
服务发现
是一个著名的服务发现解决方案,它也提供 DNS 服务。但在我们的解决方案中,我们将其用作高效的键值存储系统。
在键值存储中,我们写入集群主节点的身份。对于每个集群,都有一个键值对记录,用于标识集群的主 FQDN、端口、IPv4、IPv6。
每个 GLB/节点都运行模板:每个服务都在监听数据变化(这里主要是集群主节点的数据变化)。模板将生成有效的配置文件,并可在配置更改时自动重新加载。
因此,每个 GLB/ 都会观察到主身份的变化,然后立即重新配置自身,将新的主服务器设置为集群后端池中的单例,并重新加载以反映更改。
在 中,每个数据中心都有一个设置,每个设置都具有高可用性。但是,这些设置彼此独立,不会相互复制或共享数据。
那么您如何获得变更通知,以及信息如何在数据中心之间分布?
/筏
运行 /raft setup:节点通过 raft 共识算法相互通信。每个数据中心有 1~2 个节点。
负责故障检测、MySQL 故障转移和主节点变更通知。故障转移由单个/raft 领导节点处理,但对于主节点变更,新主节点的消息会通过 raft 机制传播到所有节点。
节点收到 消息后,会和本地相应的设置进行通信:都进行一次 KV 写操作。一个有多个节点的数据中心,会存在多个相同的写操作。
总体流程
在主节点发生故障的情况下:
节点检测到故障。
/raft 领导节点开始恢复。新的主节点被设置为状态。
/raft 将主节点变化通知所有 raft 集群节点。
所有 /raft 成员都会收到主节点变更的通知。每个成员将包含新主节点身份的 KV 记录写入本地主机。
每个 GLB/ 运行一个模板,监视 KV 存储的变化并重新配置和重新加载它。
客户端流量被重定向到新的主节点。
每个组件职责明确,整个设计简单解耦。不需要知道负载均衡。不需要知道这些信息从哪里来。代理只关心,客户端只关心代理。
和:
没有需要传播的 DNS 更改。
没有 TTL。
整个过程并不需要原来故障主节点的配合,而这部分主节点基本上已经被忽略了。
其他详情
为了进一步确保流程的安全,我们还提供以下服务:
将配置项hard-stop-after设置为很短的时间,当写连接池重新加载新的后端服务器时,会自动终止与原来主节点的所有连接。
通过使用 hard-stop-after 配置选项,我们甚至不需要客户端的配合,从而缓解了裂脑情况。值得注意的是,这并不是绝对的,我们仍然需要一些时间来杀死旧连接。然而,在某个时间点之后,我们会感到安心,因为不会再出现令人讨厌的意外。
我们并不严格要求它始终可用。事实上,我们只需要它在故障转移期间可用。如果它恰好此时不可用,GLB 将使用它所知道的信息继续运行,而不会采取任何激烈的行动。
GLB 用于验证新晋升的主节点的身份。与我们的上下文感知 MySQL 池类似,在后端服务器上进行检查以确保它确实是一个可写节点。如果我们碰巧删除了 中的主节点身份,没问题;空条目将被忽略。如果我们错误地在 中写入了非主节点名称,没问题;GLB 将拒绝更新它并继续在最后已知状态下运行。
我们将在接下来的章节中进一步实现备受期待和期望的高可用性目标。
/raft故障检测
这种方法非常可靠,因为它使用全面的方法来检测故障。我们没有观察到误报,而且由于我们不会过早进行故障转移,因此我们不会产生不必要的停机时间。
/raft 通过提供完整的 DC 网络隔离(又称 DC 隔离)进一步解决了这个问题。DC 网络隔离可能会引起一些混乱:此 DC 中的服务器可以相互通信。它们是与其他 DC 进行网络隔离,还是其他 DC 进行网络隔离?
在 /raft 设置中,raft 节点是运行故障转移的节点。它是拥有多数支持(一定数量)的节点。我们的节点部署使得没有一个数据中心可以拥有多数支持,对于任何 n-1 个 DC 也是如此。
如果 DC 网络完全隔离,则此 DC 中的节点将与其他 DC 中相应的节点失去连接。最终,隔离 DC 中的节点无法作为 Raft 集群中的节点。如果任何此类节点恰好是节点,它将退出。可以从任何其他 DC 分配新节点。该节点将由所有其他可以相互通信的 DC 支持。
因此,负责发号施令的节点将位于网络隔离数据中心之外。隔离 DC 应该有一个主服务器,该服务器将被可用 DC 中的服务器之一替换以启动故障转移。我们将此决定委托给非隔离 DC 中的节点,以减轻 DC 隔离。
更快的公告
通过发布即将修改主分支的公告,可以进一步减少总停机时间。如何实现呢?
当需要启动故障转移时,它会查看可供升级的服务器群。了解自我复制的规则,并接受提示和约束,它可以做出明智的决定,选择最佳行动方案。
它可能会意识到可以升级的服务器也是策略的良好候选者,例如:
没有什么可以阻止服务器升级(潜在用户已经暗示服务器是优先事项),并且
服务器将其所有版本视为副本。
在这个例子中,我们首先将服务器设置为可写,然后立即宣布服务器升级(在我们的例子中是写入 KV)。即使我们异步开始修复复制树,此操作通常也需要几秒钟的计算时间。
当我们的 GLB 服务器完全重新加载时,复制树可能已经完好无损,但这并不是严格要求的。服务器可以接收写入!
半同步复制
在 MySQL 的半同步复制中,主服务器在知道更改已发送到一个或多个副本之前不会确认事务已提交。这提供了一种实现无损故障转移的方法:应用于主服务器的任何更改都将应用或等待应用于其中一个副本。
一致性的代价是:可用性风险。如果没有副本确认收到更改,主服务器将被阻止,写入操作将停止。幸运的是,有一个超时设置,之后主服务器可以恢复到异步复制模式,使写入操作再次可用。
我们将超时设置为一个合理的低值:500 毫秒。此阈值足以将更改从主服务器发送到本地 DC 副本,并且通常也发送到远程 DC。有了这个超时,我们可以观察到完美的半同步行为(无需回退到异步复制),并且在确认失败的情况下以非常短的阻塞期获得令人满意的性能。
我们在本地 DC 副本上启用了半同步,并且我们期望(尽管不是严格执行)在主服务器发生故障时实现无损故障转移。对于完全 DC 故障,无损故障转移的成本很高,这不是我们所期望的。
在试验半同步超时时,我们还观察到一种对我们有利的行为:我们能够在主服务器发生故障时影响最佳候选服务器的识别。通过在指定服务器上启用半同步并将其标记为候选服务器,我们能够通过影响故障结果来减少总体停机时间。在我们的实验中,我们观察到我们通常会得到最佳候选服务器,并因此能够快速发布公告。
心跳注入
我们选择在任意位置运行 pt- 服务,而不是在升级/降级的主服务器上管理 pt- 服务的启动/关闭。这需要进行一些修补,以便 pt- 能够支持服务器来回更改状态或完全崩溃。
在我们当前的设置中,我们在主服务器及其副本上运行 pt- 服务。在主服务器上,它们会生成心跳事件。在副本服务器上,它们会识别出服务器是只读的,并定期重新检查其状态。一旦服务器升级为主服务器,该服务器上的 pt- 就会将该服务器标识为可写,并开始注入心跳事件。
所有权委托
我们进一步委托:
伪 GTID 注入
将提升的主服务器设置为可写,清除其复制状态
如果可能的话,将旧主控设置为只读
以上所有操作都降低了新主服务器发生冲突的可能性。新晋升的主服务器应在线且可访问,否则我们不应将其晋升。然后,将更改直接应用于晋升的主服务器应该是合理的。
局限性和缺点
代理层使应用程序无法感知主服务器的身份,但它也向主服务器隐藏了应用程序的身份。主服务器看到的所有连接都来自代理层,我们无法知道连接实际上来自何处。
随着分布式系统的发展,我们仍然面临一些尚未处理的情况。
值得注意的是,在数据中心隔离场景中,假设主服务器位于 DC 中,则 DC 中的应用程序仍可以写入主服务器。一旦网络恢复,可能会导致状态不一致。我们正在努力通过在非常独立的 DC 中实现可靠的方法来缓解这种裂脑。与以前一样,主服务器恢复需要一些时间,并且可能会出现短暂的裂脑。避免裂脑的运营成本非常高。
还有许多其他场景:故障转移期间的端点;部分 DC 隔离;其他。我们知道,不可能消除这种分布式系统的所有漏洞,因此我们将重点关注最重要的情况。
结果
/GLB/ 设置为我们提供了以下功能:
可靠的故障检测
数据中心无关的故障转移
典型的无损故障转移
支持数据中心网络隔离
缓解脑裂问题(仍在实施中)
无协作相关依赖关系
大多数情况下断电恢复时间约为 10 到 13 秒。(我们观察到,在某些情况下断电恢复时间长达 20 秒,在极端情况下长达 25 秒)
结论
编排/代理/服务发现范例在解耦架构中使用众所周知的可信组件,这使得部署、操作和可观察性更加容易,并且每个组件都可以独立地扩大或缩小。我们将不断测试我们的设置以继续寻求改进。
扫一扫在手机端查看
-
Tags : github mysql cluster 示例
- 上一篇:域名注册价格及续费_【9.11域名商机】终端“中天元集团”近万元收购品牌域名;域名之王两年17万出租域名mens.com;三拼“介绍宝”五位数交易;
- 下一篇:精度等级_为什么Python中的浮点数计算不精确?
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。