第11章-2 使用分片扩展写入

        上一篇:《第11章1 扩展 MySQL》,接着了解使用分配扩展写入

排队机制

        当使用设计上倾向于一致性而不是可用性的数据存储来扩展写事务时,扩展应用程序层会变得复杂得多。写入一个源数据节点的多个应用程序节点将导致数据库系统更容易出现锁定超时、死锁和必须重试的写失败,所有这些最终将导致影响客户的错误或不可接受的延迟。

        在研究我们接下来要讨论的数据分片之前,应该先检查数据中的写入热点,并考虑是否所有的写入都真的需要主动持久化到数据库中。其中的一些是否可以被放入队列中,并在一个可接受的时间范围内写入数据库?

        假设有一个存储大量客户历史数据的数据库。客户偶尔会发送API请求来检索此数据,但你还需要安排一个API来删除此数据。你似乎可以用越来越多的副本来提供读API调用的服务,但是删除怎么办呢?HTTP RFC提供了一个响应代码,“202已接受”,可以先返回该响应代码,然后将请求放在队列中(例如,Apache Kafka或Amazon简单队列服务),并控制处理这些请求的速度,以确保删除调用不会直接导致数据库超载。

        这显然与意味着“请求已立即完成”的200响应代码不同。人人都知道,与产品团队的协商对于保证API的可信赖和可实现是至关重要的。200和202响应代码之间的区别在于,为了支持更多的并行写操作,需要对数据进行哪些分片处理工作。

        如果将队列应用于写负载,那么一个重要的设计选择是,预先确定这些调用在被放入队列后所期望完成的时间范围。监控请求在队列中花费时间的增长,对于此策略何时运行,以及你何时确实需要开始分割此数据集以支持更多的并行写负载,能提供重要的衡量指标。

这可以通过分片来实现,我们接下来将讨论分片。

使用分片扩展写入

        如果不能通过最佳的查询和排队写入来管理写入流量的增长,那么分片将是剩下的选项。分片意味着将数据切分成不同的、更小的数据库集群,这样就可以同时在更多的源主机上执行更多的写入操作。可以进行两种不同类型的分片或分割:功能分割或数据分片。

        功能分割(Functional partitioning),或称为职责划分,意味着将不同的节点用于不同的任务。其中的一个例子可能是将用户记录放在一个集群上,并将其计费放在另一个集群上,这种方法允许每个集群单独扩展。用户注册量的激增可能会给用户集群带来压力。基于使用单独的系统,计费集群负载较少,从而允许进行客户计费操作。相反,如果计费周期日是月初的第一天,你可以清楚地知道运行计费操作不会在其他时间影响用户注册。

        数据分片(Data sharding)是当今扩展超大型MySQL应用程序最常见和最成功的方法。通过将数据切分成更小的部分或分片,并将它们存储在不同的节点上,可以达到拆分数据的目的。

        大多数应用程序只对需要分片的数据进行切分——通常是数据集中增长非常大的部分。假设你正在建立一个博客服务。如果你期望有1000万名用户,那么可能不需要对用户注册信息进行分片,因为可以将所有用户(或其活动子集)完全放在内存中。另外,如果期望有5亿用户,那可能需要进行数据分片。用户产生的内容,比如博文和评论,在各自的场景中几乎可以肯定需要分片,因为这类记录要大得多,而且数量更多。

        大型应用程序可能有多个逻辑数据集,可以以不同的方式被切分。可以将它们存储在不同的服务器集上,但没必要这样做。还可以通过多种方式切分相同的数据,这取决于访问数据的方式。

        当计划“只切分需要分片的数据”时,要小心。这个概念不仅需要覆盖快速增长的数据,还需要覆盖逻辑上与之相关的数据,这些数据将定期被查询。如果是基于user_id字段进行切分,但另有一组更小的表在大部分查询中都通过相同的user_id来联接它,那么将这些表一起分片是有意义的,这样可以保证大部分应用查询每次仅在单个分片中进行,能避免跨数据库联接。

选择切分方案

        分片技术最重要的挑战是查找和检索数据。如何查找数据取决于如何切分数据。有很多方法可以做好这件事,但其中一些方法更为可取。

        目标是使最重要和最频繁的查询接触到尽可能少的分片(请记住,可扩展性的原则之一是避免节点之间的交叉访问)。该过程中最关键的部分是为数据选择一个或多个分片键。分片键决定了每个分片上应该分布哪些行。如果知道对象的分片键,就可以回答下面这两个问题:

        ● 我应该把这些数据存储在哪里?

        ● 在哪里可以找到我需要获取的数据?

        稍后,我们将展示选择和使用分片键的各种方法。现在,让我们来看一个例子。假设我们参考MySQL的NDB集群中的做法,使用每个表的主键哈希来将数据切分到所有分片。这是一个非常简单的方法,但它不能很好地扩展,因为经常需要为想要的数据而检查所有分片。例如,如果想要用户3的博客文章,在哪里可以找到?它们可能均匀地分散在所有的分片上,因为它们是按主键而不是按用户划分的。使用主键哈希让我们更容易知道数据被存在哪里,但可能会使获取数据变得更加困难,这取决于需要哪些数据以及是否清楚主键是什么。

        我们总是希望将查询本地化到一个分片。当水平切分数据时,总是希望避免必须通过跨分片查询来完成任务。跨分片关联数据将增加应用层的复杂性,并从一开始就削弱了数据分片的好处。使用数据分片的最坏情况是,当不知道所需的数据存储在哪里时,需要扫描每个分片来找到它。

        一个良好的分片键通常是数据库中一个非常重要的实体的主键。这些键决定了分片的单元。例如,如果按用户ID或客户端ID切分数据,则分片的单元是用户或客户端。

        一个很好的开展这项工作的方法是,用一张实体-关系(E-R)图或一个显示所有实体及其关系的等效工具来绘制数据模型。尝试调整图表布局,从而使相关的实体紧密相连。通常可以直观地查看这样的关系图,并找到可能会错过的分片键的候选项。不过,不要只看图表,还要考虑应用程序的查询。即使两个实体在某种程度上是有关系的,如果很少或从不以该关系进行关联,那么也可以打破此关系来实现分片。

        根据实体关系图中的连接程度,某些数据模型比其他数据模型更容易进行切分。图11-2的左图描述了一个容易切分的数据模型,而右图所示的模型则难以切分。

        图11-2:两个数据模型,一个容易切分,另一个难以切分(感谢HiveDB项目和Britt Crawford提供了这些优雅的图表)

        左边的数据模型很容易被切分,因为它有许多连接的子图,这些子图主要由只有一个连接的节点组成,可以相对容易地“切断”子图之间的连接。右边的模型很难被切分,因为它没有这样的子图。幸运的是,大多数的数据模型看起来更像左图,而不是右图。

        在选择分片键时,尝试挑选某些能尽可能避免跨分片查询的键,但也要使得分片足够小,这样将不会出现不成比例的大数据块带来的问题。如果有可能,让分片最终均匀地小,如果做不到,至少要小到很容易通过将不同数量的分片组合在一起来实现平衡。例如,如果应用程序仅在美国运行,而你想把数据集分成20个分片,那么也许不应该按州划分,因为加州拥有庞大的人口数量。但可以按县或电话区号来切分,因为即使人口分布不均匀,这种划分方式也已经足够让你可以选出20个集合,使每个集合的总人口数大致相同,你还可以按亲和关系来选择,这有助于避免跨分片查询。

多个分区键

        复杂的数据模型使数据分片更加困难。许多应用程序有多个分片键,特别是当数据中有两个或更多重要的维度时。换句话说,应用程序可能需要从不同的角度看到一个有效的、连贯的数据视图。这意味着可能需要在系统中至少将一些数据存储两次。

        例如,你可能需要通过用户ID和博文ID这两者来切分博客应用的数据,因为这是应用程序查看数据的两种常见方式。可以这样想:经常需要看到一个用户的所有文章以及一篇文章的所有评论。按用户切分将无法找到文章的评论,按文章切分不能找到某个用户的文章。如果需要以两种类型的查询来访问单个分片,则必须以两种方式来切分。

        仅仅因为需要多个分片键,并不意味着需要设计两份完全冗余的数据存储。让我们看另一个例子:一个社交网络的读书俱乐部网站,此网站的用户可以对书籍发表评论,而该网站可以显示对一本书的所有评论,以及用户已经阅读和评论的所有书籍。

        可以为用户数据构建一份切分的数据存储,并为书籍数据构建另一份。评论数据同时具有用户ID和书籍ID,因此它们跨越了不同的分片。可以按用户数据来存储评论,然后按书籍数据仅存储评论的标题和ID,而不是完全复制评论。这可能足以在不访问两份数据存储的情况下呈现书籍评论的大多数视图,如果需要显示完整的评论文本,则可以从用户数据存储中检索它。

跨片查询

        大多数分片应用程序都会有一些查询需要聚合或联接多个分片的数据。例如,如果读书俱乐部网站显示了最受欢迎或最活跃的用户,那么它必须根据定义访问每个分片。实现数据切分最困难的部分是使这类查询正常工作,因为应用程序所认为的单个查询需要被分裂成多个查询且并行执行,每个分片一个。一个好的数据库抽象层可以帮助减轻查询的负担,但即便如此,这样的查询也比分片内查询慢得多,且代价更高,因此主动缓存通常也是有必要的。

        如果跨分片查询是例外情况而不是常态,你所选择的分片方案就是一个好方案。你应该努力使查询尽可能简单,并被包含在一个分片内。对于那些需要跨分片聚合的情况,我们建议从应用程序逻辑的整体加以考虑。

        跨分片查询也可以从汇总表中获益。可以通过遍历所有分片并在完成后将结果冗余地存储在每个分片上来构建汇总表。如果觉得在每个分片上复制数据太浪费,可以将汇总表合并到另一个数据存储中,这样其就只被存储一次。

        非分片的数据通常存在于全局节点中,并使用大量的缓存来使其免受负载的影响。当一致的数据分布很重要,或没有好的分片键时,一些应用程序会使用基本上随机的分片,分布式搜索应用就是一个很好的例子。在这种情况下,跨分片查询和聚合是常态的,而不是例外。

        跨分片查询并不是使用分片技术后唯一的难题,维护数据的一致性也很困难。外键不能跨分片工作,所以通常的解决方案是根据应用程序的需要检查引用完整性,或者当分片的内部一致性很重要的时候可以在分片中使用外键。可以使用XA 事务(参见链接41 https://oreil.ly/Z5gSe),但由于开销问题,这在实践中并不常见。

        还可以设计间歇性运行的清理程序。例如,如果用户的读书俱乐部账户过期,不必立即删除,可以编写一个定期作业来从每本书的分片中删除用户的评论,而且还可以构建一个定期运行的检测脚本,用来确保不同分片间的数据是一致的。

        现在我们已经解释了将数据分到多个集群的不同方法,以及如何选择分片键,接下来我们介绍两个最流行的开源工具,它们可以帮助数据分片与功能分割的顺利进行。

Vitess

        Vitess是面向MySQL的一个数据库集群系统。它起源于YouTube的内部项目,后来成长为PlanetScale这个独立的产品,以及一家由Jiten Vaidya和Sugu Sougoumarane共同创立的公司。

Vitess支持许多特性:

        ● 支持水平分片,包括数据分片。

        ● 拓扑结构管理。

        ● 源节点故障切换管理。

        ● schema变更管理。

        ● 连接池。

        ● 查询重写。

让我们来探索Vitess的架构及其组件。

Vitess架构概述

                        图11-3是Vitess网站上的一张图,展示了其架构的各个部分。

以下是一些需要了解的术语。

常用术语:

Vitess pod

        对一组数据库和Vitess相关部件的通用封装,该部件支持分片、拓扑结构管理、schema变更管理和对这些数据库的应用程序访问。

VTGate

        为应用程序与操作员控制数据库实例访问提供的服务,以供其尝试管理拓扑、添加节点或切分部分数据。它类似于前面描述的架构体系中的负载均衡器。

VTTablet

        在Vitess管理的每个数据库实例上运行的代理。它可以接收来自操作员的数据库管理命令,并代表操作员执行它们。

Topology(元数据存储)

        在给定的pod中保存由Vitess管理的数据库实例清单以及相应的信息。

vtctl

        对Vitess pod进行操作更改的命令行工具。

vtctld

        用来进行相同管理操作的图形化界面。

        Vitess的体系结构从一个一致的拓扑存储开始,该存储包含对所有集群、MySQL实例和vtgate实例的定义。这种一致的元数据存储在管理拓扑结构变更方面起着至关重要的作用。当操作员想要更改由Vitess管理的集群的拓扑结构时,实际上是通过一个名为vtctl的服务将命令发送到该数据存储,然后该数据存储将此命令的组件操作发送到vtgate。

        Vitess提供了可以在Kubernetes中部署vtgate层和元数据存储的数据库操作器(operator)。在像Kubernetes这样的平台上设置其控制平面,可以提高对单点故障的恢复能力。

        Vitess最大的优势之一是其关于如何扩展 MySQL 的理念(参见链接42 The Vitess Docs | Scalability Philosophy),其中包括以下内容。

优先使用更小的实例

        按功能、水平方式或两者兼而有之来分割数据。当故障发生时,实例越小,所造成的影响面也越小。

通过复制和自动写故障切换来增强弹性

        Vitess并没有承诺通过多写节点技术来实现“100%写入可用”。而是在发生故障时,自动进行写入故障切换,并管理拓扑更改和对数据库节点的应用程序访问,以使写停机时间尽可能短。

通过半同步复制保证持久性

        Vitess强烈建议使用半同步复制(相对于默认的异步复制),以确保在向应用程序确认写入操作之前,此写入已被数据库层中的多个节点持久化。这是为了保证持久性而在延迟方面采取的一个重要折中方案,当Vitess需要以一种计划外的方式对写入主机进行故障切换时,这种折中方案会带来好处。

        这些体系结构原则有助于维持业务流量的指数级增长,并让基础设施的数据库层具有更大的弹性。无论具体是将Vitess还是其他解决方案用作架构的一部分,都应该注意这些最佳实践。

将技术栈迁移至Vitess

        Vitess是一个用于运行数据库层的稳定平台,而不是一个临时的解决方案。因此,在将它作为数据库的访问层之前,需要深思熟虑地计划如何实现这种转换。

具体来说,在评估将Vitess作为一个可能的解决方案时,一定要考虑以下迁移步骤。

1. 测试并记录向整个系统引入的延迟。

        在应用程序技术栈中引入像Vitess这样的复杂堆栈肯定会增加一些延迟,特别是在考虑强制使用半同步复制时。确保这种权衡已被很好地记录,并与下游依赖团队进行过明确沟通,以便他们在构建依赖此数据库体系架构的SLO时做出明智的决策。

2. 使用金丝雀部署模型(参见链接43 https://oreil.ly/ldtnN)。

        在生产过渡期间,可以将vttablet配置为“外部管理”。当通过应用程序节点组缓慢地增加连接更改时,这允许应用既可以通过vttablet也可以直接连接到数据库服务器。

3. 开始分片

        一旦所有的应用程序层访问都是通过vtgate/vttablet而不是直接访问MySQL的,就可以开始使用Vitess的完整功能集来将表分离到新的集群,水平切分数据以获得更高的写吞吐量,或者简单地添加副本以获得更多的读负载容量。 (Morgan Tocker在2019年Kubecon大会上的一次演讲中详细解释了这一部署策略)

        Vitess是一个强大的数据库访问和管理产品,从最初在谷歌被研发出来后已经走过了很长的路。它已经证明了其实现显著增长和作为弹性数据库基础设施的能力。然而,这种能力和灵活性是以增加复杂性为代价的。Vitess不像传输流量的负载均衡器那么简单,你需要在业务需求和引入并维护像Vitess这样复杂数据库管理工具的成本之间做出取舍。

        上一篇:《第11章-1 扩展 MySQL

        下一篇:《第11章-3 ProxySQL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天狼1222

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值