一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

让 768 台服务器看起来像 1 台 : PlanetScale

时间:2026-07-17 09:37:56 编辑:袖梨 来源:一聚教程网

对某些人来说,这看起来像是很多台计算机。对于那些管理拥有数百万客户的应用程序基础设施的人来说,每秒执行数百万次查询是很正常的。这种规模的产品通常需要数千台服务器协同工作。

让 768 台服务器看起来像 1 台 — PlanetScale

最难扩展的基础设施组件几乎总是数据库。单个数据库服务器无法满足这样的需求,因此我们必须通过数据库分片将查询和数据分散到许多服务器上。

数据库分片是扩展 Postgres 或 MySQL 数据库以处理超过几 TB 数据的最佳方法。让我们看看如何从小型单节点数据库发展到分布在四个分片上的几 TB 数据库,一直到分布在 768 个服务器上并存储 1 PB 数据的数据库。

成长的烦恼

要理解为什么分片是扩展关系数据库的必要组成部分,我们必须了解可扩展性较差的方法的瓶颈。

首先考虑一个简单的应用程序架构。

您使用过的大多数应用程序都以这种方式运行,或者至少在其存在之初是这样的。在客户端设备上运行的软件通过互联网连接到应用程序服务器。该应用程序服务器位于数据中心,负责处理身份验证、页面加载以及应用程序行为的所有服务器端逻辑。所有持久数据(例如用户帐户、帖子、设置和消息)都存储在数据库服务器中并从中检索(其中“数据库服务器”通常是 Postgres 或 MySQL,尽管本文的重点是 Postgres)。

即使使用大型数据库服务器(数十个 CPU 核心、数百个 GB 的 RAM),瓶颈也会很快出现。通常,它要么是由于高查询量而导致的 CPU 限制,要么是由于大量读取和写入而导致的 I/O 限制 (IOPS)。

通用可扩展性定律很好地总结了这一点:

简而言之,USL 指出,资源争用会导致可扩展性随着资源的增加呈次线性增长,并且在某个点上,不一致会导致性能下降。对于 Postgres 来说也是如此,就像任何试图在更大的服务器上跨多个线程或进程进行横向扩展的软件系统一样。

至少在短期内解决此问题的一种方法是利用只读副本。

在此配置中,您将原始服务器维护为主服务器并添加其他副本,如上所示。

主节点向每个副本发送连续的消息流,以确保它们及时了解主节点上的数据更改。写入(INSERT、UPDATE、DELETE)只能写入主数据库。如果允许对任何服务器进行写入,我们最终可能会出现数据冲突。解决这个问题需要复杂而缓慢的共识算法,这是可能的,但在大多数情况下对于最佳性能来说并不理想。

但是,应用程序服务器可以向副本发送读取 (SELECT) 查询。由于大多数应用程序的读取百分比远高于写入百分比,因此这提供了更多的可扩展性。 (副本对于高可用性和数据持久性也是必要的,即使查询流量不需要它们)。

数据库可以通过添加副本来扩展以处理更多流量。一个极端的例子是 OpenAI 在单个主节点上使用 50 个副本。

事实证明,垂直扩展服务器(增加 CPU / RAM)和添加副本只能让您到目前为止。有几个瓶颈无法通过这种方式解决

1)写入仅限于一台服务器

如果写入量足够高,任何额外的只读副本都无法缓解问题。在 Postgres 确认已提交的写入之前,它必须在其预写日志 (WAL) 中记录更改并将该日志刷新到持久存储。 WAL 是主节点上所有连接之间的共享资源。这本质上是整个数据库的单一写入瓶颈,即使您有数十个副本。

2)副本不会增加数据容量

副本是主数据的完整副本,包括所有索引。添加副本为我们提供了更多运行读取的位置,但它不会分发数据。

3)备份

备份是数据持久性和 RPO/RTO 保证的重要组成部分。由于节点到存储通信的带宽限制,将大型整体数据库备份到对象存储可能需要数小时甚至数天的时间。对于许多依赖频繁且经过验证的备份的组织来说,这个时间长得令人无法接受。

处理这个问题最有效的方法是分片。

分片,带“d”

分片通过将数据和查询分布在许多不同的主节点上来解决这三个瓶颈。对于数据来说,它很有用,因为单个节点只能存储这么多数据,并且写入吞吐量有限。对于查询来说,这非常有用,因为网络互连和 CPU 一次只能处理这么多查询。

分片对于超过几 TB 数据的所有规模都很有用。例如,对于 2 TB 的数据,我们可以选择具有四个分片的设置,每个分片存储 500 GB 并处理总查询流量的 1/4。当我们需要存储 PB 数据(一百万 GB)时,我们需要更多的分片。在这种情况下,我们可以使用 256 个分片,每个分片都有一个主分片 + 2 个副本,每个分片负责存储 ~4 TB。这需要 256 * 3 = 768 台服务器!

如果没有一个好的系统,这会显着增加我们应用程序后端的复杂性。发生了这么多事情,系统如何...

  • 决定哪些数据发送到哪个服务器?
  • 决定哪些查询发送到哪个服务器?
  • 处理需要同时与多个分片对话的查询?
  • 在这个分散的数据库中进行备份?
  • 监控整个系统的健康状况?
  • 响应失败的服务器?

在解决这些问题时,有很多话可以说。但本文要解决的问题如下:

对于我们的应用程序来说,这 768 台服务器如何看起来像 1 个内聚数据库?

我们希望允许应用程序服务器不再与复杂的系统交互,如下所示:

相反,通过单个连接字符串与其进行交互,使其看起来好像正在与一个大型、可扩展的数据库进行交互:

而实际上,使用数十或数百个碎片。用于 Postgres 的 Neki 和用于 MySQL 的 Vitess 解决了这个问题。让我们看看如何。

智能体层

这里的几个关键部分中最重要的是智能体层。

智能体是位于两个服务之间的中间件服务器。在我们的例子中,这两个服务是应用程序服务器和数据库服务器。

智能体经常与 Postgres 数据库一起使用。即使没有分片,它们对于连接池和请求排队也很有用。对于常规(未分片)Postgres,PgBouncer 是一种流行的智能体,人们使用它在较少的直接 Postgres 连接上复用 1000 个应用程序连接。

PgBouncer 有一个简单的目标。它的构建目的是接受来自许多客户端的大量连接,并通过它与 Postgres 持续维护的较小连接池路由它们。查询队列对于流量激增和数据库故障转移期间非常有用,因此当新的主数据库上线时,请求可以恢复。如果您想了解更多信息,我们有关于 PgBouncer 的完整博客。

对 Postgres 进行分片需要更复杂的智能体。最大的区别在于,除了多路复用和缓冲之外,智能体还必须了解数据如何跨服务器分布并将 SQL 查询路由到正确的分片。因此,我们将其称为路由器。

当插入数据时,路由器必须知道数据如何分布。这称为分片策略。

一种常见的方法是根据 id 列的哈希对传入行进行分片。将这样的行插入数据库时:

四个分片中的每一个都被分配了一个负责存储的 ID 范围,并且路由器将插入发送到正确的分片。插入首先被发送到路由器,在路由器中计算每个 ID 的哈希值,然后将其转发到正确的分片。

当谈到读取时,一些查询非常简单,以至于路由器将它们传递到单个分片。

在这种情况下,路由器需要做的就是拥有哪些用户 ID 位于哪些服务器中的内部映射,并转发该查询。根据上面的示例,这将是第一个(顶部)分片。

有些情况更为复杂。

具有此范围 ID 的用户分布在多个分片中。路由器必须了解数据拓扑,创建一个计划将查询分发到可能包含匹配结果的所有分片,将结果聚合回路由器,并将完整结果集发送到客户端。

最终,这意味着路由器本身必须内置完整的查询解析器和路由规划器。

路由器必须能够在单个系统中执行查询解析、规划、连接池和缓冲。复杂的软件很难做好。

它是怎么知道的?

每个数据库都是独一无二的,具有自己的架构、表和查询模式。那么路由器如何才能知道哪些数据和哪些查询去往何处呢?

在 Neki 和 Vitess 中,这些都是通过代表系统数据拓扑的 JSON 文件指定的。 Vitess 的 VSchema 和 Neki 的数据拓扑为工程师提供了极大的灵活性,可以准确地描述表和查询应如何分布。下面是我们如何为用户表指定分片方案的简化示例:

该元数据存储在路由器中,并告诉路由器使用 user_hash 分片索引在其 id 列上对用户表进行分片。此 user_hash 分片索引使用路由器的内置值哈希。对于每个传入行,它都会对 ID 进行哈希处理,并使用它将其发送到正确的分片进行存储。

由于这一切都是通过文本和 JSON 传达给路由器的,因此 AI 智能体非常适合此处的配置和优化。

多个智能体,一个数据库

在跨越 768 个服务器的 256 个分片和每秒数百万次查询的规模下,我们无法通过单个智能体路由所有这些流量。我们需要很多!也许是 10 个,也许是 100 个,具体取决于交通状况。

我们仍然希望我们的应用程序将其视为单个服务器。这就是网络负载均衡器 (NLB) 的用武之地。

NLB 的工作很简单:允许通过单个主机/IP 进行连接,并将每个连接分配到多个目标之一。这就是流量在路由器之间分配的方式。一旦分配,连接在其生命周期内将保留在同一智能体上。

在某些情况下,不需要 NLB。消除 NLB 会稍微增加应用程序服务器连接逻辑的复杂性,因为它必须了解每个路由器的主机,但消除了网络跃点,从而将往返延迟保持在最低水平。

完整图片

现在,所有部件均已就位,使存储 1,000 TB 数据的 768 台服务器在我们的应用程序中显示为单个整体数据库。

  1. 应用服务器被告知“连接到位于 mydb.pscale.com 的数据库”
  2. 执行 DNS 查找,返回 NLB 的 IP 地址:123.152.100.4
  3. 应用程序请求连接到 123.152.100.4 的数据库
  4. 这将首先通过 NLB 路由连接,然后路由到 N 个智能体之一
  5. 应用程序开始发送数据库查询,该查询转到应用程序 -> NLB(可选) -> 智能体 -> 分片。复杂的路由逻辑对应用程序来说是隐藏的。 (为简单起见,NLB 未在下面显示)

此示例显示扩展至 1 PB,但分片应早于此扩展开始。精确的建议取决于每个数据库的大小、架构和 QPS,但我们建议对超过几 TB 的数据进行分片 Postgres 和 MySQL。这就是您通常开始遇到前面描述的瓶颈的地方:长时间备份、写入瓶颈等。如果您面临扩展关系数据库的挑战,Neki 和 Vitess 就是解决方案。

Vitess for MySQL 十多年来一直用于扩展世界上最大的关系数据库。我们拥有多年为客户运营大型分片数据库的经验,是 Vitess 项目的核心维护者。 Neki 是由 Vitess 的专家维护者开发的,为 Postgres 带来了更强大的分片系统。

其他一切又如何呢?

我们只触及了 Neki 和 Vitess 等分片系统提供的所有内容的表面。还有很多其他有趣的细节。分片数据的最佳方法是什么?分片数据库如何处理故障?如何更改分片数量?如何同时跨 256 个分片进行备份?

请继续关注此处了解更多信息。关注我们的 RSS 源或 X 以了解最新动态。

快乐分片。

热门栏目