写点什么

边缘上的关系型数据库:Cloudflare 如何运维分布式 PostgreSQL 集群

作者:Vignesh Ravichandran, Justin Kwan

  • 2024-04-18
    北京
  • 本文字数:6359 字

    阅读完需:约 21 分钟

大小:3.13M时长:18:14
边缘上的关系型数据库:Cloudflare如何运维分布式PostgreSQL集群

这是我们在 2023 年 10 月份旧金山 QCon 上的演讲摘要,在这次演讲中,我们讨论了高可用性设置,并考虑了 Cloudflare 围绕系统的每个组成部分必须要做出的权衡。我们会探讨将数据库基础设施放到比以往更靠近边缘时所面临的一些性能挑战,并深入介绍已实现的解决方案。

什么是边缘?

在分布式系统中,边缘(Edge)指的是在地理位置上更靠近客户端的服务。这包括 DNS 解析、内容交付和 IP 防火墙等任务。在传统上,会由中心化的数据中心来托管关系型数据库等服务,它们距离客户端比较远。与客户端的距离远近决定了度量指标,通过在更靠近客户端的地方为请求提供服务,可以减少网络延迟和成本。

控制平面

Cloudflare 保护着超过 2700 万个互联网属性,平均每秒钟管理 4600 万个 HTTP 请求。网络覆盖全球 250 个接入点(Points of Presence),在最繁忙的 PostgreSQL 集群上,峰值时每秒处理超过 5500 万次行操作,所有集群的数据量超过 50TB。

 

Cloudflare 的控制平面需要编排数量众多的微服务,用来支撑仪表盘。它管理数据路径上关键网络服务的规则和配置。数据库集群存储着每个消费者的数据,比如 DNS 记录的变更、防火墙规则、DDoS 防护、API 网关路由以及内部服务信息(如计费权限和用户认证)。应用团队经常利用 PostgreSQL 来实现特定的目的,使用存储过程来执行具有事务一致性的业务逻辑。PostgreSQL 还可以用作发件箱(outbox)队列,捕获领域事件,比如在中心化数据中心由流量分析生成的 DDoS 规则。

 

有一个单独的守护进程(daemon)会从数据库中检索这些事件,并通过 Kafka 将其传递给边缘服务,从而使对延迟要求较高的服务能够绕过对数据库的直接访问,并且能够从 PostgreSQL 和 Kafka 提供的持久性中受益。

 

Cloudflare 在裸机上运行软件和服务,需要处理机架式(rack-mounted)服务器、高带宽网卡和运维维护。内部自建(on-premise)的数据存储提供了最大的灵活性,允许团队对固态 RAID 配置进行微调,并利用开源集群管理系统对系统进行增强。而 Amazon RDS 等托管云数据库不可能实现集群的这种透明度和细粒度控制。

 

令人遗憾的是,这意味着当负载增加时,没有立即可用的自动扩展按钮来提高集群的容量,这也是采用内部自建的方式运行整个技术栈所面临的固有挑战。

架构

我们来讨论一下 Cloudflare 的边缘数据库架构,它需要每秒处理数百万笔交易。

 

在设计系统时,团队优先考虑到了高可用性,将服务等级目标(Service-Level Objective,SLO)设定为 5 个 9,整个技术栈每年最多停机五秒钟。事务性工作负载偏重于读取操作。基础设施必须以最小的延迟处理高速的读写操作,并保持容错性。Cloudflare 在内部使用了一个任播(anycast)BGP网络,客户端会自然地在 PgBouncer 代理实例之间实现负载平衡。

 

BGP Anycast 可优化客户端附近的读取查询,而写入查询则会转发到主数据库实例所在的主区域。



图 1:Cloudflare 数据库架构——图片来源

 

在顶部,PgBouncer为应用程序客户端管理数据库服务器连接池。然后,HAProxy 在多个数据库集群实例之间平衡负载查询,防止过载。在典型的配置中,一个主实例会将数据复制到多个副本,以实现高速的读取查询,它们是由 etcd 支撑的stolon高可用集群管理器来进行管理的。在此环境中,etcd用来实现分布式集群领导共识和配置一致性。

 

主备(active-standby)模式可确保跨区域的数据冗余,位于 Portland 的主区域负责入站查询,而位于 Luxembourg 的备用区域则随时准备进行流量重定向或故障转移。

 

现在,我们将会对每一层进行分析,探讨各个组件及其权衡。

边缘上的持久化

构建高度分布式和关系型边缘数据的决定推动团队思考基本的架构原则,尤其是CAP理论。因为应用程序是分布式的,并且依赖于 PostgreSQL 这样的开源软件,所以必须优先考虑一致性或高可用性,两者只能选其一,人们通常会倾向于后者。

 

在单数据中心的场景中,典型的架构包括一个主数据库、一个同步的副本以及多个异步的副本。这种拓扑结构可以跨多个数据中心复制。半自动复制的配置可以确保异步副本出现故障时,不会对应用程序造成重大的影响。同样的权衡也适用于跨区域的复制,这样能够允许一个区域的应用程序在另一个区域宕机时继续工作。但是,如果同步副本出现故障,整个应用程序会停止运行,以避免出现不一致。这种半自动复制的拓扑结构有效地平衡了各种需求。

 

PostgreSQL 起源于90年代的伯克利,最初设计时采用的是单体架构。为了将其转变成分布式系统,我们在很大程度上依赖了两个方面的复制,即逻辑复制(logical replication)和流复制(streaming replication)。第三个选项是级联复制,它构建在逻辑复制和流复制之上。

 

在深入研究复制在创建分布式集群中的作用之前,我们先简单介绍一下预写式日志(write-ahead logs)(WAL)。在包括 MySQL 和 Oracle 的所有关系型数据库中,ACID 的持久性都是通过 WAL 实现的。对数据库的变更最初保存在易丢失的内存中,然后异步刷新到磁盘,这些更改会首先按顺序记录到基于磁盘的 WAL 文件中。这样就能在数据库崩溃时通过恢复确保数据的持久性。

 

实践证明,这一特性在构建复制系统时至关重要,可以方便地捕获和复制工作单元(即每个日志条目)。

 

流复制模式非常简单:每个副本建立一个 TCP 连接,然后将日志条目从主副本按照流的方式传输到另一个副本。它的显著优势在于性能,每秒钟可以捕获 1TB 的数据变更,并且延迟极小,我们会在本文后续的内容中介绍潜在的延迟原因。需要注意的是,这是一种“全有或全无(all-or-nothing)”的方式,由于其文件系统级别具有块(block)复制的特点,所以会复制每个 Postgres 对象的所有变更。

 

逻辑复制是一个更新的版本,它在 SQL 层面运行,提供在表、行和列级别复制数据的灵活性。不过,它无法复制 DDL 变更,需要自定义的工具,而且在可扩展性方面面临着挑战,尤其是在 TB 级的规模时。

集群管理

我们进行集群管理的主要原因之一是解决数据库故障。不管是逻辑故障(如数据损坏)还是更严重的故障(如自然灾害导致的硬件故障),它们都是难以避免的。在这种情况下,采用手动方式进行故障转移都是非常繁琐的。此外,大约有 5%的 Cloudflare 商品服务器可能在任何时间点出现故障,这意味着上千台服务器和多个数据中心随时都会面临故障。自动化的集群管理系统对于高效处理这些多样且关键的故障是至关重要的。

 

团队选择了使用stolon,这是一个用 Go 编写的开源集群管理系统,在 PostgreSQL 集群之上运行,是很薄的一层,它具有 PostgreSQL 原生的接口,支持多站点冗余。不管是在单个区域内,还是跨多个区域,我们都可以跨多个 PostgreSQL 集群部署单个 Stolon 集群。Stolon 的特性包括稳定的故障转移(误报率极低)以及将 Keeper 节点作为父进程管理 PostgreSQL 的变更。Sentinel 节点作为编排者,监控 Postgres 组件的健康状况并做出决策,如启动新主副本节点的选举。Proxy 节点负责处理客户端连接,确保只有一个写入主副本,避免出现多主副本的情况。

 


图 2:stolon 的架构

连接池

数据库连接是有限的资源,鉴于其固有的开销,所以需要对其进行有效的管理。

 

PostgreSQL 连接依赖于 TCP 协议,其中包括三次握手和 SSL 握手以确保通信安全。每个连接都需要一个独立的操作系统级别的进程,这就要求主 postmaster 进程必须进行 fork 操作,从而导致 CPU 时间的消耗和内存的占用。随着上千的并发连接被打开,分配给事务处理的套接字描述符和 CPU 时间都会相应的减少。

 

服务器端连接池可以管理固定数量的数据库连接,同时向客户端提供与 PostgreSQL 兼容的接口。作为一个中介,当客户通过它连接到数据库时,会循环使用这些连接,从而减少打开的连接数。

 

这样可以集中控制租户资源,使团队能够调节每个上游应用服务分配的连接数。 Cloudflare对PgBouncer的增强包括了受TCP Vegas启发的特性,TCP Vegas 是一种 TCP 拥塞避免算法,通过限制租户的总吞吐量来实现更严格的多租户资源隔离。

 

Cloudflare 选择了使用 PgBouncer 作为连接池,这是因为它与 PostgreSQL 协议能够兼容,即客户端可以连接到 PgBouncer 上并像往常一样提交查询,这简化了数据库切换和故障转移的处理。PgBouncer 是一个轻量级的单进程服务器,以异步方式管理连接,因此能比 PostgreSQL 处理更多的并发连接。



图 3:PgBouncer 的运行原理

 

PgBouncer 引入了客户端连接,与直接的 PostgreSQL 服务器连接区分开来,并采用了高效的非阻塞网络 I/O 模型,利用单线程和操作系统提供的epoll机制进行事件监控。

 


图 4:PgBouncer 进程

 

与 PostgreSQL 每个连接对应一个线程(thread-per-connection)的模型不同,PgBouncer 的单线程事件循环避免了多个线程栈,从而能够最大限度地减少了内存使用量,并通过在一个线程中为所有客户端的请求提供服务来提高 CPU 利用率,防止闲置连接线程浪费 CPU 时间。

 

为了解决让多个单线程 PgBouncer 进程充分利用一台机器上所有 CPU 内核的难题,我们在操作系统中找到了解决方案:团队添加了SO_REUSEPORT套接字选项,允许多个进程套接字同时监听同一个端口。

负载均衡

Cloudflare使用HAProxy负载均衡器在多个 PostgreSQL 服务器之间平均分配传入的数据库查询,防止单个服务器过载。与 PgBouncer 类似,HAProxy 通过将故障服务器上的流量重定向到健康服务器上,提供了高可用性和容错性,从而减少因数据库实例性能下降而造成的停机时间。HAProxy 通过使用内核的splice系统调用,能够以最小的开销在第 4 层有效地处理 TCP 连接。入站和出站的 TCP 流均能在内核中转换,无需将数据复制到用户空间就可以实现数据传输。

挑战和解决方案

接下来,我们探讨数据库基础架构面临的挑战,并分享一些在边缘实现高可用性的性能技巧。复制延迟(replication lag)在大流量和写密集型操作(如执行 ETL 作业)时会特别明显。所有的批写入操作(如数据迁移和 GDPR 合规删除),甚至自动存储压缩(autovacuum)也会扩大复制延迟。

 

我们的团队将复制延迟的 SLO 定为 60 秒,并据此向应用团队提出建议。为尽量减少复制延迟,SQL 写入被分批打包成较小的块,以确保复制更加顺畅。通过缓存,或者在写入主副本或同步副本后直接读取,以保持写入后读取(read-after-write)的一致性。避免延迟的一种非常规方法是将所有副本从集群中删除,只留下主副本。虽然这种方法在实践中很有效,但需要深入了解查询工作负载和潜在的系统变化。你可以将其视为 Rust 中的 unsafe 关键字。

 

2020 年,一次重大事故严重影响了 Cloudflare 的数据库性能和可用性。级联故障导致 API 可用性下降了 75%,仪表盘速度慢了 80 倍。主区域和备用区域的主数据库实例都执行了故障转移,但主区域的新主数据库在高负载下并没有完全恢复,这导致再次出现了故障。由于集群中没有同步副本可以对外发布,因此必须做出一个关键决定:要么同步一个异步副本,但这可能会造成数据丢失,要么将区域疏散到备用集群,但会造成额外的停机时间。

 

对我们来说,数据丢失是不可接受的,因此我们选择了后一种方案。进一步的调查发现,网络交换机出现了部分故障,处于降级状态,这阻断了跨区域的两个 etcd 集群之间的通信。

 

无法通信导致 Raft 协议进入死锁状态,进而造成了只读的集群。故障转移和重新同步暴露了 PostgreSQL 的低效率,特别是在重新同步的耗时方面。该配置能有效处理机器崩溃这样的全面故障,但当 Raft 集群中的节点开始提供相互冲突的信息时,就会出现难以预料的行为的问题。

 

例如,由于交换机故障,节点 1、2 和 3 的网络连接能力下降。节点 1 误以为节点 3 不再是领导者,从而导致一系列的领导者选举失败,并形成死锁。该死锁迫使群集进入只读模式,中断了区域间的通信,并触发了向主副本的故障转移。



图 5:集群中的冲突信息

 

在故障转移的过程中,同步副本进行了升级,由于事务历史可能存在分歧,因此需要旧的主副本撤销已提交的事务。在解除存在分歧的历史后,同步副本必须从更正后的主副本接收并重播新事务,在我们的场景中,由于同步副本没有吸收新数据而发生了故障,因此导致了停机。

 

我们发现的问题包括硬件故障、Raft 罕见的拜占庭故障以及 Postgres 重新同步时间过长。团队优先通过优化 Postgres 来解决第三个问题。分析日志发现,大部分时间都花在了 rsync 上,在重新同步过程中复制了超过 1.5 TB 的日志文件。

 


图 6:pg_rewind复制所有的日志,包括最后一个分歧点前的通用段

 

解决方案包括从内部优化 PostgreSQL,只复制最后一个分歧点的必要文件,这将数据传输量减少到原始大小的 5%。对 PostgreSQL 进行内部优化后,副本重建时间从 2 个多小时缩短到 5 分钟,速度提高了 95%。



图 7:修复后的 pg_rewind 仅从最后一个分歧点复制日志

 

从这次大规模故障中,我们吸取了如下的经验教训:

  • 预测降级的状态,而不仅仅是完全故障的状态

  • 投资开源软件,以获取内部的专业技能。我们提交的开源补丁为PostgreSQL CommitFest做出了贡献。

  • 在内部构建和修复软件的重要性。

从边缘访问数据

仅仅在一个区域维护单个集群是不够的。为了应对各种故障,包括拜占庭式硬件故障所带来的挑战性场景,我们需要将 PostgreSQL 集群分布到多个区域。从Cloudflare Workers的角度来看,这种“集群的集群(cluster-of-clusters )”方式增强了韧性。

 

Cloudflare 从基于美国的主区域扩展到欧洲和亚洲,形成了使用 PostgreSQL 流复制的中心辐射(hub-and-spoke)模式。

 

确保跨区域的同步至关重要,它可以处理复制延迟等问题:当面临高复制延迟时,将流量转回主区域有助于满足应用团队的 SLA。这一策略增强了系统的健壮性,减轻了潜在故障的影响。

 

分布式集群具有显著的优势,尤其是在实施智能故障转移方面更是如此。基于当前的负载、可用容量或时区(“follow-the-sun”——指的是根据事件发生的时间,在全球范围内选择当前在办公时间的人员来及时处理对应的问题,参见该文——译者注)等因素都策略性地影响如何选择主区域,我们根据全球不同地区的活跃时间来降低延迟。这种动态方法解决了 PostgreSQL 的单主限制。

 

容量方面的考虑也会影响选择:某些地区的容量可能更大,而物流方面的挑战(如 COVID 期间的硬件运输)也会影响决策。流量模式和合规性要求会进一步影响区域的选择,以便于团队能够高效地满足特定的监管需求。采用分布式方法使 Cloudflare 能够有效地应对这些挑战。

 

将 PostgreSQL 扩展到多个区域并实现快速故障转移(如前面讨论的pg_rewind等工具)是非常高效的。但是,当考虑到应用程序的依赖性时,就会出现一定的复杂性。虽然数据库的迁移是可控的,但在整个应用生态系统(包括 Kafka 消息队列等分布式组件)上进行故障转移却是一项挑战。在切换区域时,了解完整的应用依赖关系图至关重要。

 


图 8:完整的应用依赖图

 

尽管自动化相关的工作正在进行中,但来自不同组织的团队之间的协调变得至关重要。跨区域切换服务的顺序要考虑到 Kubernetes 集群中的数据库集群、应用服务(如身份认证和配置),以及它们对 R2 和 SSL 等中央基础设施的依赖性。

数据库的发展趋势

作为实践者,我们观察到关系数据库的一些主要趋势。首先,人们开始将数据嵌入到边缘中,在有些区域使用单体或 PostgreSQL,并辅以 SQLite 等嵌入式数据库进行真正的边缘处理。保持边缘的 SQLite 与核心/中心位置的 PostgreSQL 数据同步至关重要。此外,确保 SQLite 与 PostgreSQL 的兼容对于无缝集成也非常重要。

 

另一个趋势涉及边缘的持久化,由 Cloudflare Workers 处理客户端数据。此外,存储和计算也在向协同分布(colocation)转变,智能放置(Smart Placement)等特性就说明了这一点。



图 9:Cloudflare Workers 与数据库集群的协同分布

 

这样可以避免跨区域网络跳转到中心化的数据存储,从而减少延迟,并解决了许多客户端应用程序花费大量时间与数据库通信的问题。

 

最后,数据本地化也是一项挑战,尤其是在使用 PostgreSQL 的欧洲地区。逻辑复制是关键点,它提供了在行或列级别进行复制的灵活性,但目前仍处于探索阶段。预计逻辑复制将在应对这一挑战方面发挥重要作用。


原文链接:

 Relational Data at the Edge: How Cloudflare Operates Distributed PostgreSQL Clusters

2024-04-18 08:005488

评论

发布
暂无评论

【转】阿秀:C++经典49问49答

杨明越

60K*17薪的面试题是什么样的,需要具备什么技术?首发“Java面试考点大全”

Java架构之路

Java 程序员 架构 面试 编程语言

产品训练营第五周作业

产品经理训练营

2021 iOS底层提升计划

iOS底层

最值得期待的Python进阶宝典《Effective Python》第2版中文版来咯!

华章IT

Python 代码整洁 Python编程 EffectivePython Python进阶

c语言函数与指针基础

赫鲁小夫

编程常见必备知识

梦醒了

编程 链表 函数

快速搭建本土「Clubhouse」

anyRTC开发者

ios android WebRTC RTC 语音通话

助力文化荟萃,区块链或可打造“新”春晚?

CECBC

区块链 春晚

关于零售业数字化势在必行的一些杰出观点

小刘在学习

新零售

计算机内部的总线是什么

FishyFine

计算机结构

话题讨论 | 你”节后综合症“了吗?

程序员架构进阶

话题讨论 28天写作 2月春节不断更 话题王者 节后焦虑

开源微服务运行时 Dapr 发布 1.0 版本

阿里巴巴云原生

开源 容器 微服务 开发者 云原生

OAtuth2.0 知多少

圣杰

oauth2.0 dotnet

LeetCode题解:322. 零钱兑换,动态规划,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

读书笔记-MySQL索引

yunCrush

jenkins Android 自动构建-扫码下载

三爻

量化机器人APP开发|量化机器人软件系统开发

系统开发

Your small business questions, answered

小韩

面试腾讯T3,过关斩将直通3面,终斩获offer流下了激动的泪水(腾讯面经总结分享)

Java架构之路

Java 程序员 架构 面试 编程语言

第二次书摘的微信视频号直播复盘

小匚

用户体验

Linux Jenkins 自动打包vue部署到远程window服务器

三爻

Vue jenkins Win

作业5

赝品

2021最新总结:阿里/京东/饿了么/拼多多/爱奇艺面经分享(堪称Offer收割机)

比伯

Java 编程 架构 面试 计算机

为了面试阿里巴巴、腾讯、字节跳动、京东、华为等大厂,我收集了8家大厂高频Java面试真题集锦(含答案)

Java架构之路

Java 程序员 架构 面试 编程语言

云算力系统开发|云算力APP软件开发

系统开发

区块链技术的价值传递

CECBC

区块链

量化炒币机器人软件系统开发|量化炒币机器人app开发

系统开发

区块链技术带来金融服务的信任变革

CECBC

金融

做一个很出色的程序员

四猿外

Java 程序员 面试 架构师 职场成长

菜单展示的递归与非递归形式实现 (go语言版)

松小鼠

数据结构和算法

边缘上的关系型数据库:Cloudflare如何运维分布式PostgreSQL集群_大数据_InfoQ精选文章