第十一章:扩展 MySQL
在个人项目中运行 MySQL,甚至在年轻公司中运行 MySQL,与在市��已经建立并且“呈现指数增长”业务中运行 MySQL 大不相同。在高速业务环境中,流量可能每年增长数倍,环境变得更加复杂,伴随的数据需求迅速增加。扩展 MySQL 与其他类型的服务器大不相同,主要是因为数据的有状态性质。将其与 Web 服务器进行比较,后者的广泛接受的模型是在负载均衡器后面添加更多服务器通常是您需要做的全部。
在本章中,我们将解释什么是扩展,并引导您了解可能需要扩展的不同方面。我们探讨了为什么读取扩展是必不可少的,并向您展示如何安全地实现它,使用诸如排队等策略使写入扩展更可预测。最后,我们涵盖了使用诸如 ProxySQL 和 Vitess 这样的工具对数据集进行分片以扩展写入。通过本章结束时,您应该能够确定系统具有什么季节性模式,如何扩展读取以及如何扩展写入。
什么是扩展?
扩展是系统支持不断增长的流量的能力。系统是否扩展良好或扩展不佳的标准可以通过成本和简单性来衡量。如果增加系统的扩展能力过于昂贵或复杂,您可能会在遇到限制时花费更多精力来解决这个问题。
容量是一个相关概念。系统的容量是它在给定时间内可以执行的工作量。¹ 但是,容量必须加以限定。系统的最大吞吐量与其容量不同。大多数基准测试衡量系统的最大吞吐量,但您不能过度推动真实系统。如果这样做,性能将下降,响应时间将变得不可接受地长且不稳定。我们将系统的实际容量定义为它在仍然提供可接受性能的情况下可以实现的吞吐量。
容量和可扩展性与性能无关。您可以将其比作高速公路上的车辆:
- 系统就是高速公路,上面有所有的车道和车辆。
- 性能是车辆的速度。
- 容量是车道数乘以最大安全速度。
- 可扩展性是您可以增加更多车辆和更多车道而不会减慢交通的程度。
在这个类比中,可扩展性取决于诸如立交设计的优良程度、有多少车辆发生事故或抛锚、车辆是否以不同的速度行驶或频繁变道等因素,但通常,可扩展性并不取决于车辆引擎的强大程度。这并不是说性能不重要,因为它确实重要。我们只是指出,即使系统性能不高,系统也可以具有可扩展性。
从 50,000 英尺高度来看,可扩展性是通过增加资源来增加容量的能力。
即使您的 MySQL 架构是可扩展的,您的应用程序可能并非如此。如果由于任何原因增加容量很困难,那么您的应用程序整体上就不具备可扩展性。我们先前用吞吐量来定义容量,但从同样的 50,000 英尺高度来看容量也是值得一提的。从这个角度来看,容量简单地意味着处理负载的能力,从几个不同的角度来看负载是有用的:
数据量
您的应用程序可以累积的数据量是最常见的扩展挑战之一。这对今天许多 Web 应用程序来说尤为重要,这些应用程序从不删除任何数据。例如,社交网络网站通常不会删除旧消息或评论。
用户数量
即使每个用户只有少量数据,如果用户数量很多,数据量会累积起来——而且数据量可能比用户数量增长更快。许多用户通常意味着更多的交易,而交易数量可能与用户数量不成比例。最后,许多用户(和更多数据)可能意味着越来越复杂的查询,特别是如果查询依赖于用户之间的关系数量。 (关系数量受到限制,为(N × (N – 1)) / 2,其中N是用户数量。)
用户活动
并非所有用户活动都是相同的,用户活动也不是恒定的。如果你的用户突然变得更活跃——例如因为他们喜欢的新功能——你的负载可能会显著增加。用户活动不仅仅是页面浏览的数量。即使页面浏览的数量相同,如果需要大量工作才能生成的站点部分变得更受欢迎,那么同样数量的页面浏览可能会导致更多的工作。一些用户比其他用户更活跃:他们可能比普通用户拥有更多的朋友、消息或照片。
相关数据集的大小
如果用户之间存在关系,应用程序可能需要在整个相关用户组上运行查询和计算。这比仅仅处理个别用户及其数据更复杂。社交网络网站经常面临由于拥有许多朋友的热门群体或用户而带来的挑战。
扩展挑战可能以多种形式出现。在下一节中,我们将讨论如何确定瓶颈所在以及如何解决。
读取受限与写入受限的工作负载
在考虑扩展数据库架构时,你应该首先检查的是你是在扩展读取受限的工作负载还是写入受限的工作负载。读取受限的工作负载是指读取流量(SELECT
)超过了服务器容量的情况。写入受限的工作负载超过了服务器提供 DML(INSERT
,UPDATE
,DELETE
)的能力。了解你所面临的情况涉及了解你的工作负载。
理解你的工作负载
数据库工作负载包括很多方面。首先,它是你的容量,或者正如我们之前提到的,是单位时间内的工作量。对于数据库来说,这通常归结为每秒查询数。工作负载的一个定义可能是系统可以执行多少 QPS。然而,不要被这个迷惑。20%的 CPU 下的一千个 QPS 并不总是意味着你可以再增加四千个 QPS。并非每个查询都是相同的。
查询有各种形式:读取、写入、主键查找、子查询、连接、批量插入等等。每种查询都有与之相关的成本。这个成本以 CPU 时间或延迟来衡量。当一个查询在磁盘上等待返回信息时,这段时间会增加成本。² 了解你的资源容量是很重要的。你有多少个 CPU,你的磁盘的读取和写入 IOPS 和吞吐量限制是多少,你的网络吞吐量是多少?每个因素都会对延迟产生影响,而延迟直接关系到你的工作负载。
工作负载是所有类型查询及其延迟的混合。更公平的说法是,如果我们在 20%的 CPU 上处理一千个 QPS,我们可以再增加四千个 QPS,只要它们的延迟相同。³ 如果我们引入四千个额外的查询并且遇到磁盘 IOPS 瓶颈,所有读取的延迟都会增加。
如果您只能访问基本系统指标,如 CPU、内存和磁盘,几乎不可能理解您正在受到哪些限制。您需要确定您的读取与写入性能如何。我们在“检查读取与写入性能”中提供了一个示例,在第三章中。使用该示例,您可以确定读取与写入的延迟。如果您随时间趋势这些数字,您可以看到您的读取或写入延迟是否增加,因此您可能受到限制的地方。
读取限制工作负载
假设,在设计产品时,您采用了一个源主机用于所有数据库流量的捷径。增加更多应用节点可能会扩展为客户端提供请求,但最终将受到您的单一源数据库主机响应这些读取请求的能力的限制。这的主要指标是 CPU 利用率。高 CPU 意味着服务器花费所有时间处理查询。CPU 利用率越高,您在查询中看到的延迟就越多。然而,这并不是唯一的指标。您还可以看到大量的磁盘读取 IOPS 或吞吐量,表明您经常访问磁盘或从磁盘读取大量行。
通过添加索引、优化查询和缓存可缓存的数据,您可以最初改善这一点。一旦您没有更多的改进空间,您将面临一个读取限制的工作负载,这就是使用副本扩展读取流量的时候。我们将在本章后面讨论如何使用读取副本池扩展您的读取量,如何为这些池运行健康检查,以及在开始使用该架构时要避免的陷阱。
写入限制工作负载
您可能也遇到了写入限制的负载。以下是一些写入限制数据库负载的示例:
- 可能注册人数正在呈指数增长。
- 现在是高峰电子商务季节,销售额正在增长,订单数量也在增加。
- 现在是选举季节,您有很多竞选通讯要发出。
所有这些都是导致更多数据库写入的业务用例,现在您必须扩展。再次强调,即使您可以在一段时间内垂直扩展单一源数据库,但只能走得这么远。当瓶颈是写入量时,您必须开始考虑如何拆分数据,以便可以在不同的子集上并行接受写入。我们将在本章后面讨论如何为写入扩展而进行分片。
在这一点上,问一下“如果我看到两种类型的增长怎么办?”是很合理的。重要的是仔细检查您的模式,并确定是否有一组表在读取方面增长得比另一组表在写入需求方面增长得更快。尝试同时为两者扩展数据库集群会带来很多痛苦和事件。我们建议将表分开放入不同的功能集群中,以独立扩展读取和写入;这是更有效地扩展读取流量的先决条件。
现在您已经确定了您的负载是读取限制还是写入限制,我们将讨论如何以有效的方式帮助引导数据的功能拆分。
功能分片
根据业务中的“功能”拆分数据是一个需要深入了解数据的上下文密集型任务。这与流行的软件架构范式如面向服务的架构(SOA)和微服务紧密相关。并非所有功能方法都是平等的,在一个夸张的例子中,如果您将每个表放入自己的“功能”数据库中,您可能会通过过多的碎片化使一切变得更糟。
您如何处理将大型单体/混合关注点数据库拆分为一组合理的较小集群,以帮助业务扩展?以下是一些需要牢记的指导原则:
- 不要根据工程团队的结构进行拆分。这种情况总会在某个时候发生变化。
- 根据业务功能拆分表格。用于账户注册的表格可以与用于托管现有客户设置的表格分开,用于支持新功能的表格应该从其自己的数据库开始。
- 不要回避处理数据中混合了不同业务关注点的地方,并且您需要倡导不仅进行数据分离,还要进行应用重构,并在这些边界之间引入 API 访问。我们见过的一个常见例子是将客户身份与客户账单混合在一起。
起初,会有一些明显具有自己业务功能和访问模式的表格,因此很容易将其拆分到一个单独的集群中,但随着进展,这种分离会变得更加微妙。
现在我们已经根据业务功能以周到的方式拆分了数据,让我们谈谈如何使用副本读取池来扩展读取负载。
使用只读池扩展读取
集群中的副本可以担任多个目的。首先,当当前源需要出于任何原因停止服务时,它们是写入故障转移的候选者,无论是计划的还是非计划的。但由于这些副本也在不断运行更新以匹配源中的数据,您也可以使用它们来提供读取请求。
在图 11-1 中,我们首先看一下具有只读副本池的新设置的可视化效果。
图 11-1. 应用节点使用虚拟 IP 访问只读副本
为了简单起见,我们将假装应用节点仍通过直接连接到源数据库来完成写请求。稍后我们将讨论如何更好地连接到源节点。请注意,尽管如此,相同的应用节点连接到一个虚拟 IP,该虚拟 IP 充当它们与只读副本之间的中间层。这是一个副本读取池,这是如何将不断增长的读取负载分散到多个主机的方法。您可能还注意到,并非所有副本都在池中。这是一种常见的方法,用于防止不同的读取工作负载相互影响。如果您的报告流程或备份流程倾向于消耗所有磁盘 I/O 资源并导致复制延迟,您可以略过一个或多个副本节点来执行这些任务,并将其排除在为客户端流量提供服务的读取池之外。或者,您可以通过将复制检查与负载均衡器健康检查相结合,自动将落后的备份节点从池中移除,并在其赶上时重新引入。当应用程序与一个用于读取的单一节点通信,并且您可以无缝管理这些资源而不影响客户时,将您的只读副本转换为可互换资源的灵活性将显著增加。
现在有多个数据库主机用于提供读取请求,对于顺利的生产运行,有一些事项需要考虑:
- 如何将流量路由到所有这些只读副本?
- 如何均匀分配负载?
- 如何运行健康检查并移除不健康或滞后的副本,以避免提供陈旧数据?
- 如何避免意外删除所有节点,导致应用流量受到更大的损害?
- 如何主动地为维护目的手动移除服务器?
- 如何将新配置的服务器添加到负载均衡器?
- 有哪些自动化检查措施可以避免在新配置的节点准备就绪之前将其添加到负载均衡器?
- 你对“准备好接受新节点”的定义是否足够具体?
管理这些读取池的一种非常常见的方式是使用负载均衡器运行一个充当所有流向读取副本的流量的中间人的虚拟 IP。执行此操作的技术包括 HAProxy,如果您自己托管,则为硬件负载均衡器,如果在公共云环境中运行,则为网络负载均衡器。在使用 HAProxy 的情况下,所有应用主机将连接到那个“前端”,而 HAProxy 负责将这些请求定向到后端定义的多个读取副本之一。以下是一个定义虚拟 IP 前端并将其映射到多个读取副本作为后端池的示例 HAProxy 配置文件:
global
log 127.0.0.1 local0 notice
user haproxy
group haproxy
defaults
log global
retries 2
timeout connect 3000
timeout server 5000
timeout client 5000
listen mysql-readpool
bind 127.0.0.1:3306
mode tcp
option mysql-check user haproxy_check
balance leastconn
server mysql-1 10.0.0.1:3306 check
server mysql-2 10.0.0.2:3306 check
通常,您使用配置管理自动填充此类文件。在此配置中有几点需要注意。在 MySQL 中,使用leastconn在池节点之间进行负载均衡是推荐的方式。在负载升高时使用roundrobin等随机负载均衡方式将无法帮助您使用未过载的主机。确保在 MySQL 实例上创建了适当的数据库用户来运行此健康检查,否则所有节点都将被标记为不健康。
促进分片的工具,如 Vitess 和 ProxySQL,也可以充当负载均衡器。我们将在本章末讨论这些工具。
为读取池管理配置
现在您在应用节点和副本之间有了一个“门”,您需要一种方法来轻松管理包含或不包含在此读取池中的节点,使用您选择的负载均衡器。您不希望这是一个手动管理的配置。您已经在扩展到大量数据库实例的轨道上,手动管理配置文件将导致错误、响应时间变慢、主机故障,并且根本无法扩展。
服务发现是一个很好的选择,可以自动发现可以在此列表中的主机。这可能意味着将服务发现解决方案部署为技术堆栈的一部分,或者依赖于云提供商提供的托管服务发现选项(如果有的话)。在这里要小心的重要事项是非常明确地指出使读取副本有资格进入此读取池的标准。理想情况下,您应该排除源节点和可能专门用于报告的一个或多个副本。但也许您需要更复杂的东西,其中副本进一步分段以服务不同的应用程序读取负载?我们建议每个池中至少有三个节点的副本,除了您的备份/报告服务器和源节点。
无论您是自己运行服务发现⁴还是使用云提供商提供的服务,您都应该了解该服务的保证。以下是一些需要考虑的事项,无论您是运行服务发现还是与团队合作:
- 它有多快能检测到主机的故障?
- 数据传播速度有多快?
- 当数据库实例发生故障时,负载均衡器上的配置会如何刷新?
- 数据库成员的更改是作为后台进程进行,还是需要中断现有连接?
- 如果服务发现本身出现故障会发生什么?这会影响任何新的数据库连接还是只会影响更改负载均衡器成员资格?在那时,您可以手动进行更改吗?
灵活性带来复杂性,您必须在生产中平衡两者以获得最佳结果。您的工作是始终将决策与正在追求的 SLI 和 SLO 联系在一起,而不是实现神话般的 100%的正常运行时间目标。
现在您知道如何填充配置并在主机进出时更新它们,现在是时候讨论如何为副本读取池的成员运行健康检查了。
读取池的健康检查
在这一点上,您需要考虑什么标准可以认为读副本是健康的并准备好接受应用程序的读取流量。这些标准可以简单到“数据库进程正在运行,端口响应”,也可以变得更加复杂,比如“数据库正在运行,复制滞后不超过 30 秒,读查询运行的延迟不高于 100 毫秒”。
提示
检查变量read_only
和super_read_only
的状态,以确保负载均衡器的读取池中的所有成员实际上都是副本。
决定进行到何种程度的健康检查应该是与您的应用程序开发团队进行讨论的一个过程,以便每个人都了解并对他们在从数据库读取时期望的行为达成一致。以下是一些可以帮助引导这一决策过程的团队提出的问题:
- 可接受多少数据陈旧?如果返回的数据几分钟前的,会有什么影响?
- 应用程序的最大可接受查询延迟是多少?
- 读查询是否存在任何重试逻辑,如果存在,是否是指数退避?
- 我们是否已经为应用程序设定了 SLO?该 SLO 是否延伸到查询延迟或仅涉及正常运行时间?
- 在没有这些数据的情况下,系统会如何表现?这种退化是否可接受?如果是,那么持续多久?
在许多情况下,您只需使用端口检查即可确认 MySQL 进程正在运行且可以接受连接。这意味着只要数据库在运行,它将成为该池的一部分并提供服务请求。
然而,有时您可能需要更复杂的东西,因为涉及的数据集足够关键,您不希望在复制滞后超过几秒或根本没有运行复制时提供服务。对于这些情况,您仍然可以使用读取池,但可以通过 HTTP 检查来增强健康检查。其工作原理是您选择的负载均衡器将运行一个命令(通常是一个脚本),并根据响应代码确定节点是否健康。例如,在 HAProxy 中,后端将具有类似以下代码行:
option httpchk GET /check-lag
这一行意味着对于读取池中的每个主机,负载均衡器将使用GET
调用调用路径/check-lag
并检查响应代码。该路径运行一个脚本,其中包含有关可接受的滞后程度的逻辑。该脚本将现有的滞后状态与该阈值进行比较,并根据情况,负载均衡器将考虑副本是否健康。
警告
即使健康检查是一个强大的工具,也要小心使用那些具有复杂逻辑的(比如之前描述的滞后检查),并确保你有一个计划,以防池中的所有副本都未通过健康检查。您可以拥有一个静态的“备用”池,用于某些全局故障(例如,整个集群滞后),以避免意外破坏所有读取请求。
选择负载均衡算法
有许多不同的算法来确定哪个服务器应该接收下一个连接。每个供应商使用不同的术语,但这个列表应该提供了一个可用的想法:
随机
负载均衡器将每个请求定向到从可用服务器池中随机选择的服务器。
轮询
负载均衡器将请求发送到服务器的重复序列:A、B、C、A、B、C,依此类推。
最少连接
下一个连接将发送到活动连接最少的服务器。
最快响应
处理请求速度最快的服务器将接收下一个连接。当池中包含快速和慢速机器时,这种方法可以很好地运作。然而,在 SQL 中,当查询复杂性差异很大时,这就变得非常棘手。即使是相同的查询在不同情况下表现也会有很大差异,比如当它从查询缓存中提取时,或者当服务器的缓存已经包含所需数据时。
哈希
负载均衡器对连接的源 IP 地址进行哈希处理,将其映射到池中的一个服务器。每当来自相同 IP 地址的连接请求时,负载均衡器都会将其发送到同一台服务器。只有当池中的机器数量发生变化时,绑定才会更改。
加权
负载均衡器可以结合并增加其他算法的权重。例如,您可能有单 CPU 和双 CPU 的机器。双 CPU 的机器大约是单 CPU 的两倍强大,因此您可以告诉负载均衡器向它们发送大约两倍数量的请求。
对于 MySQL 来说,最佳算法取决于您的工作负载。例如,最小连接算法可能会在将新服务器添加到可用服务器池之前,使新服务器过载。
您需要进行实验,找到最适合您工作负载的性能。一定要考虑在特殊情况下发生的情况,以及日常规范下会发生的情况。在那些特殊情况下,例如在高查询负载时,进行模式更改时,或者在异常数量的服务器下线时,您最不希望出现严重问题。
我们这里只描述了即时配置算法,不排队连接请求。有时使用排队的算法可能更有效。例如,一个算法可能在数据库服务器上维持给定的并发性,比如同时允许不超过N个活动事务。如果有太多活动事务,算法可以将新请求放入队列,并从符合条件的第一个“可用”服务器提供服务。一些连接池支持排队算法。
现在我们已经讨论了如何扩展读取负载以及如何进行健康检查,是时候讨论扩展写入了。在直接寻找如何扩展写入之前,您可以查看排队可以使写入流量增长更可控的地方。让我们讨论一下排队如何帮助扩展您的写入性能。
排队
当使用一个偏向一致性而非可用性的数据存储来扩展写事务时,应用层的扩展变得更加复杂。更多的应用节点写入一个源节点将导致数据库系统更容易受到锁超时、死锁和失败写入的影响,必须重试。所有这些最终将导致面向客户的错误或不可接受的延迟。
在研究接下来我们将讨论的数据分片之前,您应该检查数据中的写入热点,并考虑是否所有写入都真正需要主动持久化到数据库。一些写入可以放入队列,并在可接受的时间范围内写入数据库吗?
假设您有一个存储大量客户历史数据的数据库。客户偶尔发送 API 请求来检索这些数据,但您还需要支持一个用于删除这些数据的 API。您可以合理地从越来越多的副本中提供读取 API 调用,但删除呢?HTTP RFC 允许一个响应代码,“202 Accepted”。您可以返回该代码,将请求放入队列(例如,Apache Kafka 或 Amazon Simple Queue Service),并以不会直接导致数据库过载的速度处理这些请求。
这显然不同于 200 响应代码,它意味着请求已经立即完成。在这种情况下,与产品团队进行协商对于使 API 的保证变得可信和可实现至关重要。200 和 202 响应代码之间的区别在于分片这些数据以支持更多并行写入的所有工程工作。
如果你将排队应用于写入负载,一个重要的设计选择是提前确定这些调用在放入队列后预期完成的时间范围。监控请求在队列中花费的时间的增长将是你确定这种策略何时已经到头,你真的需要开始分割这个数据集以支持更多并行写入负载的指标。你可以通过分片来实现这一点,接下��我们将讨论。
使用分片扩展写入
如果无法通过优化查询和排队写入来管理写入流量的增长,那么分片是你的下一个选择。
分片意味着将数据分割成不同的、更小的数据库集群,这样你就可以同时在更多源主机上执行更多写操作。你可以进行两种不同类型的分片或分区:功能分区和数据分片。
功能分区,或者任务划分,意味着将不同的节点专门用于不同的任务。一个例子可能是将用户记录放在一个集群中,将他们的账单放在另一个集群中。这种方法允许每个集群独立扩展。用户注册激增可能会给用户集群带来压力。有了独立的系统,你的账单集群负载较轻,可以为客户开具账单。相反,如果你的账单周期是每月的第一天,你可以运行它,而不会影响用户注册。
数据分片是当今扩展非常大型 MySQL 应用程序的最常见和成功的方法。你通过将数据分割成更小的片段或分片,并将它们存储在不同的节点上来进行分片。
大多数应用程序只对需要分片的数据进行分片——通常是数据集中将会增长非常大的部分。假设你正在构建一个博客服务。如果你预计有 1000 万用户,你可能不需要对用户注册信息进行分片,因为你可能能够完全将所有用户(或其中的活跃子集)存储在内存中。另一方面,如果你预计有 5 亿用户,你可能应该对这些数据进行分片。用户生成的内容,如帖子和评论,在任何情况下几乎肯定需要分片,因为这些记录更大,而且数量更多。
大型应用程序可能有几个逻辑数据集,你可以以不同的方式进行分片。你可以将它们存储在不同的服务器集上,但不一定要这样做。你还可以以不同的方式对同一数据进行分片,具体取决于你如何访问它。
在计划“只分片需要分片的内容”时要小心。这个概念不仅需要包括增长迅速的数据,还需要包括逻辑上属于它的数据,并且将经常同时查询。如果你根据user_id
字段进行分片,但有一组其他小表在大多数查询中与该user_id
进行连接,那么将这些表一起分片是有意义的,这样你可以一次只对一个分片进行大多数应用查询,避免跨数据库连接。
选择分区方案
分片的最重要挑战是查找和检索数据。你如何查找数据取决于你如何分片。有许多方法可以做到这一点,有些方法比其他方法更好。
目标是使您最重要和频繁的查询尽可能少地触及分片(记住,可扩展性原则之一是避免节点之间的交叉通信)。该过程中最关键的部分是选择数据的分区键(或键)。分区键确定应将哪些行放入每个分片。如果您知道对象的分区键,您可以回答两个问题:
- 我应该将这些数据存储在哪里?
- 我可以在哪里找到我需要获取的数据?
我们稍后将展示选择和使用分区键的各种方法。现在,让我们看一个例子。假设我们像 MySQL 的 NDB 集群一样,使用每个表主键的哈希来将数据分区到所有分片中。这是一个非常简单的方法,但不适合扩展,因为它经常需要您检查所有分片以获取所需数据。例如,如果您想要用户 3 的博客文章,您可以在哪里找到它们?它们可能均匀分布在所有分片中,因为它们是按主键分区的,而不是按用户分区的。使用主键哈希使得知道存储数据的位置变得简单,但根据您需要的数据和是否知道主键,可能会使获取数据变得更困难。
您总是希望将查询局限在一个分片中。在水平分片数据时,您希望始终避免跨分片查询以完成任务。在跨分片连接数据会增加应用程序层的复杂性,并消耗分片数据的好处。分片数据集的最坏情况是当您不知道所需数据存储在哪里,因此需要扫描每个分片才能找到它。
一个好的分区键通常是数据库中一个非常重要实体的主键。这些键确定了分片的单位。例如,如果您按用户 ID 或客户 ID 对数据进行分区,那么分片的单位就是用户或客户。
一个好的开始方法是使用实体关系图或显示所有实体及其关系的等效工具绘制数据模型图。尝试布置图表,使相关实体彼此靠近。您通常可以通过视觉检查这样的图表,并找到否则会错过的分区键候选项。但不要只看图表;还要考虑您应用程序的查询。即使两个实体在某种程度上相关,如果您很少或从不在关系上进行连接,您可以打破关系以实现分片。
一些数据模型比其他数据模型更容易分片,这取决于实体关系图中的连接程度。图 11-2 展示了左侧易于分片的数据模型和右侧难以分片的数据模型。
图 11-2. 两个数据模型,一个易于分片,另一��难以分片⁵
左侧的数据模型易于分片,因为它有许多连接的子图,主要由只有一个连接的节点组成,您可以相对容易地“切断”子图之间的连接。右侧的模型难以分片,因为没有这样的子图。幸运的是,大多数数据模型更像左侧图表而不是右侧图表。
在选择分区键时,尽量选择一些可以尽可能避免跨片查询的内容,但也要使分片足够小,以免出现数据不均匀的问题。你希望分片最终变得均匀小,如果可能的话,如果不行,至少要足够小,以便通过将不同数量的分片组合在一起来平衡。例如,如果你的应用程序仅限于美国,你想将数据集分成 20 个分片,你可能不应该按州进行分片,因为加利福尼亚州人口太多。但你可以按县或电话区号进行分片,因为尽管它们的人口不均匀,但它们足够多,以至于你仍然可以选择 20 组,总体上人口大致相等,并且你可以选择它们以避免跨片查询。
多个分区键
复杂的数据模型使数据分片更加困难。许多应用程序有多个分区键,特别是如果数据中有两个或更多重要的“维度”。换句话说,应用程序可能需要从不同角度高效、连贯地查看数据。这意味着你可能需要在系统内至少存储一些数据两次。
例如,你可能需要按照用户 ID 和帖子 ID 对博客应用程序的数据进行分片,因为这是应用程序查看数据的两种常见方式。想象一下:你经常想看到某个用户的所有帖子和某个帖子的所有评论。按用户分片无法帮助你找到帖子的评论,按帖子分片无法帮助你找到用户的帖子。如果你需要让这两种类型的查询仅涉及单个分片,那么你将需要双向分片。
仅仅因为你需要多个分区键,并不意味着你需要设计两个完全冗余的数据存储。让我们看另一个例子:一个社交网络读书俱乐部网站,用户可以在该网站上评论书籍。该网站可以显示一本书的所有评论,以及用户已阅读并评论的所有书籍。
你可以为用户数据构建一个分片数据存储,为书籍数据构建另一个。评论既有用户 ID 又有帖子 ID,因此它们跨越分片之间的边界。你可以将评论与用户数据一起存储,而只需将评论的标题和 ID 与书籍数据一起存储。这可能足以在不访问两个数据存储的情况下呈现大多数书籍评论的视图,如果需要显示完整的评论文本,可以从用户数据存储中检索。
跨片查询
大多数分片应用程序至少有一些需要从多个分片聚合或��接数据的查询。例如,如果读书俱乐部网站显示最受欢迎或活跃的用户,它必须根据定义访问每个分片。使这样的查询正常工作是实现数据分片最困难的部分,因为应用程序将一个查询视为单个查询,需要将其拆分并并行执行多个查询,每个查询对应一个分片。一个良好的数据库抽象层可以帮助减轻痛苦,但即使如此,这样的查询比分片内查询慢得多,成本更高,通常也需要积极的缓存。
如果你选择的分片方案使跨片查询成为异常而不是规范,那么你将知道你选择的分片方案是一个好的选择。你应该努力使你的查询尽可能简单,并且包含在一个分片中。对于那些需要一些跨片聚合的情况,我们建议将其作为应用程序逻辑的一部分。
跨片查询也可以从摘要表中受益。你可以通过遍历所有分片并在每个分片上存储结果的冗余数据来构建它们。如果在每个分片上复制数据太浪费,你可以将摘要表合并到另一个数据存储中,这样它们只存储一次。
非分片数据通常存储在全局节点中,并进行大量缓存以保护免受负载影响。
一些应用程序基本上使用随机分片,其中一致的数据分布很重要,或者当没有很好的分区键时。分布式搜索应用程序是一个很好的例子。在这种情况下,跨分片查询和聚合是常规操作,而不是例外。
在分片中,跨分片查询并不是唯一困难的事情。保持数据一致性也是困难的。跨分片的外键不起作用,因此正常解决方案是根据需要在应用程序中检查引用完整性或在分片内部使用外键,因为分片内部的一致性可能是最重要的事情。虽然可以使用XA 事务,但这在实践中并不常见,因为会增加开销。
您还可以设计定期运行的清理流程。例如,如果用户的读书俱乐部账户过期,您不必立即删除它。您可以编写一个定期作业,从每本书的分片中删除用户的评论,并构建一个定期运行的检查脚本,确保数据在分片之间保持一致。
现在我们已经解释了如何将数据分割到多个集群以及如何选择分区键的不同方式,让我们来介绍两种最受欢迎的开源工具,可以帮助促进分片和分区。
Vitess
Vitess 是用于 MySQL 的数据库集群系统。它起源于 YouTube,然后成为 PlanetScale,由 Jiten Vaidya 和 Sugu Sougoumarane 共同创立的一个独立产品和公司。
Vitess 提供了许多功能:
- 支持水平分片,包括对数据进行分片
- 拓扑管理
- 源节点故障转移管理
- 模式更改管理
- 连接池
- 查询重写
让我们探索 Vitess 的架构及其组件。
Vitess 架构概述
图 11-3 是来自 Vitess 网站的图表,展示了其架构的不同部分。
图 11-3. Vitess 架构图(改编自 vitess.io)
以下是一些你需要了解的术语:
Vitess pod
一组数据库的一般封装以及支持分片、拓扑管理、模式更改管理和应用程序访问这些数据库的 Vitess 相关部分。
VTGate
控制应用程序和运维人员访问数据库实例的服务,用于管理拓扑结构、添加节点或对部分数据进行分片。这类似于之前描述的架构中的负载均衡器。
VTTablet
在 Vitess 管理的每个数据库实例上运行的代理。它可以接收来自运维人员的数据库管理命令,并代表运维人员执行这些命令。
拓扑(元数据存储)
在给定 pod 中保存由 Vitess 管理的数据库实例的库存以及相关信息。
vtctl
用于对 Vitess pod 进行操作更改的命令行工具。
vtctld
用于相同管理操作的图形界面。
Vitess 的架构始于一个一致的拓扑存储,其中保存了所有集群、MySQL 实例和 vtgate 实例的定义。这个一致的元数据存储在管理拓扑变化中发挥着至关重要的作用。当运维人员想要对 Vitess 管理的集群的拓扑进行更改时,实际上是通过一个名为 vtctl 的服务向数据存储发送命令,然后将该命令的组件操作发送给 vtgate。
Vitess 提供了可以在 Kubernetes 中部署 vtgate 层和元数据存储的数据库运维人员。在像 Kubernetes 这样的平台中拥有其控制平面可以增加其对单点故障的弹性。
Vitess 最大的优势之一是其关于如何扩展 MySQL 的理念,其中包括以下内容:
偏好使用较小的实例
按功能、水平或两者分割您的数据。但是当发生故障时,较小的实例会导致较小的爆炸半径。
复制和自动写入故障转移以增加弹性
Vitess 不通过多写节点技巧承诺“100%在线写入”。相反,它自动化写入故障转移,并在故障转移期间管理拓扑变化和应用程序对数据库节点的访问,以使写入停机时间尽可能短。
使用半同步复制确保持久性
Vitess 强烈推荐使用半同步复制(与默认的异步相对)来确保在向应用程序确认写入之前,写入始终由数据库层中的多个节点持久化。这是一种以延迟为代价换取持久性保证的关键权衡,当 Vitess 需要在非计划方式下故障转移写入主机时,这种权衡会产生回报。
这些架构原则可以帮助您在业务流量呈指数增长时在基础设施的数据库层具有更多的弹性。无论您是否专门使用 Vitess 或其他解决方案作为架构的一部分,您都应该遵循这些最佳实践。
将您的堆栈迁移到 Vitess
Vitess 是一个用于运行数据库层的有主见的平台,而不是一个即插即用的解决方案。因此,在您将其作为数据库访问层之前,您需要深思熟虑地计划如何实施这样的过渡。
具体而言,在评估 Vitess 作为可能解决方案时,请务必考虑以下迁移步骤:
1. 测试并记录您为整个系统引入的延迟。
将像 Vitess 这样复杂的堆栈引入应用程序堆栈肯定会增加一定量的延迟,特别是考虑到半同步复制的执行。确保这种权衡得到充分记录和明确沟通,以便您的下游依赖在构建依赖于这种数据库架构的 SLO 时做出知情决策。
2. 使用金丝雀部署模型。
在生产中过渡期间,您可以将vttablet配置为“外部管理”。这允许vttablet和直接连接到数据库服务器,随着您逐渐通过应用程序节点群增加连接更改。
3. 开始分片。
一旦所有应用层访问都通过vtgate/vttablet而不是直接访问 MySQL,您就可以开始使用 Vitess 的完整功能集来将表拆分到新的集群中,将数据水平分片以获得更多的写入吞吐量,或者仅仅添加副本以获得更多的读取负载能力。⁶
Vitess 是一个强大的数据库访问和管理产品,它已经从早期在谷歌的日子里走过了很长的路。它已经证明了它能够实现戏剧性的增长和一个弹性的数据库基础设施。然而,这种强大和灵活性是以增加复杂性为代价的。Vitess 不像一个简单的负载均衡器通过流量,你应该权衡业务需求和引入和维护像 Vitess 这样复杂的数据库管理工具的成本。
ProxySQL
ProxySQL 专门为 MySQL 协议编写,并以 GPL 发布。René Cannaò,一个为许多公司提供咨询的 DBA 和长期的 MySQL 贡献者,是主要作者。现在它是一个提供 ProxySQL 产品付费支持和开发合同的全功能公司。
让我们深入了解一些关于其架构、配置模式、用例和功能的细节。
ProxySQL 架构概述
您可以将 ProxySQL 用作任何应用程序代码和 MySQL 实例之间的中间层。ProxySQL 为应用程序提供了一个基于会话的、基于 MySQL 协议的接口,用于与数据库交互。代替应用程序直接打开连接到数据库实例,ProxySQL 代表应用程序打开连接。
这种设计使代理对应用程序节点看起来是不可见的。其会话感知性允许在没有停机的情况下在 MySQL 实例之间移动这些连接。当您处理不再投资于的应用程序时,这尤其有用,因为您现在可以利用 ProxySQL 中的功能而无需对您可能不确定更改的代码进行任何更改。
ProxySQL 还提供强大的连接池。应用程序打开到 ProxySQL 的连接与 ProxySQL 打开到配置连接的数据库实例的连接是分开的。这种分离可以保护数据库实例免受应用层突发流量的影响。
当您有能力单独管理客户端连接与实际连接到数据库的连接数量时,您引入了以前没有的灵活性。现在您可以扩展应用程序节点池,而无需担心它会增加到数据库的连接负载超出您想要支持的范围。这允许在使用 ProxySQL 时解释常见模式时,应用程序和业务需求的多样化场景。
配置 ProxySQL
ProxySQL 在启动时使用配置文件,但在内存中和嵌入式 SQLite 文件中维护其运行时配置,您可以直接访问并使用管理界面查询。
ProxySQL 的管理界面允许您发出命令来更改运行配置,然后使用 MySQL 命令将新配置转储到磁盘以实现持久性。这使您可以对运行中的 ProxySQL 实例进行零停机更改。您还可以使用此管理界面来进行由配置管理或自动故障转移脚本发出的自动更改。您可以在图 11-4 中看到您的架构通常如何利用 ProxySQL 和服务发现来为服务提供强大的访问层。
警告
需要注意的是,虽然我们在此图中将 ProxySQL 显示为一个对象,但我们强烈建议在生产环境中利用其集群机制,并在给定堆栈中部署多个实例。永远不要运行单点故障(SPoF)。
图 11-4。应用程序节点、ProxySQL 和服务发现之间的交互(根据 Bill Sickles 的图表调整)
ProxySQL 对其连接的数据库进行独立和分层的健康检查。根据这些健康检查的结果,ProxySQL 添加或删除主机或调整流量权重。您可以指定复制延迟阈值、成功连接的时间以及连接失败时的重试次数等许多其他配置选项,以控制在服务和应用程序需求的背景下可接受的故障容忍度。这些配置选项允许 ProxySQL 对不响应的主机做出准确的反应,要么暂时删除后端数据库,然后稍后重复健康检查,要么完全删除挣扎的后端成员,直到操作员介入。
使用 ProxySQL 进行分片
ProxySQL 对于许多分片拓扑结构非常有用。虽然它不像 Vitess 那样自动分割数据,但它可以作为一个很好的轻量级中间层,具有分片感知能力,并可以相应地路由应用程序连接。让我们来看看你可以如何将其用作分片层的路由层。
按用户分片
如果你的数据按功能或业务功能在不同的数据库集群中分割,并且不同的应用程序群访问这些集群,你应该为每个应用程序使用完全不同的数据库凭据。ProxySQL 可以利用这个用户参数将流量路由到完全不同的后端数据库池,无论是写入还是读取。
您可以通过在其管理界面上运行这些命令,然后将更改保存到其磁盘配置文件中,来配置 ProxySQL 中的此类路由:
INSERT INTO mysql_users
(username, password, active, default_hostgroup, comment)
VALUES
('accounts', 'shard0_pass', 1, 0, 'Routed to the accounts shard'),
('transactions', 'shard1_pass', 1, 1, 'Routed to the transactions shard'),
('logging', 'shard2_pass', 1, 2, 'Routed to the logging shard');
LOAD MYSQL USERS RULES TO RUNTIME;
SAVE MYSQL USERS RULES TO DISK;
提示
始终确保您保持 ProxySQL 的运��时配置和磁盘配置同步,以避免在 ProxySQL 进程重新启动时出现不愉快的惊喜。
这还可以方便地记录所有这些用户执行的操作以符合合规性,而不会对数据库造成任何负载。您将在第十三章中看到,我们还建议出于合规性原因为不同的数据库用户分别设置,因此这种设计也符合一些合规性目标。
按模式分片
您可以使用 ProxySQL 的模式名称作为管理流量路由规则来支持分片数据集的另一种方式。以下是您如何在 ProxySQL 的配置中定义的示例:
INSERT INTO mysql_query_rules (rule_id, active, schemaname,
destination_hostgroup, apply)
VALUES
(1, 1, 'shard_0', 0, 1),
(2, 1, 'shard_1', 1, 1),
(3, 1, 'shard_2', 2, 1);
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
请注意,只要正确命名模式,此配置可用于水平分片或功能分片。
在这种方式中使用 ProxySQL 时,我们最后一个重要建议是确保使用其原生的集群功能,这样可以确保像mysql_rules
这样的关键配置表在集群中的所有 ProxySQL 节点上同步,为中间件层提供冗余。
使用 ProxySQL 的其他好处
让我们讨论一些常见模式,在这些模式中使用 ProxySQL 可以帮助缓解快速增长环境中的常见问题。
在许多应用程序中,“向数据库打开更多连接”是我们在查询延迟开始上升时经常看到的模式。然而,在实践中,这可能导致停机⁷,并且倾向于使许多连接处于空闲状态,消耗资源但不执行任何工作。当应用程序层直接向数据库打开更多连接时,数据库服务器在连接管理上花费的资源量也会增加。这会导致数千个连接压倒已经超载的数据库实例。所有这些活动导致持续的停机时间,多个微服务中的级联故障以及延长的面向客户的影响。
ProxySQL 的连接管理架构通过仅向数据库打开可以工作的连接数量,有助于保护数据库层免受意外应用程序高峰的影响。ProxySQL 可以重用这些连接来处理不同的客户端请求。这种行为最大化了单个连接到数据库服务器可以完成的工作量,从而减少了管理连接的资源数量,并允许更有效地使用数据库服务器的内存资源。
ProxySQL 中的其他值得注意的功能
ProxySQL 在一般用途应用程序代理中具有一些突出的功能:
- 基于端口、用户或简单的正则表达式匹配的查询路由
- 前端应用程序连接和后端连接到数据库的 TLS 支持
- 支持各种 MySQL 版本,如 AWS Aurora、Galera Cluster 和 Clickhouse
- 连接镜像
- 结果集缓存
- 查询重写
- 审计日志
您可以通过访问其文档了解 ProxySQL 的广泛功能集(远远超出分片支持)。
ProxySQL 是一个强大的工具,您可以使用它来扩展应用程序,并为数据库层提供适当的性能保护,并具有支持各种业务需求的附加功能(如合规性、安全规则等)。如果您的公司发现自己处于高增长轨迹上,拥有一系列新的和不那么新的服务共享数据库资源,那么它可以是一个强大的工具,可以安全地继续这种增长。ProxySQL 提供了一个易于部署的抽象,可以比 HAProxy 更复杂,但在基础设施和复杂性方面的前期投资较少。然而,它也不提供 Vitess 中找到的一些更高级的功能,比如数据集的自动分片、模式更改的管理,以及VReplication,这是一个强大的工具,用于启用抽取、转换、加载(ETL)管道和更改数据流。
摘要
扩展 MySQL 是一场旅程。您应该在本章结束时更有准备地评估您的扩展需求,并了解如何扩展读取、如何扩展写入,以及通过向架构添加排队来使您的流量增长更可预测。您现在应该了解如何通过分片来扩展写入以及随之而来的所有复杂决策。
在深入研究可扩展性瓶颈之前,请确保您已经优化了您的查询,检查了您的索引,并为 MySQL 设置了稳固的配置。这可能为您提供计划更好的长期策略所需的时间。优化后,专注于确定您是读取密集型还是写入密集型,然后考虑哪些策略最适合解决任何即时问题。在规划解决方案时,请确保考虑如何为长期可扩展性做好准备。
对于读取密集型工作负载,我们建议转移到读取池,除非复制延迟是无法克服的问题。如果延迟是一个问题,或者如果您的问题是写入密集型的,您需要考虑分片作为下一步。
¹ 在物理科学中,单位时间的工作被称为功率,但在计算中,“功率”是一个如此多义的术语,以至于它是模棱两可的,我们要避免使用它。然而,容量的一个精确定义是系统的最大功率输出。
² 为了简化解释,我们选择忽略多个 CPU 和上下文切换的复杂性。
³ 这仍然不完全准确,因为当 CPU 接近 100%时,延迟会增加,您将无法再添加四千个查询。
⁴ 最常用的并且我们推荐的是Hashicorp 的 Consul。
⁵ 感谢 HiveDB 项目和布里特·克劳福德为提供这些优雅的图表。
⁶ 这种部署策略是由摩根·托克在 2019 年的Kubecon 演讲中详细解释的。
⁷ 欲了解更多信息,请参阅维基百科关于雷鸣群问题的条目。
第十二章:云中的 MySQL
很可能,你对于是否迁移到云服务提供商,甚至你的组织最终选择哪一个云服务提供商都没有太多控制权。你可以控制的是如何构建你的数据库环境。你可以选择两个方向:托管的 MySQL 或者在虚拟机上构建。托管的 MySQL 往往更加无需过多干预,但通常更昂贵且控制权更少。在虚拟机上构建意味着你可以更加灵活地构建和观察你的平台,但需要更多的时间和运营开销。
在本章中,我们将概述托管 MySQL 的主要选项以及它们对你有何用处。我们还将解释如何开始构建一个虚拟机选项,包括选择正确的规格和磁盘类型,以及在云中运行 MySQL 时需要准备的操作复杂性(如主机重启)。
警告
我们不会涵盖云服务提供商提供的 bug。这些提供的产品在不断发展,因此我们建议你保持与动态来源(如新闻通讯或 bug 论坛)的同步,而不是依赖像本书这样的静态参考。
托管的 MySQL
云服务提供商中的托管 MySQL 提供了许多便利,帮助团队在产品增长和功能集扩展时减少操作 MySQL 的认知负担。每个公共云都有自己对于托管 SQL 数据库应该是什么样子以及如何工作的理解。亚马逊网络服务(AWS)提供了几种 Aurora MySQL 的版本(我们很快会详细讨论这些),谷歌云平台(GCP)有 Cloud SQL,几乎所有公共云提供商都提供类似的服务。
托管解决方案的主要吸引力在于它们提供了一个可访问的数据库设置,无需深入了解 MySQL 的具体细节。通过几次点击或terraform apply
,你就可以在线创建一个带有副本和定期备份的数据库,然后就可以开始了。对于想要快速入门的公司或团队来说,这可能是一个非常吸引人的选择。
另一方面,使用托管的 MySQL 会缺乏很多可见性和控制权。你无法访问操作系统或文件系统,并且在进程本身内部的操作受到限制。除了云服务提供商提供的内容,你无法检查系统的其他任何内容。在大多数情况下,如果遇到问题,你只能提交支持工单并等待回复。你无法设置任何高级拓扑结构,备份和恢复方法也受限于云服务提供商提供的内容。
值得注意的是,许多云服务提供商提供的是与 MySQL 兼容的数据存储。这是一个具有 SQL 接口但内部工作方式可能与本书关注的 Oracle MySQL 完全不同的数据存储。我们将介绍一般的权衡和每个托管解决方案的不同之处,以帮助你选择最适合你的团队和业务需求的选项。
亚马逊 Aurora for MySQL
Aurora MySQL 是一个与 MySQL 兼容的托管数据库。Aurora 最吸引人的卖点是它将计算与存储分离,这使得它们可以分别和更灵活地扩展。Aurora 管理了许多你通常需要处理的运营任务,比如执行快照备份、管理快速模式更改、审计日志和管理单一区域内的复制。
Aurora MySQL 还有不同的提供方式。我们将简要介绍这些提供方式之间的区别。
标准的 Aurora 提供的是长时间运行的计算实例,你可以选择一个实例类别(就像在运行自己的 MySQL 时一样),并且你会获得内部复制到六个副本的附加存储。
警告
在撰写本文时,AWS 认为 Aurora 快速 DDL 是一个“实验模式”功能。如果您正在阅读本文,情况仍然如此,我们建议您参考第六章以了解有关使用数据库外部工具进行在线模式更改的更多信息。
需要注意的是,在 Aurora 集群内部的复制完全是亚马逊专有的,不是我们在 Oracle MySQL 中所知道和使用的复制。由于集群中所有的 Aurora 实例共享相同的存储层来访问数据,集群内部的复制是使用块存储来完成的。¹ 然而,Aurora 确实支持以我们在社区服务器中熟悉的格式写入二进制日志,以便团队希望将数据从一个 Aurora 集群复制到另一个集群或者出于其他二进制日志的目的,比如变更数据捕获。²
提示
如果您打算将任何关键数据库放在 Aurora 上,我们强烈建议您考虑使用亚马逊的 RDS 代理来管理应用程序与 Aurora 的通信方式。在您知道应用程序端可能会出现新连接风暴的情况下,RDS 代理可以非常方便地防止新连接量影响数据库。
自 2015 年 Aurora MySQL 出现以来,AWS 已经扩展了 Aurora MySQL 的选项,以满足更广泛的用例和业务需求:
Aurora 无服务器
Aurora MySQL 的无服务器产品消除了长时间运行的计算,并利用亚马逊的无服务器平台来提供数据库的计算层。如果您的工作负载不需要持续运行,这将为您提供很大的成本灵活性。
Aurora 全局数据库
这是 Aurora 为那些需要在多个地理区域中可用数据但不想使用二进制日志复制手动管理从主要集群到其他区域集群获取数据更改的解决方案。请注意,这会带来一些权衡,您应始终参考亚马逊的文档,以确保您接受正确的权衡。
Aurora 多主
多主是 Aurora 集群的一种特殊类型,可以同时在多个计算节点上接受写入。它旨在作为一个高可用解决方案,其中单个区域中的写入可用性是最高优先级。请注意,Aurora 多主带有自己的一套限制。首先,在撰写本文时,它运行的是 MySQL 5.6 服务器核心,这将阻止您使用许多功能。集群中允许的节点数量有一个最大限制,并且您不能在同一部署中混合多主和全局数据库。我们认为 Aurora 多主是一个非常主观的解决方案,适用于您在每个数据存储和应用程序交互中拥有的可用性和一致性选择,并建议您在选择之前仔细考虑您所陈述的约束和权衡。
AWS 继续对其托管的关系型数据库产品进行更新和改进,因此我们将避免深入讨论 Aurora 各种版本之间的功能差异。然而,图 12-1 提供了一个流程图,帮助您了解哪种类型的 Aurora 可能最适合您的需求以及在什么权衡下。
图 12-1 为您提供了一个基本的决策树,帮助您在 Aurora 选项之间做出选择。重要的是,尽管 Aurora 有许多选项,但总会有权衡。例如,您无法同时实现多写高可用性和跨区域亚秒级复制。但您可以利用这些产品来展示这些权衡,并推动关于哪个更重要:写入可用性还是区域复制的困难产品讨论。
Aurora 并不是唯一由云提供商提供的托管 MySQL 产品。GCP 有自己的产品。
图 12-1. 一个流程图,帮助您选择适合您需求的 Aurora 版本
GCP Cloud SQL
Cloud SQL 是 GCP 的托管 MySQL 产品。这种产品与 AWS 的主要区别在于它运行社区服务器,但特定功能被禁用,以便实现产品的多租户性和托管性。以下是一些你不能在 Cloud SQL 中使用的功能,尽管它运行社区服务器:
SUPER
权限被禁用。- 禁用加载插件。³
- 一些客户端也被禁用,比如 mysqldump 和 mysqlimport。
与 AWS 的产品类似,您无法访问实例的 SSH。
另一方面,Cloud SQL 为您管理了许多运营任务:
- 本地高可用性支持。故障转移通过配置选项自动化。
- 数据静态加密。
- 灵活管理升级,使用多种方法。请注意,最终这些维护窗口会涉及一些停机时间(类似于 AWS Aurora),您需要平衡这一点与应用程序 SLOs 的责任。⁴
正如我们在本章开头提到的,您可能没有选择在哪个云提供商中构建这些数据库的选择,因此您更可能需要了解所选云提供商提供的托管选项以及如何使用它,或者提出直接使用虚拟机而不是托管 MySQL 的理由。
现在我们已经介绍了托管关系型数据库选项以及这些选择的复杂性,让我们谈谈稍微更复杂的路径:在云托管的虚拟机上运行 MySQL。
虚拟机上的 MySQL
托管 MySQL 的特性对于那些想快速上手的人可能非常吸引人,那么为什么有人选择自己运行呢?在虚拟机上运行 MySQL 就像在裸机上运行一样。您可以完全控制所有操作方面。您可以在单个区域运行主 MySQL,但在其他区域设置副本以用于灾难恢复,或者运行一个延迟副本。您还可以根据工作负载最优化地定制备份方法。如果性能下降或遇到问题,您可以完全控制操作系统和文件系统,允许您进行任何自查。
云中的机器类型
如 第四章 中讨论的,MySQL 的 CPU 核心数量和可用 RAM 对 MySQL 的性能有直接影响。为数据中心选择特定硬件规格的缺点是它们不能很容易地更改。如果您有一个 56 核、512 GB RAM 的机器架设,您当然可以减少安装的 RAM,但您已经为此付费了,所以除非您可以在其他地方重复使用 RAM,否则您可能在硬件上花费过多。
当您使用云提供商时,为您的工作负载优化机器规格要容易得多。主要的云提供商允许您选择设置虚拟 CPU(vCPU)范围、可用的 RAM 量、网络和磁盘限制的机器规格。随之而来的是,您可以根据工作负载的变化调整 VM 的大小。这意味着,如果您在一年中的特定时间经历高峰流量,比如假期季节,您可以临时增加机器规格以应对这种情况。一旦流量模式回落,您可以再次将它们调整为更小。这种灵活性是许多人转向云的原因。
选择正确的机器类型
如果您已经在云提供商上,选择一台机器相当简单。如果遇到 vCPU、内存或网络瓶颈,您可以找到适当的机器类型来克服,并调整大小。但如果您从数据中心搬迁,确定正确的配置可能会有些棘手。
CPU
在第四章中,我们讨论了如何为你的工作负载选择正确的 CPU。当你转移到云时,大部分指导仍然适用。请记住,与云提供商一起,你得到的是虚拟 CPU,而不是物理 CPU。这意味着 CPU 不是专门属于你的。它可能与同一物理主机上的其他租户共享。很可能,你会看到比在你自己的独立服务器上更多的延迟和利用率变化。
如果你从物理机器迁移到云提供商,估算你的 CPU 使用量也可能会有些棘手。我们成功地使用以下公式来计算 vCPU 数量:(核心数 × 95% 总 CPU 使用量)× 2。
例如,假设你在数据中心有一台 40 核服务器。在过去的 30 天里,CPU 使用率的峰值为 30%。在云提供商中以 50% 利用率运行这个需要多少核心?使用上述公式,我们会估算出 24 个核心。如果你选择的云提供商没有提供 24 核的机型,考虑四舍五入到最近的类型或确定你的提供商是否提供自定义机型。⁵
警告
随着 CPU 使用率或核心数的增加,上下文切换也会增加:在 CPU 上切换任务的行为。因此,你不希望运行在 100% 的 CPU 容量,因为你会浪费大量时间在线程之间切换。这将表现为查询的延迟。我们建议目标是 50% 的典型利用率,峰值可达到 65%–70%。如果你维持在 70% 的 CPU 或更高,你可能会看到延迟增加,你应该考虑添加更多的 CPU。
也要注意 CPU 芯片系列,如果有这个选项的话。如果你正在运行一个高流量的网络应用程序,你可能希望确保你有一个更晚一代的芯片可用。同样,如果你正在考虑后端数据处理,在那里旧的、稍慢一些的 CPU 芯片系列可能是可以的,这可能会节省成本。
内存
正如第一章和第四章所讨论的,RAM 可以极大地影响 MySQL 的性能。
选择最适合你数据工作集需求的机型规格,更倾向于过多的 RAM 而不是不足。
网络性能
虽然 CPU 和内存大小是选择机型最重要的部分,但确保你也审查了可用的网络性能限制,以确保你不会让你的应用程序挨饿。例如,如果你有一个将读取大量数据的批处理过程,你可能会发现在较小的机型上耗尽带宽。
提示
值得注意的是,云区域和区域之间的网络出口通常是有成本的。当设置副本时,这可能会让人感到惊讶,但我们仍然认为将副本放在不同的区域是很重要的,以实现冗余。
选择正确的磁盘类型
尽管机型通常是动态的,但是为数据存储做出的选择可能是你最复杂的决定。一旦你选择了一个磁盘类型并开始用它来存储数据,转移到另一种磁盘类型就会变得困难。通常情况下,你需要挂载第二个磁盘并复制数据。纠正这个问题并不是不可能的,但肯定比只需快速重启来添加更多 CPU 要复杂。
选择正确的磁盘类型也高度依赖于你期望运行的工作负载。高度读密集型的工作负载将受益于更多的内存而不是磁盘性能,因为内存访问速度快得多。如果你的工作集大于你的 InnoDB 缓冲池,你将总是需要去磁盘读取一些数据。写密集型的工作负载将总是需要去磁盘,这是大多数人开始看到他们的第一个磁盘瓶颈的地方。
附件类型
首先要做出的决定是选择本地连接的磁盘还是网络连接的磁盘。本地连接的磁盘具有提供极高性能和一致吞吐量的优点,但也容易丢失数据。这是因为它们被视为临时数据的磁盘。如果运行本地连接数据的虚拟机的硬件崩溃,你可能会丢失本地磁盘上的所有数据。同样,在某些情况下,即使关闭实例,再次启动时可能会在不同的主机机器上且磁盘为空。本地连接的磁盘通常没有任何复制或 RAID 支持。主机级磁盘故障可能导致数据丢失。如果选择这条路线,我们强烈建议考虑使用软件 RAID,以至少减少数据丢失的可能性。有关 RAID 的讨论,请参见第四章了解更多信息。
相比之下,网络连接的磁盘提供冗余性和可靠性而不是性能。这并不是说网络连接的磁盘性能不好,只是不如本地连接的性能好。你的网络连接磁盘可能会出现停顿,而本地连接的磁盘可能不会。你还可以在本地实现更高的吞吐量和 IOPS 数字。
当使用网络连接的磁盘时,云服务提供商提供方便的备份或快照工具。这些对于 MySQL 使用效果很好,假设你已经配置了符合 ACID 标准的设置⁶并且你的备份解决方案设计得当。你可以在任何时候进行磁盘快照,并通过正常的崩溃恢复无问题地恢复它。
你还可以使用磁盘快照来快速制作副本,即使磁盘大小达到数 TB。通过这样做,你可以最大程度地减少需要追赶的复制延迟量,以便副本可以使用。
请注意,如果使用本地连接的磁盘而不是网络连接的磁盘,你需要自己解决如何使用 LVM 或第三方工具如 XtraBackup 备份数据的问题。有关备份的更详细讨论,请参见第十章。
关于附件类型的最后一点是,云服务提供商不像硬件上的 RAID 卡那样提供写缓存(电池或闪存支持)。
SSD 与 HDD
总的来说,你应该为所有东西使用 SSD,尤其是你的 MySQL 数据卷。如果预算特别紧张,你可以探索 HDD 作为启动磁盘的更便宜选项。在我们的实验中,我们发现 SSD 的启动速度比 HDD 快两到三倍。如果启动时间很重要,尤其是在停机或重新启动情况下,请坚持使用 SSD。
IOPS 和吞吐量
另一个复杂的因素是确定你的 IOPS 和吞吐量需求。在选择需要的磁盘类型之前,你应该对这些需求有一个良好的了解,无论是历史还是未来的。
如果你正在迁移现有工作负载,理想情况下你已��有了这些的历史磁盘使用度量标准,这将帮助你最好地选择你的磁盘。如果没有,你可以使用pt-diskstats,来自 Percona Toolkit 软件包,收集一天的度量标准以测量峰值。
对于新数据库,投入一些时间来了解应用程序的强度。即使了解读写比这样基本的事情也有所帮助。如果其他方法都失败了,找到性能和成本之间的一个良好折中点,并设定可能需要稍后调整的期望。
附加提示
如果选择在 VM 上运行自己的 MySQL,你将需要负责比托管服务更多的事情。你需要自己做磁盘大小调整、备份等工作。如果选择这条路线,以下是一些建议要考虑的。
处理主机重新启动
您的虚拟机实际上只是在别人的硬件上运行。尽管我们不喜欢,硬件可能会出现故障,当这种情况发生时,您的虚拟机将立即终止。如果配置了,您的虚拟机将开始在另一台主机上重新启动。如果这发生在您正在提供生产流量的情况下,尤其是在接受写入的源节点上,可能会对用户造成中断。
没有什么魔法解决方案可以让您避免这个问题 - 您只能处理它。如果发生这种情况,您往往有两个选择:启动到一个副本的故障转移过程(在“复制故障转移”中有介绍),或等待源重新上线。处理未经计划的晋升可能非常棘手。我们的建议是允许服务器重新上线并让复制自然重新连接。
您可以通过遵循以下建议使这个过程更容易应对:
- 使用 SSD 引导磁盘以实现尽快重新启动。通常系统在不到五分钟内恢复在线。
- 在最多五分钟内抑制您对主机宕机的即时通知,以允许系统完全重新启动并恢复健康。
- 如果源服务器重新启动,您可以编写一个选项动态关闭
read_only
标志,允许写入继续进行而无需人工干预。当与crond@reboot
选项配合使用时效果很好,该选项将在系统启动时运行脚本。唯一的注意事项是您需要能够查询系统以确定系统是否应该接受写入。 - 通过自动向可能需要了解中断的团队或频道发送电子邮件或聊天消息来最大程度地沟通。“主机 FQDN 意外宕机,预计将在五分钟内恢复在线”可能足以阻止人们给您发消息甚至呼叫您。
分离操作系统和 MySQL 数据
无论您选择本地附加还是网络附加,出于以下原因,我们建议将操作系统和 MySQL 数据分开:
- 磁盘快照将仅限于 MySQL 数据,不包含任何操作系统信息。
- 对于网络附加磁盘,您可以轻松地将磁盘断开并重新连接到另一台机器。
- 对于网络附加磁盘,您可以升级或替换操作��统而无需重新复制数据到文件系统。
也要考虑放置特定文件的位置,比如 MySQL 进程 ID 文件、任何日志文件和套接字文件。我们建议这些文件与操作系统保持在一起,尽管日志可能可以留在数据磁盘上。
备份二进制日志
将您的二进制日志发送到一个存储桶。在存储桶上设置生命周期控制,在一定时间段后自动清除旧文件。同样,防止在一定时间段之前删除文件或完全禁止删除。
不要忘记考虑安全性。让全世界都能读取这个存储桶可能是一场等待发生的噩梦。控制谁可以读取或删除这些数据对于维护安全的备份策略至关重要。考虑允许所有数据库机器写入,但没有一个可以读取或删除。从受限制的帐户、机器或两者分别控制读取和删除。
自动扩展您的磁盘
对于网络附加磁盘,您支付的是预留的空间量,而不是使用的空间量。这意味着在 MySQL 数据磁盘上留下大量预留但未使用的空间可能是浪费的。您可以优化的一种方式是将磁盘空间使用率目标定为更高的百分比,比如 90%,但如何减轻磁盘空间耗尽的风险呢?
云服务提供商通常有一个可用的 API 调用来扩展你的磁盘大小。通过一点点代码,你可以确定你的服务器是否超过了 90% 的磁盘已满标记,并调用该 API 来扩展磁盘。这也可以减少接近磁盘空间耗尽而被呼叫的可能性。总的来说,这个过程可以在你花费在预留磁盘空间上产生显著差异。
我们将分享一些关于此的警告,然而:
- 考虑一下应该多频繁运行查找已用磁盘空间百分比的代码。你需要根据磁盘的吞吐量来确定,一个进程需要多长时间才能完全填满剩余的磁盘。你的代码应该比这更频繁地运行。
- 如果你的进程失控并不断扩展磁盘而没有限制,你可能会在付费提供商账单��期时惊讶地发现一个 64 TB 的卷。
- 这个磁盘扩展 API 调用可能会导致磁盘短暂停滞。请确保在负载下进行测试,以确保不会对用户产生不利影响。
摘要
如果你在成千上万家在公共云中运行的公司之一工作,当涉及如何运行你的数据库时,你有很多选择。作为数据库工程师,你将被问及要使用哪种托管解决方案,是否要完全使用托管关系数据库解决方案,以及每种选择的权衡是什么。在这些讨论中最重要的是要记住,没有免费的午餐。你的每个选择都伴随着一系列的权衡。你可以做的最有用的事情是将这些权衡框定在你的业务运营方式和成熟阶段的背景下,以帮助指导你的组织朝着最合适的方向发展。我们希望这一章能帮助你在这些讨论中具备比较手中权衡和公司需求的能力。
¹ 如果你真的想了解那个架构的细节,我们强烈推荐阅读 2017 年 Aurora 团队发表的《SIGMOD 论文》。
² 变更数据捕获是数据架构中用于确定数据何时发生变化并在域和系统之间传输该变化的设计模式。关于这方面的更多阅读,我们强烈推荐马丁·克莱普曼(O’Reilly)的《数据密集型应用设计》第十一章。
³ Cloud SQL 确实提供了自己的解决方案用于支持合规需求的审计日志记录。
⁴ 欲了解更多信息,请参阅 Cloud SQL 文档中的“最小化维护影响”。
⁵ 请注意,自定义机器类型可能比预定机器类型更昂贵。当在大量实例上工作时,选择大小时考虑成本是非常重要的。
⁶ 提醒一下,这些是innodb_flush_log_at_trx_commit=1
和sync_binlog=1
。
第十三章:与 MySQL 合规
数据库工程团队的角色引起了许多内部业务利益相关者的关注。正如我们已经介绍的,您不仅需要为性能和正常运行计划,还需要考虑基础设施成本、灾难恢复以及各种合规需求。
您的工作不仅限于在业务运行时管理这些数据。您还需要帮助企业保护数据并为法律要求或对业务至关重要的监管认证进行认证。您必须了解实现这些需求的业务目标,并将这些要求包含在所有数据架构设计中,包括如何自动化运营任务、管理访问权限以及将管理任务转换为自动化任务的代码。
本章涵盖了企业可能追求的不同类型的合规认证以及各种特定于数据库的关注点。我们帮助解释如何为不同的合规需求设计,并讨论访问日志记录如何成为填补合规要求的关键部分。最后,我们涵盖了数据主权作为各类企业数据架构实践中新兴关注点。
警告
本章不旨在为您提供法律建议。我们旨在帮助您在运行大量数据库时管理合规需求,以及如何从早期开始设计合规。在寻求如何正确履行特定控制的建议时,您应始终与公司的法律团队咨询。
什么是合规?
治理、风险管理和合规(GRC)是指导企业如何评估和优先考虑其资产风险以及如何遵守管理其产品所使用的个人或健康数据处理和传输的法律的原则、流程和法律。早期初创企业通常不具备许多合规需求,因为他们在找到产品市场契合度时。然而,随着业务的发展,您将开始遇到许多法规。有些法规需要适用于企业所有数据,而有些可以适用于特定部分。
在合规背景下经常使用的一个术语是控制。控制是公司内部定义和实践的过程和规则,旨在减少不希望的风险结果的机会。
让我们介绍一些您应该了解的合规法规。稍后,我们将介绍可以帮助更轻松满足这些各种要求的架构变更。
服务组织控制第 2 型
服务组织控制第 2 型(SOC 2)是一组合规控制,服务组织可以用来报告与安全、可用性、处理完整性、机密性和隐私相关的实践。寻求获得 SOC 2 认证的组织中的数据库工程师需要建立良好的数据库变更管理、备份和恢复程序以及管理数据库实例访问权限的实践。
萨班斯-奥克斯法案
2002 年萨班斯-奥克斯法案(SOX)是一项所有成为上市公司的公司都必须遵守的法案。它旨在通过提高根据证券法规定向投资者披露的公司披露的准确性和可靠性来保护投资者,并为其他目的。对于一个工程组织,SOX 职责要求证明包含影响收入的数据的数据库只能被有需要的人访问,并且对这些数据的任何更改都已记录,并且更改有记录的原因。
如果您是一家上市公司,SOX 控制 404 是一个您必须熟悉并履行的法律要求的控制。它旨在通过证据保证公司报告的财务状况得到数据访问和变更管理实践的支持,这些实践准确地将提供的服务归因于收取的收入,并为对此类数据的任何更改提供审计跟踪。
付款卡行业数据安全标准
付款卡行业数据安全标准(PCI DSS)是所有处理信用卡数据的金融机构必须遵守的标准。其目的是保护持卡人数据,防止泄露并用于欺诈交易。
作为数据库工程师,当涉及到 PCI-DSS 控制时,一个重要的方面是管理对持卡人数据的访问。这意味着您需要在架构中考虑该控制,以确保卡数据得到单独管理。我们将在本章后面讨论如何实现这一点,当我们讨论角色分离时。
健康保险可携带性和责任法案
1996 年的健康保险可携带性和责任法案(HIPAA)是一项旨在保护个人健康相关数据隐私的美国法规,当这些数据由医疗提供者、医疗计划或其业务伙伴收集和处理时适用。该法律适用于被定义为电子个人健康信息(ePHI)的数据。提供需要符合 HIPAA 合规性的产品的组织将需要他们的数据库工程师实施控制措施,如 ePHI 的访问控制、所有 ePHI 的加密以及每当访问 ePHI 时进行活动记录。
联邦风险和授权管理计划
对于在美国运营并希望与美国政府实体开展业务的公司,联邦风险和授权管理计划(FedRAMP)是联邦政府提供的认证,使企业有资格成为联邦实体的云服务提供商。这是一系列要求的标准,使得符合条件的企业有资格接待联邦实体作为客户。这些标准包括配置管理、访问控制、安全评估以及对数据访问和更改的审计。
通用数据保护条例
《通用数据保护条例》(GDPR)是欧盟于 2016 年颁布的一项法规,旨在规范个人可识别信息在欧盟人员中的存储和管理方式,无论数据处理实体的总部在何处。它引入了管理数据隐私的第一步,例如在收集私人数据之前要求同意、设定跨处理器组织对该私人数据的访问限制,并为个人提供法律途径,要求任何可能通过其在线活动收集其数据的数据处理器的系统中清除其数据。这被称为个人的“被遗忘权”。
施雷姆斯 II
2020 年,欧盟与 Facebook 的爱尔兰实体之间的案件由欧盟司法法院裁定。这一裁决,通常被称为施雷姆斯 II,对所有在欧盟运营并收集欧盟人数据的美国公司产生了广泛影响。
隐私盾是美国公司多年来在欧盟运营的法律基础。施雷姆斯裁决宣布,当美国公司实体在欧盟收集欧盟人的数据时,隐私盾不足以保护欧盟人的隐私。在取消的核心是欧盟司法法院裁定,隐私盾不足以保证欧盟人不会通过美国法律手段(即使用 1978 年《外国情报监视法案》提供的机制,或 FISA)被美国政府监视,因此,由美国实体收集的欧盟人的个人可识别数据必须留在欧盟,不得跨越到美国资产或被美国人访问。
与 GDPR 最初版本相比,该裁决使得数据架构的推理变得更加复杂。由于这项裁决是最近才做出的,执法仍然是一个未知的数量。这种情况让每家企业决定其收集和处理的数据有多少在范围内,以及以何种方式。可以肯定的是,如果您有当前或未来的欧洲客户,Schrems II 将会针对您运行的应用程序和数据基础设施。
为合规控制构建
正如您所看到的,企业的法规合规世界,以及由此产生的企业用于运营的数据,是庞大的,控制措施可能因每项法律的目标或您的企业所需的认证而有所不同。好消息是,同样的工作可以涵盖多个控制措施,从而实现效率和更一致的实践,管理基础设施时。然而,您需要了解哪些控制措施对业务是必需的,以及出于何种目的。一旦您的公司发展到需要开始实施这些法规合规控制的规模,您将成为向不同类型的审计员提供合规证据的人。了解每项控制措施的目的将有助于提供正确类型的证据,使��计更加容易。
提示
为合规性构建是一个持续的过程,不能在需要时轻松“添加”。本章中提出的许多架构建议(角色分离、跟踪变更等)是您在公司过了“仍在寻找市场适应性”阶段后应该考虑和倡导的事项。这些实践将使您的业务在合规性真正成为一个需求而不仅仅是“好有的”时,为成功打下基础。
机密管理
在讨论如何管理机密之前,让我们首先明确您基础设施中可能属于该定义的事物:
- 应用程序与数据库交互的密码字符串
- 供支持人员/运营人员管理数据库实例的密码字符串
- 可以访问/修改数据的 API 令牌
- SSH 私钥
- 证书密钥
您组织中需要的一个核心能力是以安全和独立的方式管理机密,而不是与配置管理混为一谈。您需要一种交付和轮换敏感数据(如数据库访问凭据)的方式,无论是供应用程序还是团队使用,用于报告目的。
如果您在云环境中运行应用程序和数据库,我们强烈建议您在考虑构建自己的解决方案之前,先了解该云服务提供商的首选机密管理解决方案。您需要的是至少提供符合国家标准与技术研究所(NIST)标准可接受级别的加密,因为这是许多法规(包括 HIPAA 和 FedRAMP)所要求的。
如果您的云服务提供商没有一个可接受的机密管理解决方案,您可能需要自行托管。这可能是您的组织的一项新尝试,需要比本书所涵盖的更广泛的努力。
无论您使用托管解决方案还是最终需要自行运行,都要意识到这种机密管理解决方案给您的架构带来的复杂性。这种解决方案的目标是管理机密,而不是成为产品的单点故障。提前与您的法律团队和安全组织明确讨论解决方案可用时会发生什么的权衡将会很有帮助。关于缓存哪些机密以及以何种方式缓存的明确对话将有助于避免后期期望不一致。
通常,公司在早期发展阶段基于便利性做出的决定需要在计划合规控制之前进行调整。您应该准备向领导层解释为什么在改善安全姿势和降低风险的背景下这项工作很重要。
不要共享用户
不要跨服务共享数据库凭据。如果您的数据库意外泄漏,现在必须评估应用程序堆栈和流程中有多少部分需要获取新的数据库凭据对,这种决策的效果将成倍增长。作为数据库工程师,加入一家初创公司是很常见的,人们通常会采取看似方便的捷径,“所有代码都使用相同的对来访问数据库。” 相信我们:这是一种非常昂贵的捷径,如果您限制每个数据库用户的访问权限仅限于其所需的服务,您的未来自己会感谢您。
现在我们已经介绍了这个基本但至关重要的基本实践,让我们谈谈在选择存储数据库凭据或秘密的解决方案时需要考虑的事项。
不要在代码存储库中检查生产数据库凭据
这可能看起来很明显,但我们在许多大大小小公司的安全事件报告中仍然看到这种情况发生。保持谦卑的心态很重要,不要假设这种错误在您的组织中不太可能发生。信任但验证的方法将在很大程度上有助于防止未来的痛苦。在合并拉取请求之前扫描代码存储库中的潜在秘密字符串是一种常见做法(也是像 GitHub 这样的托管存储库服务可以为您执行的操作)。如果您的组织尚未考虑这一点,您可能需要成为这种需求的倡导者。请记住,合规性和安全性对整个组织都是必需的,尽管并非所有事情都可以或应该由数据库团队完成,但您是整个工程组织讨论这些优先事项的利益相关者之一。
这些实践对于正确开始合规性和安全姿势至关重要。它们将使我们在使用秘密管理时即将涵盖的一些操作变得更加简单,并且在有必要进行紧急更改时将进一步降低业务风险。
让我们谈谈在选择秘密管理解决方案时的权衡。
选择一个秘密管理解决方案
选择一个秘密管理解决方案将取决于您运行的环境以及最容易与数据库和应用程序堆栈集成的内容。在方便性和满足所有需求之间总会存在权衡。因此,您需要明确向所有利益相关者(其中一些不是工程师)说明限制是什么,可用性或弹性的权衡是什么。在检查您的云(或私有)基础设施可以提供的内容时,您应考虑以下一些权衡:
空间限制
许多由云提供的秘密管理解决方案对秘密的长度做出了假设,这可能会导致意外惊喜,如果您想存储比数据库用户和密码对更长的内容。如果您的合规控制要求将更长的文本字符串(例如 SSH 密钥或 SSL 的私有证书)也视为秘密,您应该查看您可以在给定密钥上存储的最大大小。一些组织后来会遇到的雷区之一是,随着秘密足迹的增长,需要一个新的不同的秘密管理解决方案来容纳更长的秘密。现在他们必须处理迁移(可能会影响正常运行时间)或管理工具和与两个单独的秘密解决方案集成的复杂性。
秘密轮换
如果你在公共云中运行并且可以使用他们提供的托管密钥管理解决方案,那么对你来说有个好消息:截至目前,所有三个主要的云服务提供商都提供了一些自动化轮换密钥的方法,以及版本控制,使得服务过渡到新密钥对你的服务更加无缝。然而,如果你选择的密钥管理解决方案不支持轮换密钥,那么你需要计划如何进行这项工作,无论是作为计划更改(例如,你可能有一个要求数据库凭据定期轮换的控制)还是作为紧急变更(例如,有人意外地在公共存储库中检查了数据库密码)。如何在不影响正在运行的应用程序的情况下进行这项工作?这在很大程度上取决于你如何向正在运行的应用程序传递配置更改以及你的部署流水线的运作方式。这是如何编排的一般想法。这个变更是一个部署。即使你的应用程序将数据库凭据作为配置行访问,你仍然需要将这个配置更改传播到你的所有设备,并通常还需要编排一个重新启动,而不影响整个服务的可用性。
区域可用性
要考虑你的密钥管理解决方案不仅用于存储密钥,还用于传递密钥。如果你要避免像在代码中存储密钥这样的已知不良做法,你需要让你的应用程序能够在运行时检索它需要处理请求的密钥。这意味着你现在必须考虑如何检索这些密钥,如果你的应用程序无法访问密钥管理服务会发生什么,以及这种新依赖引入了什么故障模式。如果你负责需要在许多地理区域运行的应用程序,你的密钥管理解决方案的区域能力就成为另一个需要考虑的因素。你的云提供商的解决方案是否会自动将密钥复制到其他区域?还是你必须构建这种能力?
角色和数据的分离
这些监管法律的一个重要目标是根据数据泄露的风险将数据分开,无论是对企业还是对其客户。这就是最小权限的概念,无论是对人类访问还是对应用程序代码访问。这种分离允许根据数据内容和相关风险进行更合适的控制和变更跟踪。
为合规性原因进行分片
可能会迫使特定数据集分开到专用集群的一个因素是具有非常不同控制要求的不同合规性要求。比如说,你是一个市场传播提供商,正在创建一个侧重于医疗技术的新产品。目前正在使用的数据与健康无关,并且不受多项法律要求约束。一旦企业进入健康技术领域,你现在就有了一部分客户及其数据,你的公司承担了作为个人健康信息(PHI)处理者的法律责任。在这种情况下,最好从一开始就在专用数据存储中开发新产品,这样你就可以更适当地应用 HIPAA 合规性控制,而不会给现有数据集及其依赖的应用程序增加不必要的负担。
分开数据库用户
随着您的产品变得更加复杂,支持其的技术堆栈也随之增长,您将开始拥有多个具有数据访问权限的应用程序。在组织早期就开始良好控制数据访问权限非常重要,不要在多个代码库之间共享相同的数据库访问凭据。安全事件和凭据意外泄露是发生在各种公司的事件,无论规模大小,当这些事件发生时,您将受益于泄露的秘密影响已知且隔离的业务操作范围,因为您在匆忙更换该秘密时。
变更跟踪
许多合规法规都涉及跟踪变更的控制:影响财务报告的数据子集的变更,生成发票的系统的变更,以及这些变更如何被审查、测试和跟踪。这种合规控制首次显而易见的地方之一是数据库。随着您为扩大合规责任的业务工作,像“如何审查、应用和跟踪生产中的模式更改”这样的流程需要比简单地“工程师登录生产源节点并进行更改”更严格和更有计划。如果您通过与内部审计团队或合规团队一起规划如何处理审计来做好准备,这将减轻很多负担。
这里的目标是避免年度审计成为一个重大干扰事件,团队在为审计团队收集证据而忙碌。相反,如果您对这些正常业务操作的工作方式进行一些更改,您可以获得“内置”证据,这样收集和用于审计的证据就会简单得多。您将在本节中看到一个关于从所有方面生成结构化日志的重复主题。这是有意的。这在很大程度上有助于以相同方式跟踪各种变更,这种一致性有助于企业以更少的干扰实现其审计需求。您可以看到这一切是如何在图 13-1 中结合在一起的。
图 13-1。不同操作任务的示例,所有任务都将结构化日志发送到一个地方,使审计更容易
让我们看看数据库系统的不同变更类型以及如何为其自动化合规跟踪。
数据访问日志记录
许多合规控制要求您保留��特定数据集的更改或访问的日志。这可以用于跟踪财务数据的更改,也可以用于 PCI 或 HIPAA 等法规,其中数据足够敏感,所有访问都需要被跟踪。
您可以直接在数据库级别解决这个需求,通过利用Percona 的审计日志插件(如果您使用 Percona 的分支)或等效的MySQL 企业审计插件(如果您使用 MySQL 社区服务器)。这样做的好处是,现在您可以在数据更改之前的最后一跳跟踪变更,特别是如果您处于一个数据库可以通过多个路径进行更改的环境中。
跟踪变更的不良选项
您可能会问,“为什么不使用触发器跟踪我关心的表的任何更改?”这绝对是我们过去看到的一种方法,但不建议。以下是我们不建议使用触发器的一些原因:
- 已知触发器会对写入性能造成影响,这将在最糟糕的时候影响您。
- 触发器相当于将业务逻辑存储在数据库中,这是不推荐的。
- 将代码存储在数据库中可能会绕过对该代码进行测试、分期和部署的任何流程。在事件期间,触发器可能会成为您的团队的意外惊喜。
- 触发器只能支持跟踪写入操作。如果需要,它无法扩展到跟踪读取访问。
让我们看看如何使用 Percona 审计日志插件以及如何调整它。
安装和调整 Percona 审计日志
Percona 的审计日志插件作为 Percona 的 MySQL 分支的一部分提供,但默认情况下未安装或启用。您可以通过在任何新实例的引导过程中运行以下命令来安装它:
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
SHOW PLUGINS;
第二个命令列出正在运行的插件,并应确认审计日志插件实际上现在作为服务器进程的一部分在运行。除了启用它,您还需要确定如何摄取其输出。这就是真正的计划发生的地方。
Percona 的审计日志插件允许您定义需要跟踪的语句动词。这允许灵活地满足各种控制要求,而不会在审计日志中产生大量与您关心的内容无关的噪音。确保您查阅其文档以正确配置该变量。
插件的一个灵活优势是您可以安装它,但实际上不监视查询。这在您仍在研究如何摄取其输出并需要在不影响正常运行的情况下关闭和打开它时非常有用。但是,这种灵活性也带来了复杂性。除了管理审计日志插件附带的配置变量之外,您还需要监视它是否始终运行。如果这对业务来说是一个关键功能,那么值得监控。由于插件可以在不重新启动服务器的情况下禁用,您需要更多的方式来确认它是否正在执行所需的操作,而不仅仅是检查磁盘上的my.cnf文件以确认它是否正在执行所需的操作。您最好使用 shell 查询来解析插件的当前状态,并确认它是否实际上正在监视查询。以下是两个示例单行查询来检查这两个方面:
# Single liner to check that the audit log plugin is active
$ mysql -e "show plugins" | grep -w audit_log | grep -iw active
# Single liner to check that the plugin policy is actually monitoring queries
$ mysqladmin variables | grep -w audit_log_policy | grep -iw queries
这些示例假定您只想监视查询。如果您还希望使用插件来跟踪登录,您需要编辑检查。
摄取和使用审计插件日志
正如您所见,审计日志插件具有很大的灵活性,但它也只为您生成审计事件。您需要确定最佳方法来摄取这些日志,将它们放在一个可以轻松搜索和分析的地方,并合理地发现其中的异常,而不至于成为一个重大负担。插件可以简单地将输出转储到本地文件,但这可能会增加通过这些日志填满数据库主机磁盘而导致故障的风险。
更复杂的选项是使用插件将其输出发送到rsyslog
,这是一个常见的 Unix 日志管理实用程序,然后使用rsyslog
将所有这些事件转发到您组织选择的结构化日志平台。这个选项很吸引人,因为它将这些数据带入到您的组织已经进行结构化日志存储的地方,这降低了数据库团队之外的利益相关者查看、搜索和分析这些事件的门槛。请记住,使用rsyslog
以这种方式进行日志转发将要求您熟悉它的工作原理。确保您决定并以有意义的方式记录rsyslog
如何配置这个数据流。可能有许多rsyslog
的默认配置对您期望的结果没有帮助,您需要尽职调查找出这些配置并相应地更改它们。
警告
确保记录审计日志插件输出的存储方式,即使是临时存储在数据库主机上。如��这些文件的传送方式变慢,插件中缓冲事件的影响可能会影响数据库服务器本身的性能。这种故障状态很难调试,因为它的唯一症状是查询执行变慢。考虑以弹性为主的整个这些日志管道的混沌测试计划。
Percona 审计日志插件是一个强大的工具,可以帮助您满足许多合规性控制要求。根据我们的经验,它比使用触发器更高效,并且与配置管理和结构化日志记录软件很好地集成,从而形成一个对多个利益相关方团队有效的解决方案。
模式更改的版本控制
第六章介绍了不同的策略和工具,有助于规模化运行模式更改。让我们谈谈这些策略所支持的合规性问题。
使用版本控制来跟踪和运行模式更改,内置跟踪请求更改的人员、审查和批准的人员以及在生产中的运行情况。这也是使用每个数据库集群单独存储库的一个很好的理由。随着公司中数据库的增长,您会发现并非所有数据库都是平等的。有些需要更严格的合规性(例如,保存财务数据的数据库),而有些则用于不太关键的产品实验。在进行审计时,为每个数据集和集群提供更改记录将是一个巨大的便利。
基于合规需求的数据和集群模式管理的分离还使得更容易控制谁可以提交或批准您选择的版本控制管理中的模式更改。在进行业务审计时,通常需要证明谁可以对数据库进行更改。拥有一个较小的人员操作圈 可以 对这些数据进行更改,符合最小权限安全原则。
数据库用户管理
对数据库的更改不仅限于模式更改。您还需要以可跟踪和可重复的方式管理数据库用户及其细粒度权限。让我们看看如何满足一些常见的合规性控制,以解决数据库访问控制问题。
使用配置管理
使数据库用户跟踪合规的一个简单方法是利用与使数据库配置更改合规的相同流程。您可以在配置管理存储库中管理所有内容,并使用源代码控制、拉取请求流程和同行审查来提供证据,证明对数据库用户的所有更改都是以可审计和可跟踪的方式完成的。
计划凭证轮换
无论是因为未经计划的安全事件还是因为您有一个需要按计划轮换凭证的控制,您都需要制定一个计划,以便在不影响应用程序正常运行时间的情况下轮换数据库用户。这可能意味着更改应用程序使用的用户名和密码字符串。如果您尚未运行最新且最好的主要版本以支持双密码,请按照以下步骤在生产中旋转数据库凭证,而不会影响服务正常运行时间:
- 首先在数据库中引入一个新的用户名/密码对。
- 测试新凭证是否具有与旧凭证相同的访问权限。理想情况下,您应该自动执行此操作,作为部署新凭证的一部分,通过比较
SHOW GRANTS
并确认权限是否相同。 - 创建一个应用程序部署,替换应用程序配置中的凭证。
- 重新启动此服务的所有实例,以确保新对正在使用。
- 删除旧的用户名和密码对。
无论更改是例行还是紧急的(因为凭证泄漏或安全风险),此过程应该是相同的。由于后者不是您可以控制或完全防止发生的事件,最好将此过程自动化,或至少在运行手册中进行充分记录,并定期执行,以便在发生意外情况时,团队不会感到恐慌。
提示
在 MySQL 中旋转数据库用户密码曾经是一个复杂的协调工作,需要在不影响可用性的情况下完成。MySQL 8.0.14 引入了双密码支持,结合密码过期策略支持,使得在操作上更容易做正确的事情。
停用不再使用的数据库用户
任何未使用的数据库用户仍然在您的实例上活跃是一个您不需要的安全风险。定期审计您实例上活跃的数据库用户,与您的应用程序配置进行比较,并删除任何未被任何应用程序活跃使用的用户是很重要的。
当为您的公司满足合规需求时,您会发现许多合规控制要求组织跟踪对某些资产的任何和所有更改。这些控制在报告中很典型,比如我们在本章前面描述的 SOC 2,其中主要关注的是提供数据完整性和安全性的证据。
有几种方法可以找出一个定义的数据库用户是否正在使用。我们在第三章中广泛介绍了 Performance Schema 作为检查服务器性能的一种方式。在 Performance Schema 中有一个 users
表,存储了连接到服务器的用户的历史信息。这种历史跟踪可以追溯到服务器进程的生命周期或此表允许的最大大小,以先到者为准。由于该表跟踪已连接的用户,而不是未连接的用户,您需要循环遍历已知用户,并查看是否有任何用户不出现在此表中,作为他们可能不再使用的信号。
这是一个启用 Performance Schema 中该工具的查询:
mysql> UPDATE performance_schema.setup_instruments
-> SET ENABLED=‘YES’ WHERE NAME='memory/sql/user_conn';
Query OK, 1 rows affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
一旦您启用了这个功能,查找此信息的表是 performance_schema.users
。
如果您为合规控制使用审计日志解决方案,比如我们之前提到的 Percona 插件,您可以使用这些日志来确定用户是否在给定的几周内连接到实例。
无论您如何确定这一点,建议设置一个政策,“在六个月的不活跃后,将删除未连接的数据库用户。” 这是一个有助于防止不需要的访问权限并且现在是一个风险的做法。
您帮助管理的数据库将受到需要这种谨慎程度的控制范围。随着您的公司不断发展并开始考虑更加合规,您需要有一个故事来展示在将数据库更改应用之前如何审查和跟踪生产数据库的变化。合规控制还将关注的另一件事是证明您在发生灾难事件时恢复数据和服务的能力。为此,我们需要转向备份和恢复,看看它们如何适用。
备份和恢复程序
第十章涵盖了不同类型的备份。备份显然很重要。它们在事件中可以极大地帮助,同时也是许多合规控制的关键部分。在大多数 SOC 2 实施中,您将有关于创建和测试备份的控制(但无论如何,您应该测试您的备份)。随着您管理的数据库集群数量增加,您很快会发现无法继续手动执行备份和备份测试等流程,甚至无法通过手动阅读日志文件报告成功和失败。
在评估出于合规原因如何管理备份时,您需要满足一些要求:
- 您需要自动化备份过程。
- 如果备份失败,您需要备份过程提醒您。
- 您需要自动化备份测试。
- 备份测试失败也应该是您可以在某处跟踪的事件。
接下来我们讨论如何安排备份和备份测试。
运行自动备份和备份测试
为了满足这些要求,��需要一个机制,不仅仅运行计划任务(例如 Linux 系统中的crond),而且可以按计划运行,并且还具有向您的监控系统和工单系统发送事件的能力,以便在失败时发出警报并为以后的审计跟踪失败。您可以通过将备份和备份测试作为监控检查运行来做到这一点,⁴但是备份可能需要一段时间才能运行,特别是如果您有一些以 TB 为单位的数据库实例。只要监控系统可以处理运行时间远远超过几秒的典型时间的检查,将备份作为监控项目运行就可以工作。因此,请确保运行监控系统的团队意识到这种用例。
如果您的监控系统无法处理这种用例,那么请确保您有一些方法留下“踪迹”以跟踪备份已经发生并成功完成,以及备份测试已经发生并成功完成。这样的踪迹可以是一个文件,其中包含备份或备份测试运行结束时您的备份过程编辑的时间戳,作为任务发生并完成的证据。一旦放置了这个踪迹,您可以使用监控系统进行更快速的检查,以确保踪迹存在。
在所有这些策略中,您想要拥有的,以及您的 SOC 2 控制要求的,是备份成功完成的记录以及任何失败的备份的记录,显示它们已转化为正确跟踪的工作项。
备份和备份测试的集中日志
您可能还会被要求展示日志,证明备份和备份测试过程成功完成。您最好准备好这样的审计项目,通过使用可以将日志发送到以保持连续性的集中日志解决方案。请记住,我们为这些业务需求构建的解决方案应该假设服务器易于且可重复替换,而不是定制的。因此,如果您在下一次审计之前退休该机器并替换它,那么随机实例上的本地日志文件并不理想。您希望任何与业务相关的资产,例如备份过程的日志,都位于任何具有正确访问策略的人都可以访问的集中位置。
通过备份进行灾难恢复规划
作为 SOC 2 的一部分,还需要进行适当的灾难恢复规划。这意味着您需要证明您测试系统生成的任何备份,您需要跟踪这些测试失败的时间以及失败已经被纠正,并且最好您需要知道数据灾难恢复需要多长时间。这最后一部分需要跟踪备份测试需要多长时间的指标。第二章提到数据库实例大小作为确定备份恢复是否在预期时间内变得过大的度量标准。使这成为一个自我改进的循环的方法是使您的备份和测试备份的脚本也发送每个任务需要多长时间的指标。这样,您就可以获得每个数据库集群备份需要多长时间以及恢复和测试这些备份需要多长时间的指标。现在您有了一种跟踪任何给定数据集是否变得过大以至于超出业务对 MTTR 的期望的方法。如果是这样,那么您有数据要么优先处理将数据集分割到可接受大小,要么重新审视业务的恢复 SLA。
关于备份的一个重要最终注意事项:您的安全利益相关者需要访问实时数据库和备份以进行控制。确保您在您喜爱的云服务提供商中的备份设置不会默认为备份存储桶的宽松访问控制。许多安全漏洞并非通过侵入实时基础设施发生,而是通过从某个存储桶泄漏的备份发生。
摘要
合规性是一个广泛的政策和控制世界,以及对每个解释的影响。它不仅影响您在业务中运行数据库的方式,还影响法律、财务和 IT 部门,甚至影响您如何部署软件更改。本章重点介绍了每种常见类型的合规性法规如何特别影响您作为数据库工程师的职责。然后我们涵盖了这些法规可能受到影响的不同实践和架构决策,您也需要考虑这些。
广义上说,摆脱与控制相关的噩梦的最佳方法是提前计划。分离应用程序用户,制定凭据轮换策略,并确保密码始终以加密方式存储——绝不是明文。确保在需要开始记录对数据库的访问之前,你有一个可信赖的日志记录管道。最后,你希望对模式更改进行控制和记录。
本章的目标不是让你一次性考虑整个基础架构中所有这些控制措施,而是让为每项法规规定的范围提供证据的任务更容易,并尽可能自动化或简化组合。最终,这些控制措施旨在保护企业和客户的隐私。对每项控制措施的目标有清晰的理解将使您和您的团队在公司发展并进入更广泛市场时更容易管理这一重要任务。
¹ 欲了解更多关于这些标准的信息,请与您友好的信息安全团队交谈,或者从 O’Reilly 获取NIST 网络安全框架口袋指南。
² 有关此内容,请参阅插件文档。
³ 作为一个开始,这里有一个关于“可靠日志转发”的整个页面。
⁴ 在博客文章“使用 Sensu 进行 DBA 任务”中,您可以看到一些将备份任务纳入数据库监控解决方案的示例。
附录 A. 升级 MySQL
升级是稳定性和功能之间的权衡。在选择升级时,您应考虑这一点。使用 MySQL 的最大优势之一是其广泛的安装基础。这意味着您可以从许多其他人测试和使用 MySQL 中获益。如果升级到太新的版本,您可能会不知不觉地引入一个错误或回归到您的环境中。如果保持太落后,您可能会遇到不明显的错误或无法利用为性能优化的功能。
为什么升级?
决定继续进行版本升级可能是一个风险的过程。通常涉及备份所有数据,测试更改,然后运行升级过程。在我们深入讨论细节之前,了解为什么您可能希望升级是很重要的。
有许多升级的原因:
安全漏洞
多年来,发现 MySQL 中的安全漏洞的可能性已经减少,但仍然有可能。您或您的安全团队可能会评估这些漏洞,并确定您应该执行升级。
已知错误
在生产环境中遇到未知或无法解释的行为时,我们建议找出您正在运行的 MySQL 版本,然后阅读最新版本的发行说明。您完全有可能发现您正在经历的情况实际上是 MySQL 中的软件错误。如果您的问题得到解决,您可能需要升级 MySQL。
新功能
MySQL 在如何添加功能方面并不总是严格遵循主要/次要/点版本发布策略。许多人可能期望点版本(例如,从 8.0.21 到 8.0.22)只包含错误修复,而次要版本更改(从 8.0 到 8.1)将包含次要功能。Oracle 经常在次要点版本中发布可能对您的工作负载产生影响的新功能。这种策略是一把双刃剑,这就是为什么在升级之前应该阅读所有的发行说明。
MySQL 终止生命周期支持
Oracle 为 MySQL 设置了终止生命周期(EOL)时间。一般来说,建议保持在支持的版本内,这样至少安全修复仍然受支持。
现在我们已经讨论了影响您升级决定的各种因素以及具体版本,让我们讨论规划和安全完成升级的过程。
升级生命周期
一旦您做出升级是正确的决定,通常会采取以下步骤:
- 阅读该版本的发行说明,包括任何次要更改。
- 阅读官方文档中的升级说明。
- 执行新版本的测试。
- 最后,升级您的服务器。
发行说明通常包含重要信息,如新功能、更改或已弃用的功能,通常还会列出已修复的错误。升级说明为您提供了如何执行升级的详细概述,并提醒您在继续之前需要了解的任何重要信息。
此外,您还应该制定一个计划,以应对引入问题的情况,比如查询开始表现不佳,或者更糟糕的是,您开始遇到崩溃错误。对于所有主要和次要版本更改(例如,从 8.0 降级到 5.7 或从 5.7 降级到 5.6),唯一的降级方法是从升级之前的备份恢复。这使得升级特别危险,因此请确保您有一个计划。
警告
需要注意的是,自 MySQL 8.0 起,您也不能降级点版本。例如,一旦您运行 8.0.25,您就无法降级到 8.0.24,而不导出所有数据并重新导入。
测试升级
一旦您阅读了发布和升级说明,您应该对任何测试的关注点或重点有很好的理解。下一步将是测试这个新版本在您的工作负载下的行为。您还需要验证您已经审查了配置文件。MySQL 的新版本通常会重命名变量或完全弃用它们。
测试是一个难以完成的步骤,每种方法都有注意事项。考虑到之前提到的关于降级的风险,您应该在升级之前尽可能多地采用这些方法。
开发环境测试
希望您有一个用于数��的开发环境。这是开始测试的好地方,可以在共享开发数据库上进行测试,也可以在独立数据库上进行测试。使用这个的主要目标是发现任何明显的语法问题。大多数开发环境不包含与生产数据相同大小的数据,因此很难进行准确的测试。例如,您可能运行您常用的查询并看到它们正常,因为它们只访问表中的 10 行。当您转到生产环境时,表中有 1000 万行,您可能会看到退化。
生产镜像
另一个选择是创建生产数据的副本,并将您的 SQL 流量发送到副本。这种方法在Etsy 的 Code As Craft 博客上的一篇博文中展示过。简而言之,您有生产数据库的第二个副本,停止使用复制,并在副本上升级 MySQL。完成后,使用tcpdump和pt-query-digest的组合将流量发送到您的实时生产系统和副本。您的应用程序仍然仅使用生产系统进行实时流量,而具有升级版本的副本可以为您提供性能指标并显示语法错误。
副本
如果您的拓扑结构有读取副本并且您有能力将副本脱离池,您可以考虑首先升级其中一个副本。这将允许您查看实际生产工作负载下的读取流量表现。如果观察到错误或退化,您可以将副本脱离池并进行调整。这种方法的缺点是您无法测试性能或写入流量。
工具
Percona Toolkit 提供了工具pt-upgrade,它接受查询作为输入,针对两个不同的目标运行这些查询,并生成报告告诉您行数、行数据或错误的任何差异。由于它可以接受许多不同类型的输入(慢查询日志、常规查询日志、二进制日志),它可以是获得额外测试覆盖的好选择。
最佳使用方法是首先收集您最感兴趣的查询,可以使用慢查询日志或二进制日志。然后设置两个相同的系统,仅将其中一个升级到新版本,并对两者运行pt-upgrade以查看差异。
大规模升级
升级 MySQL 非常简单,并且在官方 MySQL 文档中有详细介绍。简而言之,如果您正在进行原地升级,您将停止 MySQL,替换二进制文件,启动 MySQL,然后运行mysql_upgrade
脚本。
如果您在数百个 MySQL 服务器上执行此操作,这可能会变得重复。我们建议尽可能自动化这个过程。您可以使用 Ansible 的一种方式来实现这一点。
这里是一个建议的安全升级过程的骨架,您可以将其用作构建 Ansible playbook 的指南,如果您选择的话:
1. 验证目标。
你要做的第一件事是防止生产系统的意外升级。如果您有一个可以查询以确定数据库是否正在接收流量的系统,这是检查的地方。如果您遵循我们在第五章的建议,应该使用read_only
标志来防止对副本的意外写入。如果没有可以检查的系统,这可以作为一个很好的替代方案。如果服务器是可写的,那么您可能不希望对其进行升级,因为它可能正在接收生产写入。您还可以使用此步骤验证您是否已经升级了服务器。这样可以稍后对其运行 playbook,而它将不执行任何操作。
2. 设置停机状态。
希望你的系统正在被监控。下一步涉及设置某种形式的停机或警报抑制,以便在 MySQL 在新版本上重新启动时不会收到警报。
3. 其他前提条件。
如果您有任何其他依赖服务,比如配置管理工具或其他监控工具,在 MySQL 离线时会生成错误,现在是关闭它们的好时机。
4. 删除旧包。
我们首选的方法是此时完全删除任何已安装的 MySQL 包。这有助于避免主要版本(5.7 到 8.0)之间的任何冲突包。
5. 安装新包。
接下来,您将希望在系统上安装新包。
6. 启动 mysqld
。
启动mysqld
服务。
7. 运行 mysql_upgrade
。
如果早于 MySQL 8.0,²运行mysql_upgrade
过程。特别注意,如果您像我们建议的那样使用super_read_only
运行 MySQL,则在mysql_upgrade
步骤中将其设置为OFF
。
8. 重新启动 mysqld
。
我们更喜欢在此时对mysqld
进行干净重启。这将确保它正确启动并使用升级后的文件,并且您的配置文件也在工作。
9. 验证您可以连接。
简单连接并运行SELECT 1
以确保 MySQL 正常工作。
10. 恢复任何已禁用的服务。
如果关闭了任何配置管理或监控工具,请重新启用它们。
11. 清除停机状态。
将服务器从停机状态中取出,以便观察是否有任何升级过程失败的情况。
通过这个过程,您可以将您的运行手册指向任何服务器,并仅升级不接收流量的未升级节点。
摘要
升级 MySQL 有许多原因,最引人注目的是修复您正在遇到的错误或利用新功能。例如,MySQL 8.0 引入了一个功能,InnoDB 可以立即添加列,无需重建整个表。这种功能增强对于执行大量ALTER TABLE .. ADD COLUMN
语句的公司来说可以节省大量时间。通过努力进行安全升级过程,最终将节省执行这些列添加语句的时间,以及改进开发人员体验。
主要版本升级可能令人生畏。您绝对应该花费大量精力测试升级是否会产生任何不良影响。通常,您希望检查升级是否导致任何查询延迟偏差或新错误。一旦您获得信心,慢慢推出并具有回滚过程。
最后,如果您有大量服务器需要管理,请考虑大力投资于尽可能自动化该过程。自动化可以使升级过程变得更容易重复,并比直接登录每台服务器更节省时间,也减少了因在错误服务器上而导致的拼写错误和意外停机的几率。
¹ 长期参与 MySQL 社区的成员 Stewart Smith,著名地提出了点二十规则:“[规则] 是指软件在发布点二十版本之前永远不会真正成熟。”虽然这不是一个严格的规则,但它突显了新版本和稳定性之间的权衡。
² MySQL 8.0 将 mysql_upgrade
过程移入了服务器启动过程中。无需作为额外步骤运行。
附录 B. Kubernetes 上的 MySQL
如果您在过去五年中一直在科技领域工作,很可能已经听说过 Kubernetes,与运行 Kubernetes 的团队合作,或者看过很多关于 Kubernetes 的会议演讲或阅读了很多博客文章。如果您的组织运行自己的 Kubernetes 集群,那么在某个时候,您会被问及是否在其中运行 MySQL 也是一个好主意。从表面上看,这似乎是一个合理的选择。管理许多 Kubernetes 集群是一个复杂的任务,通常需要专门的人力资源,因此您的组织希望利用这种专业知识来处理不仅仅是无状态工作负载。但是,有很多理由可以探索在 Kubernetes 上运行 MySQL,也有一些不太好的理由。让我们在这里揭开一些关于在 Kubernetes 上运行 MySQL 的恐惧、不确定性和怀疑。
使用 Kubernetes 配置资源
在 Kubernetes 达到技术高峰之前,许多公司要么构建了完全定制的技术堆栈来配置和管理虚拟机和裸金属服务器,要么将开源项目粘合在一起,完成资源生命周期的较小部分。然后 Kubernetes 出现了,作为一个更完整的生态系统,用于管理计算和存储资源,将其作为统一的配置堆栈的前景变得越来越吸引人。然而,像 MySQL 这样的有状态负载仍然落后,因为普遍看法是“你不能在容器上运行数据库。”
仔细界定您的目标
要记住的重要事情是“我们想在这里获得什么具体价值?”Kubernetes 对于无状态负载非常强大,因为它带来了计算资源的弹性和效率。然而,在查看统一的配置堆栈时,将胜利范围缩小到“我们只想使用 Kubernetes 来配置和配置数据库资源系统。”这意味着您需要事先明确,将使用 Kubernetes 配置的数据库工作负载将与无状态工作��载分开管理,需要不同的操作员技能集,并且将以不同方式处理容器故障。
选择您的控制平面
现在有各种 MySQL 操作员,但最佳选择将主要取决于您决定的 Kubernetes 管理 MySQL 的范围。您是否需要一个可以完成所有工作的操作员:配置、故障转移和管理连接到数据库?还是您只需将 Kubernetes 用作配置堆栈,并在服务后使用其他方法来管理数据库?早早决定您对控制平面的期望,因为这将驱动许多更精细的可操作性细节。
更详细的细节
一旦您决定开始使用 Kubernetes 配置 MySQL 资源,您需要在组织中就适合此解决方案的数据规模达成一致。请记住,这现在是一个新的操作模型,用于运行关系型数据库,而在这条少有人走的道路上,随着规模的扩大,一切都变得更加复杂。以下是一些重要事项,您在与 Kubernetes 工程团队合作时(希望您有专门的团队负责此事)需要考虑如何支持有状态工作负载:
- 单个数据库实例支持的最大数据集大小是多少?
- 您是否将卷挂载到容器中,并将容器恢复与数据挂载分开管理?还是数据将成为容器的一部分?
- 将支持的最大查询吞吐量是多少?您将如何管理资源?
- 您将如何确保运行数据库工作负载的 Kubernetes 节点专用于此,而不与无状态、更具弹性的工作负载共享?
- 您将使用什么控制平面来运行数据库实例?它是否是 Kubernetes 本地的?
- 备份将如何工作?恢复过程是什么?
- 您将如何控制并安全地推出配置更改和 MySQL 升级?
- 您将如何升级 Kubernetes 集群本身而不会造成中断?
与您的合作伙伴 Kubernetes 工程团队就这个解决方案的工作方式达成一致意见,将有助于为希望使用该解决方案的功能团队建立完善的 SLO,并在正确传达解决方案解决了什么以及团队仍需自行解决什么方面。
我们在运行 MySQL on Kubernetes 时的建议是投资于学习一个已经在 Kubernetes 生态系统中经过验证和证明的控制平面,比如 Vitess。但在尝试运行之前,也要先爬行。在您的组织中,MySQL 不应该是在 Kubernetes 上运行工作负载的第一个实验对象。始终要证明可行性,并与无状态工作负载团队一起学习尖锐的边缘,然后再尝试运行像 MySQL 这样更复杂的用例。在确定最佳的初始采用用例时,从小数据集(仅在磁盘上几个千兆字节的数据库)和较少关键数据集开始,以使您的团队、Kubernetes 团队和功能团队熟悉在 Kubernetes 上运行有状态工作负载的新操作模型,同时对业务风险较小。
在 Kubernetes 上运行有状态工作负载已经成熟了几年,并且随着那些投入大量工程时间使其更具可行性的公司的重要贡献,它仍处于初期阶段,与直接在 VM 上运行相比,您会发现缓慢和谨慎的采用方法才是长远回报的关键。特别要考虑 MySQL 在 Kubernetes 上的故障模式是什么样的,并问自己:如果一切都出错了,我该如何重新组合?我会丢失数据吗?确保您有答案。
总结
Kubernetes 是当前技术领域中增长最快的基础设施平台之一,而且理由充分。它所带来的工程速度和由云原生基金会支持的丰富生态系统使其成为公司吸引投资的有吸引力的选择。但您应该通过风险和回报的视角来考虑像在 Kubernetes 上运行 MySQL 这样的决策,以及对您的团队和公司的影响。确保您对组织的 Kubernetes 之旅中像数据存储这样的有状态服务的位置有共同的理解。想要利用 Kubernetes 对所有工作负载的现有投资是可以理解的,但这需要与数据存储层的稳定性需求进行良好的平衡。
¹关于在 Kubernetes 上运行数据库工作负载的出色“实战”会议演讲,我们推荐由 Alice Goldfuss 主持的“容器操作员手册”主题演讲。