本文翻译自 How does database sharding work?,如有疑问,请联系译者

了解什么是数据库分片,分片如何工作的,以及一些常见的分片框架和工具。

how-does-database-sharding-work

如果你使用过 Google 或 YouTube,那么你很可能已经访问过分片数据。分片通过将数据分区存储在多个服务器上,而不是将所有内容放在一个巨大的服务器上,以实现扩展数据库的目的。这篇文章将介绍数据库分片的工作原理、思考如何给你自己的数据库分片,以及其他一些有用的、可以提供帮助的工具,尤其是针对 MySQL 和 Postgres。

分片是扩展关系数据库的重要方式

试想以下场景:本季度你第三次扩大了 MySQL 版本 RDS 的实例规模,而 CFO 刚刚在会上上花了 30 分钟来“讨论预算”。也许是时候横向扩展而不是纵向扩展了! [1] RDS 中的读取副本似乎很简单,但读取数据只是问题的一半。一个心力憔悴的开发者该怎么办?

分片——这个术语可能最初来自视频游戏——一种扩展关系数据库的方式。你可能以前看过这张表格,这张表描述的是如何通过横向扩展来帮助你处理存储在单个服务器上的用户表:

user_id first_name last_name email
ZpaDr20TTD4ZL7Wma Peter Gibbons peter@initech.net
bI32htQ1PsEQioC7G Bill Lumbergh bill@initech.net
99J3x257SGP7J4IkF Milton Waddams stapler@initech.net
0SH0pyi9bO5RM4I03 Lawrence two@onetime.com

并将其转换为存储在 2 个(或 1,000 个)服务器上的用户表:

user_id first_name last_name email Server
ZpaDr20TTD4ZL7Wma Peter Gibbons peter@initech.net Server A
bI32htQ1PsEQioC7G Bill Lumbergh bill@initech.net Server B
99J3x257SGP7J4IkF Milton Waddams stapler@initech.net Server B
0SH0pyi9bO5RM4I03 Lawrence two@onetime.com Server A

但这只是一种类型的分片(行级或水平)。有多种不同的方法可以跨服务器分割数据,以最好地匹配您的业务和数据模型的工作方式。例如,垂直分片是指在架构或表级别拆分内容。稍后会详细介绍!

分区这个概念已经提出很久了,尤其是在 OLAP 的设置中,主要是作为一种提高查询速度的机制。筛选 HDFS 分区以查找丢失的快照的噩梦充斥着我的睡眠时间表……无论如何,分片采用了这个概念并将其应用于分布式系统:除了将数据分割成逻辑组之外,让我们将这些组放置在多个能够对彼此通信的服务器上。甚至 Oracles 数据库也这样做

自关系型数据库存在以来,它们就被设计在单个服务器上运行。一部分原因是因为设计本如此,另一部分也是因为基本的物理规律,正确地分片你的数据是一件相当困难的事情。

关系型数据库中的分片是如何工作的

要对数据库进行分片,你需要做以下几件事情:

  1. 决定分片方案 —— 哪些数据需要被拆分,以及如何拆分?如何组织这些数据?
  2. 设定基础设施指标 —— 你要分片到多少台服务器?每一个有多少数据?
  3. 创建路由层 —— 应用程序如何知道在哪里存储新数据并查询现有数据?
  4. 计划并执行迁移工作 —— 如何以最短的停机时间从单个数据库迁移到多个数据库?

每个人的数据模型和业务限制都不相同,所以没有一份硬性的操作指南。现在我们来深入了解一下。

分片方案和算法

如何决定对数据分片(也称为分区策略),应该是你的业务运作方式和查询负载的集中位置的直接函数。对于一个 B2B SaaS 公司,每个用户都属于一个组织,通过拆分组织层级的数据来进行分片可能是有意义的。如果你是一个做消费者业务的公司,你可能想要基于随机哈希来进行分片。 Notion 通过简单地分割团队 ID 来手动对他们的 Postgres 数据库进行分片。所有这一切都表明,分片可以很简单,也可以很复杂,取决于你的设计。

有几种流行的“算法”可以决定哪些行存储在一起,以及存储在哪些服务器上:

  • 基于哈希的分片(也称为基于密钥) —— 从行中获取一个值,对其进行哈希处理,然后将哈希值桶发送到同一服务器。选择散列的任何列都是你的分片键
  • 基于范围的分片 —— 选择一列,创建范围,并根据这些范围分配分片。对于(某种程度上)随机或均匀分布的数值列最有用。
  • 基于目录的分片 —— 选择一列,手动分配分片,并维护一个查找表,以便你知道每行的存储位置。

如果你的分片方案不是随机的(例如基于哈希的方案),你就会了解到为什么查询分析和了解负载分布如何可以是有用的。

想象一下你是亚马逊,你想要对存储客户订单的 MySQL 数据库进行分片。表面上看,似乎没有任何有意义的聚类:当然,你有一些经常订购大量商品的客户,但这种订单数量(以及在购物过程中相关的读取)基本上是随机的。使用基于哈希的分片方案,并使用订单 ID 作为分片键可能是有意义的。

分片方案的很大一部分是考虑哪些表存储在一起。分布式系统中跨数据库的联接非常困难且成本高昂,因此理想情况下,回答特定查询所需的所有数据都存在于同一台物理计算机上。对于亚马逊来说,这意味着 orders 表和包含 orders 表中产品的 products 表需要物理上位于同一位置。这还需要增量维护:如果客户下了新订单,则该订单的产品数据需要包含在新分片中,以便以后可以快速读取。

分片维护是扩展关系数据库的一个经常被低估的部分。根据你的分区策略,你最终可能会遇到热点,其中集群中的特定服务器要么存储太多数据,要么处理太多数据吞吐量太大。在我们的亚马逊示例中,这可能是因为一家大型企业开始订购一吨的东西,并且他们的所有数据都在一台服务器上。管理这些热点、重新分配数据和负载以及重新组织分区策略以防止将来出现问题是你在分片时注册的一部分。

决定使用哪些服务器

设置好分片方案后,就可以决定要在多少台机器上存储数据以及需要它们有多大。这里没有公式;主要取决于你的预算、对未来数据库负载的预测、云提供商等。

一种常见的方法是最大化灵活性。首先从少量主机开始,然后根据需要添加更多主机。为了保持服务器上分片的均匀分布,每次添加主机时都需要重新平衡。这就是为什么公司喜欢选择可被许多较小数字整除的多个分片;它允许逐步扩展服务器数量,同时保持平稳、均匀的分布。

将查询路由到正确的数据库

当你的数据分布在多个数据库(想象一下其中 20 个)时,你的应用程序如何知道要查询哪个数据库?你需要构建某种决定的路由层。但应该怎么做呢?

对于那些从头开始构建分片的人来说,最常见的答案是在应用程序层。你需要在应用程序代码中构建逻辑,以决定特定查询连接到哪个数据库(和模式),以该查询内的数据及其在分片方案中的位置为条件。逻辑看起来像这样:

if data.sharding_key in database_1.sharding_keys:
  …connect to database_1
else if data.sharding_key in database_2.sharding_keys:
  …connect to database_2

根据你对数据进行分区的方式以及正在使用的物理机器/数据库的数量,此逻辑可能相对简单并存储在 JSON blob、配置文件等中。更常见的是,团队将使用某种数据库中的键值存储或查找表。重要的是让将一段数据与其目的地相关联的信息编码在某处,以便应用程序知道去哪查询。

第一次构建这个实际上并不那么困难;但是随着时间的推移,**运营维护才会成为真正的问题。如果你将分片从一个数据库移动到另一个数据库、重新平衡、添加新机器、删除机器、更改任何数据库属性……你将需要更新该应用程序逻辑来解释它。 ProxySQL 并不是一个成熟的解决方案,但它可以归类为粗略的“分片路由”服务。

计划并执行迁移

一旦你注意到了上述所有问题,并且有了空数据库的物理服务器,以及一个在应用逻辑中进行路由的计划,你将面临一个古老的问题,即如何在不(太多)停机的情况下迁移。与迁移到单个新数据库提供程序(可能)更为直接的迁移不同,迁移到分片引入了更多可能出错的事情,以及更多的方式。

Notion 的工程团队在 如何实现分片的帖子 中提出了一个用于思考迁移的有用框架:

  1. 双写:传入的写入操作会同时应用于旧数据库和新数据库。
  2. 回刷:一旦双写开始,将旧数据迁移到新数据库。
  3. 校验:确保新数据库中数据的完整性。
  4. 切换:实际上切换到新数据库。这可以逐步完成,例如双重读取,然后迁移所有读取。

每个步骤仍然会引入停机的可能性;不过这是在这个规模的变化中你必须承担的风险。

分区框架和工具

虽然许多团队确实会从头开始构建他们所选择的数据库的分片方案,但是有一些工具可供使用,尽管它们可能不像它们构建的数据库软件那样成熟。

Vitess

Vitess是 YouTube 在需要对 MySQL 进行分片时开发的,现在可供我们使用。它基本上是在 MySQL 之上的一个层,提供分片和许多其他与大型工作负载相关的有用工具:连接池,动态重新分片和负载均衡,以及监视工具等等。如果想了解 Vitess 如何改进普通 MySQL 的技术概述,请查看他们的比较

据我所知,Vitess 是关系型数据库最成熟、最流行的开源分片层。它在多年内为 YouTube 处理了所有数据库流量,并且在Slack、GitHub、NewRelic、Pinterest、Square 等公司的生产环境中使用。

小记

在每个PlanetScale 数据库的背后都使用了 Vitess。如果你正在寻找一个无痛分片解决方案,我们可以帮助。与我们联系,我们会尽快与您联系。

Citus

Citus是为 Postgres 实现的 Vitess,但它缺少一些更炫酷的功能。它是开源的,被设计为 Postgres 扩展,可以作为单个节点或多个节点运行。它目前在 Algolia、Heap、Cisco 等公司的生产环境中使用。他们的文档为选择分片方案提供了很好的一般建议,无论是 Citus 还是其他方案。

无服务器数据库浪潮

我想更根本的问题是:为什么你不使用一个可以为你进行分片的数据库?在过去的几年中,所谓的“无服务器”数据库获得了更多的关注。从臭名昭著的Spanner 论文开始,许多人一直在思考如何使分布式系统本身成为数据库的本地功能,其中最重要的是CockroachDB。你甚至可以在 GCP 上运行cloud Spanner

你正在PlanetScale 网站上阅读这篇博客。他们销售一个基于 MySQL 和 Vitess 构建的本地分片数据库。我不是 PlanetScale 的员工,但我非常支持他们所做的事情,特别是将数据库的重点从基础设施维护转向开发人员体验

问题开始变成:如果你正在向像 AWS 这样的服务提供商支付费用来为你运行数据库,为什么你还忙着想要扩展数据库?我认为这是云服务提供商应该自问的一个好问题。