pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
数据库服务器可以协同工作,从而在主库失效时让第二台服务器快速接管其任务(高可用性),或者让多台计算机提供同一份数据(负载均衡)。理想情况下,数据库服务器应当能够无缝协同工作。提供静态网页服务的 Web 服务器只需把 Web 请求分发到多台机器,就能很容易地组合起来。事实上,只读数据库服务器也相对容易组合起来。不幸的是,大多数数据库服务器同时处理读写请求,而读/写服务器要更难组合。这是因为,只读数据只需要在每台服务器上放置一次,而对任意一台服务器的写入都必须传播到所有服务器,以保证后续对这些服务器的读取请求返回一致的结果。
这种同步问题是服务器协同工作的根本难点。由于不存在一种能够对所有使用场景都消除同步问题影响的单一方案,因此出现了多种不同的解决方案。每一种方案都以不同方式处理这个问题,并且针对特定负载将其影响降到最低。
某些方案通过只允许一台服务器修改数据来处理同步。能够修改数据的服务器称为读/写或主(master)服务器。能够回答只读查询的服务器称为从(slave)服务器。在被转变为主服务器之前不能访问的服务器称为备(standby)服务器。
某些方案是同步的,即一个修改数据的事务只有在所有服务器都提交该事务之后才被视为已提交。这保证一次故障切换不会丢失任何数据,并且所有负载均衡的服务器无论查询哪一台都将返回一致的结果。相反,异步方案允许一次提交与其传播到其他服务器之间存在一定延迟,这就带来了切换到备库时丢失某些事务的可能性,也意味着负载均衡的服务器可能返回略微陈旧的结果。当同步通信过慢时,就会使用异步通信。
这些方案还可以按粒度分类。有些方案只能处理整个数据库服务器,而另一些则允许在每个表或者每个数据库级别进行控制。
无论做出哪种选择,都必须考虑性能。功能与性能之间通常存在权衡。例如,在低速网络上采用完全同步的方案,性能可能下降一半以上,而异步方案对性能的影响则可能很小。
本节其余部分将概述多种故障切换、复制和负载均衡方案。另有一份术语表可供参考。
共享磁盘故障切换只保留数据库的一份拷贝,从而避免了同步开销。它使用一个由多台服务器共享的磁盘阵列。 如果主数据库服务器失效,备库服务器能够挂载并启动数据库,就像从一次数据库崩溃中恢复一样。这样可以实现快速故障切换且不丢失数据。
共享硬件功能在网络存储设备中很常见。使用网络文件系统也是可行的,但必须注意该文件系统具有完整的 POSIX 行为(见第 17.2.1 节)。这种方法的一个重要局限是,如果共享磁盘阵列失效或损坏,主库和备库都无法工作。另一个问题是,当主库运行时,备库绝不能访问共享存储。
共享硬件功能的一个修改版本是文件系统复制,即对一个文件系统的所有更改都被镜像到另一台计算机上的文件系统。唯一的限制是,镜像的进行方式必须确保备库服务器拥有该文件系统的一致副本 — 具体来说,对备库的写入必须与主库上的写入按相同顺序进行。DRBD 是 Linux 上一种流行的文件系统复制方案。
温备服务器(见第 24.4 节)可以通过读取预写式日志(WAL)记录流来保持最新。如果主库失效,温备几乎拥有主库的全部数据,并且可以被快速提升为新的主数据库服务器。这种方式是异步的,而且只能对整个数据库服务器进行。
主-从复制设置把所有数据修改查询都发送到主服务器。主服务器异步地把数据更改发送到从服务器。在主服务器运行的同时,从服务器可以回答只读查询。从服务器非常适合数据仓库类查询。
Slony-I 是这类复制的一个例子,它具有按表的粒度,并支持多个从库。由于它异步(分批)地更新从库,故障切换期间可能丢失数据。
使用基于语句的复制中间件时,一个程序拦截每条 SQL 查询并将其发送到一台或所有服务器。每台服务器独立运行。读写查询被发送到所有服务器,而只读查询可以只发送到一台服务器,从而分散读取负载。
如果查询只是简单地原样广播,那么像 random()、CURRENT_TIMESTAMP 和序列这样的函数在不同服务器上会得到不同的值。这是因为每台服务器独立运行,而且广播的是 SQL 查询(而不是实际被修改的行)。如果这一点不可接受,中间件或应用就必须从单一服务器查询这类值,然后在写查询中使用这些值。此外,还必须注意让所有事务在所有服务器上要么提交要么中止,或许需要使用两阶段提交(PREPARE TRANSACTION和COMMIT PREPARED)。Pgpool-II 和 Sequoia 是这类复制的例子。
对于不经常连接的服务器,例如笔记本电脑或远程服务器,在服务器之间保持数据一致是一个挑战。使用异步多主复制时,每台服务器独立工作,并定期与其他服务器通信以识别冲突的事务。冲突可以由用户或冲突解决规则来处理。Bucardo 是这类复制的一个例子。
在同步多主复制中,每台服务器都可以接受写请求,被修改的数据在每个事务提交之前从原始服务器传送到其他所有服务器。繁重的写活动可能导致过度的锁竞争,从而使性能变差。事实上,写性能往往比单一服务器还差。读请求可以发送到任意服务器。有些实现使用共享磁盘来降低通信开销。同步多主复制最适合以读为主的负载,不过它的一大优势是任何服务器都可以接受写请求 — 无需在主从服务器之间划分负载,而且由于数据更改是从一台服务器发送到另一台的,像 random() 这样的非确定性函数也不会有问题。
PostgreSQL 不提供这种类型的复制,不过 PostgreSQL 的两阶段提交(PREPARE TRANSACTION和COMMIT PREPARED)可以在应用代码或中间件中用来实现它。
由于 PostgreSQL 是开源且易于扩展的,许多公司基于 PostgreSQL 创建了具有独特故障切换、复制和负载均衡能力的商业闭源方案。
表 25.1总结了上面列出的各种方案的能力。
表 25.1. 高可用、负载均衡与复制特性矩阵
| 特性 | 共享磁盘故障切换 | 文件系统复制 | 使用 PITR 的温备 | 主-从复制 | 基于语句的复制中间件 | 异步多主复制 | 同步多主复制 |
|---|---|---|---|---|---|---|---|
| 最常见的实现 | NAS | DRBD | PITR | Slony | pgpool-II | Bucardo | |
| 通信方法 | 共享磁盘 | 磁盘块 | WAL | 表行 | SQL | 表行 | 表行与行锁 |
| 无需特殊硬件 | • | • | • | • | • | • | |
| 允许多个主服务器 | • | • | • | ||||
| 主服务器无额外开销 | • | • | • | ||||
| 无需等待多台服务器 | • | • | • | • | |||
| 主库失效绝不丢数据 | • | • | • | • | |||
| 从库接受只读查询 | • | • | • | • | |||
| 按表的粒度 | • | • | • | ||||
| 无需冲突解决 | • | • | • | • | • |
还有少数几种方案不属于上面的类别:
数据分区把表拆分成多个数据集。每个数据集只能被一台服务器修改。例如,数据可以按办公室分区,比如伦敦和巴黎,每个办公室有一台服务器。如果需要组合伦敦和巴黎数据的查询,应用可以查询两台服务器,或者使用主/从复制在每台服务器上保留另一个办公室数据的只读副本。
上面的许多方案允许多台服务器处理多个查询,但没有一种允许单个查询使用多台服务器来更快地完成。这种方案允许多台服务器并发地处理单个查询。它通常通过在服务器之间拆分数据,让每台服务器执行查询中属于自己的部分并把结果返回到一台中央服务器,在那里合并后返回给用户来实现。Pgpool-II 具有这种能力。此外,也可以使用 PL/Proxy 工具集来实现。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。