ITPub博客

首页 > 数据库 > 数据库开发技术 > 数据库分片 — PlanetScale

数据库分片 — PlanetScale

数据库开发技术 作者:ITPUB社群 时间:1970-01-01 08:33:45 0 删除 编辑

什么是分片

分片是通过将数据分散到多个服务器(即分片)上来扩展数据库的过程。对于许多管理 PB 级数据的大型组织来说,分片是首选的数据库扩展解决方案。像 Uber、Shopify、Slack 和 Cash App 这些你喜欢的公司都在使用 Vitess 和 MySQL 进行分片,以扩展其庞大的数据库。

在本文中,你将了解分片的工作原理以及设计高性能分片数据库集群时的注意事项。在此过程中,你将能够与数据库集群图进行交互,通过动手示例让这些概念真正深入人心。

借助我们的 PlanetScale Vitess 集群,你可以利用 Workflows,使显式分片比其他解决方案容易得多。它构建在 Vitess 之上,使你无需在应用程序代码中添加额外逻辑即可进行水平扩展。分片表在应用程序中表现为单个统一的表。注册以亲自尝试,或者如果你想了解它在 PlanetScale 上的工作方式,请查看我们的视频演示。

分片基础

大多数小型 Web 应用程序都会有一个或多个应用服务器,连接到一个单一的、单体式数据库服务器。应用程序将所有持久化数据存储在这个单一服务器上,并向其发送查询以满足应用程序需求。这包括用户账户信息以及应用程序运行所需的其他任何数据。

下面是一个采用此设置的交互式数据库集群。左侧有一个应用服务器,右侧有一个数据库服务器。尝试点击 insert row 和 select row,查看数据被添加到数据库并从数据库中检索出来。

插入的每一行分别具有 user_id、name 和 age。在接下来的几个示例中,我们将使用这种格式的行。

顺便提一下,你可以通过选择下方所需的速度来配置本文中动画的速度。

上述架构非常适合每秒只需要进行几百或几千次查询的低需求系统。

然而,流行的软件应用通常拥有数十万的并发用户。在某些情况下,这些应用需要在数据库中存储数PB的信息,并在高峰时段每秒处理数百万次查询。如此巨大的工作负载要求我们将数据库分散到多台服务器上,而不仅仅是一台。分片(Sharding)是解决这一问题的常用方案。

在分片数据库中,我们将拥有多个独立的数据库服务器,每个服务器存储总数据的一部分。下面你可以看到一个简单的配置,其中应用服务器可以向两个数据库服务器(分片)之一发送查询。尝试再次插入和选择行。对于剩余的交互式可视化,你也可以点击分片中的某一行来删除它,或点击分片的标签来完全清空它。

在这种设置下,运行在应用服务器上的代码必须感知所有分片,知道哪些行存储在哪里,并与每个分片保持连接。只有两个分片时这不是大问题,但当有数百个分片时就会变得复杂。将这种逻辑存储在应用代码中会很快变得混乱且难以维护。

更好的选择是让应用服务器连接到一个中间服务器,我们称之为代理(proxy)。当应用服务器需要使用数据库时,它会向代理发送查询。代理负责将查询路由到正确的分片服务器。

我们在下面展示了这样的配置。继续点击插入行按钮向分片中添加行。你将直观地看到代理服务器如何用于管理插入操作。

PlanetScale 的分片解决方案构建在 Vitess 之上。在 Vitess 中,这些代理服务器被称为 Vitess Gates,简称 VTGates。

填满分片后,你可以看到总共有6行,每个数据库服务器存储3行。当然,真实的分片数据库会在每台服务器上存储数百万甚至数十亿行,但为了学习目的,我们在这里保持较小的规模。

如果你插入了超过六行,你可能会注意到一个或多个分片服务器闪烁红色,表示它们正在过载。如果你插入得非常快,你可能也会看到代理服务器闪烁红色。当查询需求过高或服务器接近其存储容量时,就会发生服务器过载。随着数据量的增长,我们可以添加更多分片来支持它。这个过程被称为重新分片(resharding)。让我们将这个数据库集群从两个分片增加到三个分片。

同样重要的是要考虑代理也需要时间来处理查询。考虑下面的分片配置。我们有很多数据库容量,但只有一个代理服务器。尝试快速连续插入大量行。

你应该会看到代理闪烁红色,插入操作在它下面排队,而不是一次性全部处理。这是因为代理服务器达到了它可以处理的并发查询容量上限,不得不将其他插入操作排队。这种增加的延迟对于生产数据库系统来说是不可接受的。为了解决这个问题,我们可以添加更多代理服务器。再次尝试多次点击插入行。

由于我们有了更多的代理容量,我们减少了需要排队查询的可能性。在设计大规模分片数据库系统时,重要的是要了解需求,并选择正确数量的代理和分片来处理工作负载,即使在高峰时段也是如此。

如果没有合适的工具,将数据库从非分片迁移到分片的过程可能具有挑战性。一些组织花费数月时间从非分片架构过渡到分片架构。幸运的是,PlanetScale 构建了诸如 Clusters 页面和 Workflows 等工具,使得 MySQL 数据库的分片变得简单,即使该数据库正在处理实时生产流量。

分片策略

现在你已经了解了分片数据库的基本概念。接下来的问题是:数据是如何分片的?

在分片数据库架构中,最重要的考虑因素之一是分片策略。分片策略是一组用于确定哪些数据行分配到哪些分片的规则。在上述示例中,每一行被发送到哪个分片是随机选择的。通常,你的分片策略会涉及选择一个分片键:即用于确定每一行数据归属位置的列。你选择的分片策略和分片键对数据在各分片间的分布均匀程度以及查询性能有着巨大的影响。

让我们考虑几种方案并权衡它们的优缺点。

范围分片

在范围分片策略中,代理层根据预定义的值范围来决定每一行应该去往哪里。

例如,假设我们有四个分片,并希望使用 user_id(每行第一列中的数字)作为分片键。在这样的设置中,我们可以规定所有 ID 为 1-25 的行进入第一个分片,26-50 进入第二个分片,51-75 进入第三个分片,76-100 进入第四个分片。下面的数据库集群就是按这种方式配置的。试着插入一些行,你注意到了什么?

前 25 次插入全部进入了第一个分片,导致一个热分片(负载过重的分片)和三个冷分片(负载不足的分片)。如果我们继续插入,其他分片也会出现同样的问题。如果我们的 ID 是单调递增的(就像这里的情况),使用基于范围的简单分片方式通常是一个糟糕的主意。

让我们尝试用第二列 name 进行范围分片。这里,我们将所有以 a-f 开头的名字放在第一个分片,g-m 放在第二个,n-u 放在第三个,v-z 放在第四个。让我们尝试更多的插入。

哎呀,更多问题了!我们的用户中没有人的名字在 v-z 范围内,导致一个分片被浪费。这种分片方案只有在用户的名字在字母表中完美均匀分布时才有效。这在实践中很少成立。名字的首字母会因名字流行度和国籍等因素而变化。

最后,让我们尝试对第三列 age 使用范围分片。这里,我们将所有 0-24 岁的放在第一个分片,25-49 放在第二个,50-74 放在第三个,75-200 放在第四个。继续插入一些行。

同样,我们在这里遇到了问题。我们的绝大多数用户年龄在 25-74 岁之间。两个分片承载了大量流量而变得繁忙,而另外两个则相当冷清。我们可以调整这些范围以更好地适应我们的用户群体,但随着时间推移用户年龄发生变化时会发生什么呢?一定有更好的方法。

哈希分片

最流行的分片策略之一是哈希分片。在哈希分片中,我们选择一个列作为分片键,并为每一行需要插入的数据生成该值的加密哈希。每个分片负责存储一定范围哈希值的行,这个过程由代理服务器控制。

举例来说,假设我们将分片键列通过一个简单的算法,该算法总是产生0到100之间的哈希值。哈希值0-25分配到第一个分片,26-50到第二个,51-75到第三个,76-100到第四个。哈希的一个优点是相似的输入可以产生非常不同的输出。我们可能传入名字“joseph”得到哈希值45,但名字“josephine”产生28。相似的名字,完全不同的哈希值。这意味着相似的值可能最终落在完全不同的服务器上,这是一个有助于数据均匀分布的好特性。

下面我们有一个配置为在name列上使用哈希分片的服务器。插入几行数据看看它们是如何分布的。

这看起来效果不错,但这是最优的吗?使用哈希分片时,我们如何知道选择哪一列作为分片键?

理想情况下,我们希望选择具有高基数(cardinality)的列。name列不是理想的选择。可能会有非常流行的名字,即使使用哈希,我们最终也可能得到比其他服务器更热的服务器。

通常像user_id这样的列是一个好选择,因为每个值都是唯一的。我们还获得了哈希速度的额外好处。对固定大小的整数进行哈希比可变宽度的名称字符串更快。

这里是一个在id列上使用哈希分片的例子。

我们仍然实现了良好的数据分布,但现在使用的是高基数和固定大小的值。

其他策略

范围分片和哈希分片是覆盖许多场景的好选择。然而,还有其他选项。像Vitess这样的软件还支持查找分片(lookup sharding)甚至自定义分片函数。

在查找分片中,开发者可以设置一个表,包含将传入数据映射到适当分片所需的信息。当查询这些表时,会引用这个表。

Vitess 还支持自定义分片函数,允许开发者编写自己的函数,以列为输入,并生成自定义输出,让系统知道需要使用哪些分片。

跨分片查询

设计分片数据库集群时的另一个重要考虑因素是每个查询需要多少个分片来完成。理想情况下,大多数单个查询可以由单个分片处理。考虑以下对 user 表的查询,该表包含前面描述的 user_id、name 和 age 列:

SELECT
name
, age
FROM
user
WHERE
user_id
=
1
;

假设 user_id 是我们的主键,这个查询只会选择一行,而这一行应该只存在于一个分片上。这意味着代理层只需要向一个分片请求即可获得查询结果。如果你在之前的某些交互式示例中点击过 select row 按钮,你应该已经看到了这一点,因为它只选择了单独的行。

现在让我们考虑数据库中额外的 steps 表。该表用于存储用户手机和手表上的健康应用所追踪的每日步数统计数据。每一行分别包含 user_id、step_count 和 date。我们也希望对这个表进行分片。

这些数据的一个常见用途是用户请求其步数历史记录,以便在折线图上查看。在 SQL 中,获取这些数据的查询如下所示:

SELECT
step_count,
date
FROM
steps
WHERE
user_id
=
1
;

这个查询可能返回许多行。代理需要多少个分片才能找到所有这些行?这取决于我们最初是如何对 steps 表进行分片的!

让我们考虑一下,如果我们在 step_count 列(第二列)上进行了范围分片,会发生什么。尝试插入一些行,然后点击 select where id == 1 来获取 id = 1 的用户的所有条目。

如你所见,SELECT 查询可能需要跨多个分片才能完成!这被称为跨分片查询。跨分片查询对系统性能不利,应尽可能避免。当多个分片需要完成单个查询时,这会给数据库集群增加过多的网络和 CPU 开销。

更好的解决方案是基于 user_id 进行分片。这样,给定用户的所有条目都将存在于同一个分片上,这将使我们能够避免这种跨分片查询。在下面试一试:

这将为该查询带来更好的性能。

更新

在选择分片键列时,另一个需要考虑的因素是更新频率。每当分片键列中的值发生变化时,为了维护分片策略的完整性,该行可能需要从一个分片移动到另一个分片。

再次考虑使用 step_count 还是 user_id 作为分片键的选择。step_count 是一个可能频繁变动的列。在一天中,随着用户不断行走,步数会发生变化。我们甚至可能希望允许用户手动更新过去几天的 step_count。每次发生变更时,数据库集群都必须重新评估该行属于哪个分片。这意味着单行数据可能会随着时间的推移在不同分片之间迁移。

相比之下,使用 user_id 进行分片。一旦我们为用户分配了唯一标识符,它很少会被更改。因此,一旦我们确定了某行数据所属的分片,它就会留在该分片上。唯一需要移动它的情况是需要扩展或缩减分片数量时。

在选择某列作为分片键之前,务必花时间考虑该列的易变性。

延迟

添加代理层确实有一个缺点:增加了延迟。引入代理后,进入数据库的请求多了一次网络跳转。考虑一下没有代理时,应用向数据库发送请求并返回的过程。

现在,将其与中间有代理时完成同样往返所需的时间进行比较。

显然,耗时更长!

然而,通过合理考虑服务器位置,这个问题可以被最小化。如果代理和所有分片都位于同一数据中心,增加的延迟可以降低到 1ms 或更少。对于绝大多数应用来说,增加 1ms 的延迟换来分片架构带来的可扩展性是值得的。例如,Slack 使用 Vitess 运行着大规模分片数据库集群,报告的平均查询延迟仅为 2ms。

数据持久性

副本是连接到主数据库服务器并复制其所有数据的数据库服务器。无论你拥有的是 GB 级还是 PB 级数据,运行副本始终是一个好主意。

首先,它们提高了数据的持久性。如果主服务器宕机,你仍然在副本上拥有数据的副本。添加副本降低了因硬件故障而丢失数据库的可能性。

下面是一个使用复制(replication)的分片数据库集群示例。每个分片有一个主节点(primary),然后每个分片还有两个额外的副本(replica),用于数据持久性。

副本(replica)对于保持系统高可用性也很有用。如果你的主服务器宕机且没有副本,你的应用程序可能会经历数小时甚至数天的停机,直到服务器被修复或更换并重新上线。如果你配置了副本,流量可以切换到其中一个副本,让你的应用立即恢复运行。

PlanetScale 运行在开源分片解决方案 Vitess 上。Vitess 允许你构建分片数据库集群,并自定义每个分片的副本数量。它还有一个编排组件,可以自动检测服务器故障并快速替换宕机的主节点。这可以在保持高可用性的同时确保数据安全。

快速备份

对于大规模数据库,备份时间很容易失控。

假设你有一个未分片的 4 TB 数据库运行在单台服务器上。现在,假设我们的网络规定每台服务器的备份速度可以达到 100 MB/s。在这种情况下,备份这个数据库将需要(4TB / 100MBps * 60s * 60m)= 约 11 小时。这太久了!

点击下面的运行备份按钮,查看一个小规模示例。

或者,如果这 4TB 数据分散在 4 个分片上,每个分片存储 1TB 呢?每台服务器可以同时以 100MBps 的速度捕获备份,使集群能够以 400MBps 的速度备份数据,只需约 2.7 小时。再次点击运行备份按钮。

注意备份完成的速度快了多少。这完全归功于我们将数据分散到多个分片上

有关分片如何帮助提升备份性能的更多细节,请查看这篇博客文章。对你的数据库进行分片会带来许多好处,这里并未涵盖全部内容。查看我们的另一篇博客文章了解更多细节。

结论

分片是扩展数据库系统的绝佳解决方案。然而,要让分片数据库表现良好,需要仔细关注分片策略、分片键选择和查询优化。这里涵盖的概念奠定了基础,帮助你使用 Vitess 和 PlanetScale 等技术构建高质量的分片数据库。

如果你有兴趣开始使用分片数据库,可以注册一个 PlanetScale 账户,并按照我们的分片快速入门自行尝试。如果你希望获得我们解决方案团队的实操支持,请联系我们。

在寻找分片的 Postgres?Neki 是面向 Postgres 的水平分片方案,由 Vitess 背后的团队打造。注册一个 PlanetScale 账户即可立即使用 Neki,或从 Neki 快速入门开始。


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:PlanetScale Blog

原创地址:https://planetscale.com/blog/database-sharding

原文作者:PlanetScale Blog;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-11-21

  • 博文量
    1122
  • 访问量
    547362